use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(untagged)]
pub enum ApObjects {
SingleHref(String),
MultipleHrefs(Vec<String>),
SingleObj(Box<ApObject>),
MultipleObjs(Vec<ApObject>),
}
impl ApObjects {
fn ap_objects_vec(&self) -> Vec<ApObject> {
match self {
ApObjects::SingleHref(href) => {
vec![ApObject::from(href.to_string())]
}
ApObjects::MultipleHrefs(hrefs) => hrefs
.iter()
.map(|href| ApObject::from(href.to_string()))
.collect(),
ApObjects::SingleObj(obj) => {
vec![*obj.clone()]
}
ApObjects::MultipleObjs(objs) => objs.clone(),
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(untagged)]
pub enum ApObjectAddresses {
SingleUrl(String),
MultipleUrls(Vec<String>),
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum ApObjectType {
Article,
Note,
Document,
Image,
Video,
Audio,
Page,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum ApObjectMediaType {
#[serde(rename = "text/html")]
TextHtml,
#[serde(rename = "image/png")]
ImagePng,
#[serde(rename = "image/jpeg")]
ImageJpeg,
#[serde(rename = "image/gif")]
ImageGif,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct ApObject {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(rename = "mediaType")]
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<ApObjectMediaType>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
pub ap_type: Option<ApObjectType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<String>,
#[serde(rename = "attributedTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attributed_to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<ApObjectAddresses>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<ApObjectAddresses>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bto: Option<ApObjectAddresses>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<ApObjectAddresses>,
}
impl From<String> for ApObject {
fn from(url: String) -> Self {
ApObject {
name: None,
id: None,
url: Option::from(url),
media_type: None,
ap_type: None,
content: None,
summary: None,
actor: None,
attributed_to: None,
to: None,
cc: None,
bto: None,
bcc: None,
}
}
}
#[cfg(test)]
mod ap_object_tests {
use super::*;
#[test]
fn parse_single_url() {
let u1 = "http://example.com/icon.png";
let j = format!("\"{}\"", u1);
let ap_obj: ApObjects = serde_json::from_str(&j).unwrap();
let v = ap_obj.ap_objects_vec();
assert_eq!(1, v.len());
assert_eq!(u1.to_string(), v[0].url.clone().unwrap());
}
#[test]
fn parse_array_of_urls() {
let u1 = "http://example.com/icon.png";
let u2 = "http://example.org/foo.png";
let j = format!("[\"{}\",\"{}\"]", u1, u2);
let ap_obj: ApObjects = serde_json::from_str(&j).unwrap();
let v = ap_obj.ap_objects_vec();
assert_eq!(2, v.len());
assert_eq!(u1.to_string(), v[0].url.clone().unwrap());
assert_eq!(u2.to_string(), v[1].url.clone().unwrap());
}
#[test]
fn parse_obj() {
let u1 = "http://example.com/icon.png";
let j = format!("{{\"url\":\"{}\"}}", u1);
let ap_obj: ApObjects = serde_json::from_str(&j).unwrap();
let v = ap_obj.ap_objects_vec();
assert_eq!(1, v.len());
assert_eq!(u1.to_string(), v[0].url.clone().unwrap());
}
#[test]
fn parse_multiple_objs() {
let u1 = "http://example.com/icon.png";
let u2 = "http://example.org/foo.png";
let j = format!("[{{\"url\":\"{}\"}},{{\"url\":\"{}\"}}]", u1, u2);
let ap_obj: ApObjects = serde_json::from_str(&j).unwrap();
let v = ap_obj.ap_objects_vec();
assert_eq!(2, v.len());
assert_eq!(u1.to_string(), v[0].url.clone().unwrap());
assert_eq!(u2.to_string(), v[1].url.clone().unwrap());
}
#[test]
fn size_of_icons() {
println!("Size of Icons is {}", std::mem::size_of::<ApObjects>())
}
#[test]
fn roundtrip_mastodon_example_test() {
let ex = r#"
{
"@context" : [
"https://www.w3.org/ns/activitystreams",
{
"atomUri" : "ostatus:atomUri",
"blurhash" : "toot:blurhash",
"conversation" : "ostatus:conversation",
"focalPoint" : {
"@container" : "@list",
"@id" : "toot:focalPoint"
},
"inReplyToAtomUri" : "ostatus:inReplyToAtomUri",
"ostatus" : "http://ostatus.org#",
"sensitive" : "as:sensitive",
"toot" : "http://joinmastodon.org/ns#",
"votersCount" : "toot:votersCount"
}
],
"atomUri" : "https://fosstodon.org/users/xpil/statuses/107831067777241377",
"attachment" : [
{
"blurhash" : "UHH.KH00t7M{xuWBaya|WBj[fQayofWVj[ay",
"height" : 249,
"mediaType" : "image/jpeg",
"name" : null,
"type" : "Document",
"url" : "https://cdn.fosstodon.org/media_attachments/files/107/831/067/339/133/535/original/7117410b8c95755d.jpg",
"width" : 599
}
],
"attributedTo" : "https://fosstodon.org/users/xpil",
"cc" : [
"https://fosstodon.org/users/xpil/followers"
],
"mediaType": "text/html",
"content" : "<p>I was 4 years old when this was published. Could as well be yesterday.</p>",
"contentMap" : {
"en" : "<p>I was 4 years old when this was published. Could as well be yesterday.</p>"
},
"conversation" : "tag:fosstodon.org,2022-02-20:objectId=43364305:objectType=Conversation",
"id" : "https://fosstodon.org/users/xpil/statuses/107831067777241377",
"inReplyTo" : null,
"inReplyToAtomUri" : null,
"published" : "2022-02-20T15:38:37Z",
"replies" : {
"first" : {
"items" : [],
"next" : "https://fosstodon.org/users/xpil/statuses/107831067777241377/replies?only_other_accounts=true&page=true",
"partOf" : "https://fosstodon.org/users/xpil/statuses/107831067777241377/replies",
"type" : "CollectionPage"
},
"id" : "https://fosstodon.org/users/xpil/statuses/107831067777241377/replies",
"type" : "Collection"
},
"sensitive" : false,
"summary" : null,
"tag" : [],
"to" : [
"https://www.w3.org/ns/activitystreams#Public"
],
"type" : "Note",
"url" : "https://fosstodon.org/@xpil/107831067777241377"
}
"#;
let ap_obj: ApObject = serde_json::from_str(ex).unwrap();
let j = serde_json::to_string(&ap_obj).unwrap();
let _ao2: ApObject = serde_json::from_str(&j).unwrap();
}
#[test]
fn roundtrip_pleforma_example_test() {
let ex = r#"
{
"@context" : [
"https://www.w3.org/ns/activitystreams",
"https://kiwifarms.cc/schemas/litepub-0.1.jsonld",
{
"@language" : "und"
}
],
"actors" : "https://kiwifarms.cc/users/Harmful-if-Swallowed",
"attachment" : [
{
"mediaType" : "image/png",
"name" : "",
"type" : "Document",
"url" : "https://kiwifarms.cc/media/9decd4556cac13af33b069d53520bdbfbda083d73fe8da26cf92a052b23db3c9.png"
}
],
"attributedTo" : "https://kiwifarms.cc/users/Harmful-if-Swallowed",
"cc" : [
"https://kiwifarms.cc/users/Harmful-if-Swallowed/followers"
],
"content" : "<span class=\"h-card\"><a class=\"u-url mention\" data-users=\"A34BHSwtq9XQSf9M3c\" href=\"https://kiwifarms.cc/users/vinnegan\" rel=\"ugc\">@<span>vinnegan</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-users=\"A38VtEtI3uaDG561NA\" href=\"https://kiwifarms.cc/users/Not_a_cat\" rel=\"ugc\">@<span>Not_a_cat</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-users=\"A36kcmMTTYAz8hR6O0\" href=\"https://kiwifarms.cc/users/Pagliano\" rel=\"ugc\">@<span>Pagliano</span></a></span> archiving this already because you know the guy is gonna purge everything as per usual",
"context" : "https://kiwifarms.cc/contexts/4628da99-30ad-4016-82db-686e1397ab85",
"conversation" : "https://kiwifarms.cc/contexts/4628da99-30ad-4016-82db-686e1397ab85",
"id" : "https://kiwifarms.cc/objects/3a10192b-6113-490c-9f66-c0a6615121fc",
"published" : "2022-02-20T23:37:27.762136Z",
"sensitive" : null,
"source" : "@vinnegan @Not_a_cat @Pagliano archiving this already because you know the guy is gonna purge everything as per usual",
"summary" : "",
"tag" : [
{
"href" : "https://kiwifarms.cc/users/Not_a_cat",
"name" : "@Not_a_cat",
"type" : "Mention"
},
{
"href" : "https://kiwifarms.cc/users/Pagliano",
"name" : "@Pagliano",
"type" : "Mention"
},
{
"href" : "https://kiwifarms.cc/users/vinnegan",
"name" : "@vinnegan",
"type" : "Mention"
}
],
"to" : [
"https://www.w3.org/ns/activitystreams#Public",
"https://kiwifarms.cc/users/Not_a_cat",
"https://kiwifarms.cc/users/Pagliano",
"https://kiwifarms.cc/users/vinnegan"
],
"type" : "Note"
}
"#;
let ap_obj: ApObject = serde_json::from_str(ex).unwrap();
let j = serde_json::to_string(&ap_obj).unwrap();
let _ao2: ApObject = serde_json::from_str(&j).unwrap();
}
}