Skip to content

Add a public-aware deserializer for recipient URLs#165

Merged
Nutomic merged 2 commits intoLemmyNet:mainfrom
dahlia:fix/public-addressing-aliases
Apr 24, 2026
Merged

Add a public-aware deserializer for recipient URLs#165
Nutomic merged 2 commits intoLemmyNet:mainfrom
dahlia:fix/public-addressing-aliases

Conversation

@dahlia
Copy link
Copy Markdown
Contributor

@dahlia dahlia commented Apr 22, 2026

Related to LemmyNet/lemmy#6465 and LemmyNet/lemmy#6466 (comment).

ActivityPub allows the public collection to be represented as the canonical IRI, as:Public, or Public. As described in that issue, this can break incoming recipient parsing if only the canonical URL is accepted.

This PR adds a Url-specific deserialization helper for recipient fields, instead of changing the behavior of the generic deserialize_one_or_many() helper. The new helper accepts Public and as:Public and normalizes them to the canonical ActivityStreams public URL.

It also adds focused unit tests for single-value and array inputs, plus a regression test to make sure unrelated string fields are left unchanged.

Note

This patch was developed with assistance from GPT-5.4. I reviewed the final changes myself, verified the final behavior locally, and ran the relevant tests before opening this PR.

Comment thread src/protocol/helpers.rs Outdated
Value::String(value) => Url::parse(&value).map_err(D::Error::custom),
value => Url::deserialize(value).map_err(D::Error::custom),
})
.collect()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need for a new method here. Instead put this code inside the existing deserialize_one_or_many method. That way nothing needs to be updated in Lemmy (except updating the library version).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered putting this into deserialize_one_or_many(), but kept it as a separate Url-specific helper because deserialize_one_or_many() is generic over T.

If we normalize Public/as:Public inside the generic helper by pre-processing serde_json::Value, this can also affect non-URL uses of the helper, for example Vec<String> or Vec<Value> fields where "Public" is meant to remain a plain string.

Would you still prefer changing deserialize_one_or_many() directly despite that broader behavior change? If so, I can rework the PR in that direction and document the behavior.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you see my other comment? That one would be the simplest solution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see how that would make the Lemmy-side change much smaller.

One thing I want to confirm before changing direction: if we only extend the visibility check, as:Public should work because it can be represented as a Url, but bare Public still seems like it would fail while deserializing to/cc into Vec<Url>, before the visibility check runs.

Is that acceptable? In other words, should this only handle URL-shaped public aliases such as as:Public, and leave bare Public unsupported? Or am I missing nother path where bare Public is accepted before verification?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that is true, didnt think of it. I tried just now to remove the generic T and it works with the Lemmy code. So then you can delete the existing deserialize_one_or_many method, and rename your new method to deserialize_one_or_many.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that makes sense.

I'll rework this PR in that direction: remove the generic T version of deserialize_one_or_many(), rename the new Url-specific implementation to deserialize_one_or_many(), and update the docs/tests accordingly.I’ll rework this PR in that direction: remove the generic T version of deserialize_one_or_many(), rename the new Url-specific implementation to deserialize_one_or_many(), and update the docs/tests accordingly.

After that, LemmyNet/lemmy#6466 should only need to update the dependency and can keep the existing deserialize_one_or_many call sites.

Update deserialize_one_or_many to deserialize recipient URL fields while
accepting `Public` and `as:Public` as aliases for the canonical
ActivityStreams public URL.

Add focused tests for single and array inputs, and verify that unrelated
string fields such as `content` are left unchanged.

LemmyNet/lemmy#6465
@dahlia dahlia force-pushed the fix/public-addressing-aliases branch from a2a6083 to eb45245 Compare April 23, 2026 13:04
@dahlia dahlia requested a review from Nutomic April 23, 2026 13:27
Comment thread src/protocol/helpers.rs
}
Value::String(value) => Url::parse(&value).map_err(D::Error::custom),
value => Url::deserialize(value).map_err(D::Error::custom),
})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
})
.unique()
})

No need to keep multiple identical values. The unique function is from itertools.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks. I applied that in 7cfd394, so equivalent Public aliases are now deduplicated after deserialization.

Drop repeated recipient URLs after deserialization so equivalent public
aliases such as `Public`, `as:Public`, and the canonical public URL do
not produce duplicate entries.

Update the helper documentation and tests to match the deduplicated
result.
@dahlia dahlia requested a review from Nutomic April 24, 2026 04:55
@Nutomic Nutomic merged commit 838dd9e into LemmyNet:main Apr 24, 2026
1 check passed
@Nutomic
Copy link
Copy Markdown
Member

Nutomic commented Apr 24, 2026

Good job, thank you!

dahlia added a commit to dahlia/lemmy that referenced this pull request Apr 24, 2026
Update Lemmy to activitypub_federation 0.7.0-beta.11, which
includes support for `Public` and `as:Public` recipient aliases.

No local deserialization changes are needed now that the upstream fix
has been merged and published.

LemmyNet/activitypub-federation-rust#165
LemmyNet#6465
Nutomic pushed a commit to LemmyNet/lemmy that referenced this pull request Apr 24, 2026
Update Lemmy to activitypub_federation 0.7.0-beta.11, which
includes support for `Public` and `as:Public` recipient aliases.

No local deserialization changes are needed now that the upstream fix
has been merged and published.

LemmyNet/activitypub-federation-rust#165
#6465
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants