Skip to content

Commit 838dd9e

Browse files
authored
Add a public-aware deserializer for recipient URLs (#165)
* Accept Public aliases in URL deserializer 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 * Deduplicate deserialized recipients 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.
1 parent 279d29d commit 838dd9e

1 file changed

Lines changed: 107 additions & 12 deletions

File tree

src/protocol/helpers.rs

Lines changed: 107 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
//! Serde deserialization functions which help to receive differently shaped data
22
3-
use serde::{Deserialize, Deserializer};
3+
use activitystreams_kinds::public;
4+
use itertools::Itertools;
5+
use serde::{de::Error, Deserialize, Deserializer};
6+
use serde_json::Value;
7+
use url::Url;
48

5-
/// Deserialize JSON single value or array into Vec.
9+
/// Deserialize JSON single value or array into `Vec<Url>`.
610
///
711
/// Useful if your application can handle multiple values for a field, but another federated
812
/// platform only sends a single one.
913
///
14+
/// Also accepts common `Public` aliases for recipient fields. Some implementations send `Public`
15+
/// or `as:Public` instead of the canonical `https://www.w3.org/ns/activitystreams#Public` URL
16+
/// in fields such as `to` and `cc`.
17+
///
1018
/// ```
19+
/// # use activitypub_federation::kinds::public;
1120
/// # use activitypub_federation::protocol::helpers::deserialize_one_or_many;
1221
/// # use url::Url;
1322
/// #[derive(serde::Deserialize)]
@@ -25,24 +34,39 @@ use serde::{Deserialize, Deserializer};
2534
/// "https://lemmy.ml/u/bob"
2635
/// ]}"#)?;
2736
/// assert_eq!(multiple.to.len(), 2);
28-
/// Ok::<(), anyhow::Error>(())
29-
pub fn deserialize_one_or_many<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
37+
///
38+
/// let note: Note = serde_json::from_str(r#"{"to": ["Public", "as:Public"]}"#)?;
39+
/// assert_eq!(note.to, vec![public()]);
40+
/// # Ok::<(), anyhow::Error>(())
41+
/// ```
42+
pub fn deserialize_one_or_many<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
3043
where
31-
T: Deserialize<'de>,
3244
D: Deserializer<'de>,
3345
{
3446
#[derive(Deserialize)]
3547
#[serde(untagged)]
36-
enum OneOrMany<T> {
37-
One(T),
38-
Many(Vec<T>),
48+
enum OneOrMany {
49+
Many(Vec<Value>),
50+
One(Value),
3951
}
4052

41-
let result: OneOrMany<T> = Deserialize::deserialize(deserializer)?;
42-
Ok(match result {
43-
OneOrMany::Many(list) => list,
53+
let result: OneOrMany = Deserialize::deserialize(deserializer)?;
54+
let values = match result {
4455
OneOrMany::One(value) => vec![value],
45-
})
56+
OneOrMany::Many(values) => values,
57+
};
58+
59+
values
60+
.into_iter()
61+
.map(|value| match value {
62+
Value::String(value) if matches!(value.as_str(), "Public" | "as:Public") => {
63+
Ok(public())
64+
}
65+
Value::String(value) => Url::parse(&value).map_err(D::Error::custom),
66+
value => Url::deserialize(value).map_err(D::Error::custom),
67+
})
68+
.collect::<Result<Vec<_>, _>>()
69+
.map(|values| values.into_iter().unique().collect())
4670
}
4771

4872
/// Deserialize JSON single value or single element array into single value.
@@ -140,6 +164,11 @@ where
140164

141165
#[cfg(test)]
142166
mod tests {
167+
use super::deserialize_one_or_many;
168+
use activitystreams_kinds::public;
169+
use anyhow::Result;
170+
use serde::Deserialize;
171+
143172
#[test]
144173
fn deserialize_one_multiple_values() {
145174
use crate::protocol::helpers::deserialize_one;
@@ -155,4 +184,70 @@ mod tests {
155184
);
156185
assert!(note.is_err());
157186
}
187+
188+
#[test]
189+
fn deserialize_one_or_many_single_public_aliases() -> Result<()> {
190+
use url::Url;
191+
192+
#[derive(Deserialize)]
193+
struct Note {
194+
#[serde(deserialize_with = "deserialize_one_or_many")]
195+
to: Vec<Url>,
196+
}
197+
198+
for alias in ["Public", "as:Public"] {
199+
let note = serde_json::from_str::<Note>(&format!(r#"{{"to": "{alias}"}}"#))?;
200+
assert_eq!(note.to, vec![public()]);
201+
}
202+
203+
Ok(())
204+
}
205+
206+
#[test]
207+
fn deserialize_one_or_many_array() -> Result<()> {
208+
use url::Url;
209+
210+
#[derive(Deserialize)]
211+
struct Note {
212+
#[serde(deserialize_with = "deserialize_one_or_many")]
213+
to: Vec<Url>,
214+
}
215+
216+
let note = serde_json::from_str::<Note>(
217+
r#"{
218+
"to": [
219+
"https://example.com/c/main",
220+
"Public",
221+
"as:Public",
222+
"https://www.w3.org/ns/activitystreams#Public"
223+
]
224+
}"#,
225+
)?;
226+
227+
assert_eq!(
228+
note.to,
229+
vec![Url::parse("https://example.com/c/main")?, public(),]
230+
);
231+
232+
Ok(())
233+
}
234+
235+
#[test]
236+
fn deserialize_one_or_many_leaves_other_strings_unchanged() -> Result<()> {
237+
use url::Url;
238+
239+
#[derive(Deserialize)]
240+
struct Note {
241+
#[serde(deserialize_with = "deserialize_one_or_many")]
242+
to: Vec<Url>,
243+
content: String,
244+
}
245+
246+
let note = serde_json::from_str::<Note>(r#"{"to": "Public", "content": "Public"}"#)?;
247+
248+
assert_eq!(note.to, vec![public()]);
249+
assert_eq!(note.content, "Public");
250+
251+
Ok(())
252+
}
158253
}

0 commit comments

Comments
 (0)