use crate::web_model::content_type::{ContentActivityStreams, ContentHtml}; use crate::web_model::rel::{RelOStatusSubscribe, RelSelf, RelWebFingerProfilePage}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Cow; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct WebFinger { pub subject: WebFingerSubject, pub aliases: Vec, pub links: Vec, } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum WebFingerSubject { Acct(Acct), Url(String), } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged)] #[allow(clippy::enum_variant_names)] pub enum WebFingerRel { RelWebFingerProfilePage { rel: RelWebFingerProfilePage, #[serde(rename = "type")] content_type: ContentHtml, href: String, }, RelSelf { rel: RelSelf, #[serde(rename = "type")] content_type: ContentActivityStreams, href: String, }, RelOStatusSubscribe { rel: RelOStatusSubscribe, template: String, }, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct Acct(String); impl Acct { pub fn new(uri_without_acct: Cow<'_, str>) -> Self { Acct(uri_without_acct.to_string()) } } impl From<&str> for Acct { fn from(value: &str) -> Self { Acct(value.strip_prefix("acct:").unwrap_or(value).to_string()) } } impl AsRef for Acct { fn as_ref(&self) -> &str { &self.0 } } impl Serialize for Acct { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&format!("acct:{}", self.0)) } } impl<'de> Deserialize<'de> for Acct { fn deserialize>(deserializer: D) -> Result { let acct = String::deserialize(deserializer)?; if let Some(rem) = acct.strip_prefix("acct:") { Ok(Acct(rem.to_owned())) } else { Err(Error::custom( "Missing acct protocol for account!".to_owned(), )) } } } #[cfg(test)] mod test { use crate::web_model::content_type::{ContentActivityStreams, ContentHtml}; use crate::web_model::rel::{RelOStatusSubscribe, RelSelf, RelWebFingerProfilePage}; use crate::web_model::webfinger::WebFingerSubject::Url; use crate::web_model::webfinger::{Acct, WebFinger, WebFingerRel, WebFingerSubject}; use serde_json::json; #[test] fn should_remove_acct_prefix() { let json = json!("acct:natty@tech.lgbt"); let acct: Acct = serde_json::from_value(json).unwrap(); assert_eq!(acct, Acct("natty@tech.lgbt".to_owned())) } #[test] fn should_add_acct_prefix() { let acct = Acct("natty@tech.lgbt".to_owned()); let json = serde_json::to_value(acct).unwrap(); assert_eq!(json, json!("acct:natty@tech.lgbt")); } #[test] fn should_parse_webfinger() { let json = json!({ "subject": "acct:natty@tech.lgbt", "aliases": [ "https://tech.lgbt/@natty", "https://tech.lgbt/users/natty" ], "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://tech.lgbt/@natty" }, { "rel": "self", "type": "application/activity+json", "href": "https://tech.lgbt/users/natty" }, { "rel": "http://ostatus.org/schema/1.0/subscribe", "template": "https://tech.lgbt/authorize_interaction?uri={uri}" } ] }); let webfinger: WebFinger = serde_json::from_value(json).unwrap(); let real = WebFinger { subject: WebFingerSubject::Acct(Acct::new("natty@tech.lgbt".into())), aliases: vec![ Url("https://tech.lgbt/@natty".to_owned()), Url("https://tech.lgbt/users/natty".to_owned()), ], links: vec![ WebFingerRel::RelWebFingerProfilePage { rel: RelWebFingerProfilePage, content_type: ContentHtml, href: "https://tech.lgbt/@natty".to_owned(), }, WebFingerRel::RelSelf { rel: RelSelf, content_type: ContentActivityStreams, href: "https://tech.lgbt/users/natty".to_owned(), }, WebFingerRel::RelOStatusSubscribe { rel: RelOStatusSubscribe, template: "https://tech.lgbt/authorize_interaction?uri={uri}".to_owned(), }, ], }; assert_eq!(webfinger, real) } }