diff --git a/activitystreams-derive/src/lib.rs b/activitystreams-derive/src/lib.rs index 344249f..17eeef5 100644 --- a/activitystreams-derive/src/lib.rs +++ b/activitystreams-derive/src/lib.rs @@ -70,6 +70,121 @@ use syn::{Attribute, Data, DeriveInput, Fields, Ident, Type}; use std::iter::FromIterator; +#[proc_macro_derive(PropRefs, attributes(activitystreams))] +pub fn ref_derive(input: TokenStream) -> TokenStream { + let input: DeriveInput = syn::parse(input).unwrap(); + + let name = input.ident; + + let data = match input.data { + Data::Struct(s) => s, + _ => panic!("Can only derive for structs"), + }; + + let fields = match data.fields { + Fields::Named(fields) => fields, + _ => panic!("Can only derive for named fields"), + }; + + let impls = fields + .named + .iter() + .filter_map(|field| { + let our_attr = field.attrs.iter().find(|attribute| { + attribute + .path + .segments + .last() + .map(|segment| { + segment.ident == Ident::new("activitystreams", segment.ident.span()) + }) + .unwrap_or(false) + }); + + our_attr.map(move |our_attr| { + ( + field.ident.clone().unwrap(), + field.ty.clone(), + our_attr.clone(), + ) + }) + }) + .flat_map(move |(ident, ty, attr)| { + let object = object(attr); + let name = name.clone(); + let ext_trait = Ident::new(&format!("{}Ext", object), name.span()); + + let activity_impls = quote! { + impl #object for #name {} + + impl #ext_trait for #name { + fn props(&self) -> &#ty { + self.as_ref() + } + + fn props_mut(&mut self) -> &mut #ty { + self.as_mut() + } + } + }; + + let ref_impls = quote! { + impl AsRef<#ty> for #name { + fn as_ref(&self) -> &#ty { + &self.#ident + } + } + + impl AsMut<#ty> for #name { + fn as_mut(&mut self) -> &mut #ty { + &mut self.#ident + } + } + }; + + if object == "None" { + ref_impls + } else { + quote! { + #ref_impls + #activity_impls + } + } + }); + + let tokens = proc_macro2::TokenStream::from_iter(impls); + + let full = quote! { + #tokens + }; + + full.into() +} + +fn object(attr: Attribute) -> Ident { + let group = attr + .tokens + .clone() + .into_iter() + .filter_map(|token_tree| match token_tree { + TokenTree::Group(group) => Some(group), + _ => None, + }) + .next() + .unwrap(); + + group + .stream() + .clone() + .into_iter() + .filter_map(|token_tree| match token_tree { + TokenTree::Ident(ident) => Some(ident), + _ => None, + }) + .next() + .unwrap() +} + #[proc_macro_derive(UnitString, attributes(activitystreams))] pub fn unit_string(input: TokenStream) -> TokenStream { let input: DeriveInput = syn::parse(input).unwrap(); diff --git a/activitystreams-types/Cargo.toml b/activitystreams-types/Cargo.toml index 31d30e5..b157a3b 100644 --- a/activitystreams-types/Cargo.toml +++ b/activitystreams-types/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["activitystreams", "activitypub"] edition = "2018" [dependencies] +thiserror = "1.0.11" activitystreams-derive = { version = "0.2", path = "../activitystreams-derive" } activitystreams-traits = { version = "0.2", path = "../activitystreams-traits" } chrono = { version = "0.4", features = ["serde"] } diff --git a/activitystreams-types/src/lib.rs b/activitystreams-types/src/lib.rs index 5244a1e..c49e9b3 100644 --- a/activitystreams-types/src/lib.rs +++ b/activitystreams-types/src/lib.rs @@ -61,5 +61,6 @@ pub mod collection; mod custom_props; pub mod link; pub mod object; +pub mod primitives; pub use self::custom_props::{CustomLink, CustomObject}; diff --git a/activitystreams-types/src/object/mod.rs b/activitystreams-types/src/object/mod.rs index c653667..355c004 100644 --- a/activitystreams-types/src/object/mod.rs +++ b/activitystreams-types/src/object/mod.rs @@ -19,7 +19,7 @@ //! Namespace for Object types -use activitystreams_derive::Properties; +use activitystreams_derive::{PropRefs, Properties}; use activitystreams_traits::Object; use serde_derive::{Deserialize, Serialize}; @@ -37,7 +37,7 @@ pub trait ObjectExt: Object { } /// Represents any kind of multi-paragraph written work. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Article { #[serde(rename = "type")] @@ -47,22 +47,12 @@ pub struct Article { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Article {} -impl ObjectExt for Article { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents an audio document of any kind. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Audio { #[serde(rename = "type")] @@ -72,22 +62,12 @@ pub struct Audio { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Audio {} -impl ObjectExt for Audio { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents a document of any kind. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Document { #[serde(rename = "type")] @@ -97,22 +77,12 @@ pub struct Document { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Document {} -impl ObjectExt for Document { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents any kind of event. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Event { #[serde(rename = "type")] @@ -122,22 +92,12 @@ pub struct Event { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Event {} -impl ObjectExt for Event { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// An image document of any kind -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Image { #[serde(rename = "type")] @@ -147,22 +107,12 @@ pub struct Image { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Image {} -impl ObjectExt for Image { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents a short written work typically less than a single paragraph in length. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Note { #[serde(rename = "type")] @@ -172,22 +122,12 @@ pub struct Note { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Note {} -impl ObjectExt for Note { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents a Web Page. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Page { #[serde(rename = "type")] @@ -197,20 +137,10 @@ pub struct Page { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } -impl Object for Page {} -impl ObjectExt for Page { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents a logical or physical location. /// /// The Place object is used to represent both physical and logical locations. While numerous @@ -229,7 +159,7 @@ impl ObjectExt for Page { /// While publishers are not required to use these specific properties and MAY make use of other /// mechanisms for describing locations, consuming implementations that support the Place object /// MUST support the use of these properties. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Place { #[serde(rename = "type")] @@ -239,29 +169,20 @@ pub struct Place { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, /// Adds all valid place properties to this struct #[serde(flatten)] + #[activitystreams(None)] pub place: PlaceProperties, } -impl Object for Place {} -impl ObjectExt for Place { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// A Profile is a content object that describes another `Object`, typically used to describe /// `Actor` Type objects. /// /// The `describes` property is used to reference the object being described by the profile. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, PropRefs, Properties)] #[serde(rename_all = "camelCase")] pub struct Profile { #[serde(rename = "type")] @@ -271,24 +192,15 @@ pub struct Profile { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, /// Adds all valid profile properties to this struct #[serde(flatten)] + #[activitystreams(None)] pub profile: ProfileProperties, } -impl Object for Profile {} -impl ObjectExt for Profile { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Describes a relationship between two individuals. /// /// The subject and object properties are used to identify the connected individuals. @@ -303,7 +215,7 @@ impl ObjectExt for Profile { /// individuals that are directly connected within a person's social graph. Suppose we have a user, /// Sally, with direct relationships to users Joe and Jane. Sally follows Joe's updates while Sally /// and Jane have a mutual relationship. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] +#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties, PropRefs)] #[serde(rename_all = "camelCase")] pub struct Relationship { #[serde(rename = "type")] @@ -313,29 +225,20 @@ pub struct Relationship { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, /// Adds all valid relationship properties to this struct #[serde(flatten)] + #[activitystreams(None)] pub relationship: RelationshipProperties, } -impl Object for Relationship {} -impl ObjectExt for Relationship { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// A Tombstone represents a content object that has been deleted. /// /// It can be used in Collections to signify that there used to be an object at this position, but /// it has been deleted. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Tombstone { #[serde(rename = "type")] @@ -345,26 +248,17 @@ pub struct Tombstone { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, /// Adds all valid tombstone properties to this struct #[serde(flatten)] + #[activitystreams(None)] pub tombstone_props: TombstoneProperties, } -impl Object for Tombstone {} -impl ObjectExt for Tombstone { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} - /// Represents a video document of any kind. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Video { #[serde(rename = "type")] @@ -374,16 +268,6 @@ pub struct Video { /// Adds all valid object properties to this struct #[serde(flatten)] + #[activitystreams(Object)] pub object_props: ObjectProperties, } - -impl Object for Video {} -impl ObjectExt for Video { - fn props(&self) -> &ObjectProperties { - &self.object_props - } - - fn props_mut(&mut self) -> &mut ObjectProperties { - &mut self.object_props - } -} diff --git a/activitystreams-types/src/primitives/mime_media_type.rs b/activitystreams-types/src/primitives/mime_media_type.rs new file mode 100644 index 0000000..cdd74df --- /dev/null +++ b/activitystreams-types/src/primitives/mime_media_type.rs @@ -0,0 +1,81 @@ +#[derive(Clone, Debug)] +pub struct MimeMediaType(mime::Mime); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing MIME")] +pub struct MimeMediaTypeError; + +impl From for MimeMediaType { + fn from(m: mime::Mime) -> Self { + MimeMediaType(m) + } +} + +impl From for mime::Mime { + fn from(m: MimeMediaType) -> Self { + m.0 + } +} + +impl AsRef for MimeMediaType { + fn as_ref(&self) -> &mime::Mime { + &self.0 + } +} + +impl AsMut for MimeMediaType { + fn as_mut(&mut self) -> &mut mime::Mime { + &mut self.0 + } +} + +impl std::convert::TryFrom for MimeMediaType { + type Error = MimeMediaTypeError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for MimeMediaType { + type Error = MimeMediaTypeError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for MimeMediaType { + type Error = MimeMediaTypeError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for MimeMediaType { + type Err = MimeMediaTypeError; + + fn from_str(s: &str) -> Result { + Ok(MimeMediaType(s.parse().map_err(|_| MimeMediaTypeError)?)) + } +} + +impl serde::ser::Serialize for MimeMediaType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl<'de> serde::de::Deserialize<'de> for MimeMediaType { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} diff --git a/activitystreams-types/src/primitives/mod.rs b/activitystreams-types/src/primitives/mod.rs new file mode 100644 index 0000000..91b7a9a --- /dev/null +++ b/activitystreams-types/src/primitives/mod.rs @@ -0,0 +1,21 @@ +mod mime_media_type; +mod rdf_lang_string; +mod xsd_any_uri; +mod xsd_datetime; +mod xsd_duration; +mod xsd_float; +mod xsd_non_negative_float; +mod xsd_non_negative_integer; +mod xsd_string; + +pub use self::{ + mime_media_type::{MimeMediaType, MimeMediaTypeError}, + rdf_lang_string::RDFLangString, + xsd_any_uri::{XsdAnyURI, XsdAnyURIError}, + xsd_datetime::{XsdDateTime, XsdDateTimeError}, + xsd_duration::{XsdDuration, XsdDurationError}, + xsd_float::{XsdFloat, XsdFloatError}, + xsd_non_negative_float::{XsdNonNegativeFloat, XsdNonNegativeFloatError}, + xsd_non_negative_integer::{XsdNonNegativeInteger, XsdNonNegativeIntegerError}, + xsd_string::XsdString, +}; diff --git a/activitystreams-types/src/primitives/rdf_lang_string.rs b/activitystreams-types/src/primitives/rdf_lang_string.rs new file mode 100644 index 0000000..95f02b3 --- /dev/null +++ b/activitystreams-types/src/primitives/rdf_lang_string.rs @@ -0,0 +1,14 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +pub struct RDFLangString { + #[serde(rename = "@value")] + pub value: String, + + #[serde(rename = "@language")] + pub language: String, +} + +impl std::fmt::Display for RDFLangString { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}:{}", self.language, self.value) + } +} diff --git a/activitystreams-types/src/primitives/xsd_any_uri.rs b/activitystreams-types/src/primitives/xsd_any_uri.rs new file mode 100644 index 0000000..66e1c5e --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_any_uri.rs @@ -0,0 +1,45 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +#[serde(transparent)] +pub struct XsdAnyURI(String); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Could not parse XsdAnyURI")] +pub struct XsdAnyURIError; + +impl std::convert::TryFrom for XsdAnyURI { + type Error = XsdAnyURIError; + + fn try_from(s: String) -> Result { + Ok(XsdAnyURI(s)) + } +} + +impl std::convert::TryFrom<&str> for XsdAnyURI { + type Error = XsdAnyURIError; + + fn try_from(s: &str) -> Result { + Ok(XsdAnyURI(s.to_owned())) + } +} + +impl std::convert::TryFrom<&mut str> for XsdAnyURI { + type Error = XsdAnyURIError; + + fn try_from(s: &mut str) -> Result { + Ok(XsdAnyURI(s.to_owned())) + } +} + +impl std::str::FromStr for XsdAnyURI { + type Err = XsdAnyURIError; + + fn from_str(s: &str) -> Result { + Ok(XsdAnyURI(s.to_owned())) + } +} + +impl std::fmt::Display for XsdAnyURI { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} diff --git a/activitystreams-types/src/primitives/xsd_datetime.rs b/activitystreams-types/src/primitives/xsd_datetime.rs new file mode 100644 index 0000000..c89bf75 --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_datetime.rs @@ -0,0 +1,92 @@ +#[derive(Clone, Debug)] +pub struct XsdDateTime(chrono::DateTime); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing DateTime")] +pub struct XsdDateTimeError; + +impl From> for XsdDateTime { + fn from(d: chrono::DateTime) -> Self { + XsdDateTime(d) + } +} + +impl From for chrono::DateTime { + fn from(d: XsdDateTime) -> Self { + d.0 + } +} + +impl AsRef> for XsdDateTime { + fn as_ref(&self) -> &chrono::DateTime { + &self.0 + } +} + +impl AsMut> for XsdDateTime { + fn as_mut(&mut self) -> &mut chrono::DateTime { + &mut self.0 + } +} + +impl std::convert::TryFrom for XsdDateTime { + type Error = XsdDateTimeError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdDateTime { + type Error = XsdDateTimeError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdDateTime { + type Error = XsdDateTimeError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdDateTime { + type Err = XsdDateTimeError; + + fn from_str(s: &str) -> Result { + Ok(XsdDateTime( + chrono::DateTime::parse_from_rfc3339(s) + .map_err(|_| XsdDateTimeError)? + .into(), + )) + } +} + +impl std::fmt::Display for XsdDateTime { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = self.0.to_rfc3339(); + std::fmt::Display::fmt(&s, f) + } +} + +impl serde::ser::Serialize for XsdDateTime { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> serde::de::Deserialize<'de> for XsdDateTime { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} diff --git a/activitystreams-types/src/primitives/xsd_duration.rs b/activitystreams-types/src/primitives/xsd_duration.rs new file mode 100644 index 0000000..0d4cba3 --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_duration.rs @@ -0,0 +1,174 @@ +#[derive(Clone, Debug)] +pub struct XsdDuration(chrono::Duration); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing Duration")] +pub struct XsdDurationError; + +impl From for XsdDuration { + fn from(d: chrono::Duration) -> Self { + XsdDuration(d) + } +} + +impl From for chrono::Duration { + fn from(d: XsdDuration) -> Self { + d.0 + } +} + +impl AsRef for XsdDuration { + fn as_ref(&self) -> &chrono::Duration { + &self.0 + } +} + +impl AsMut for XsdDuration { + fn as_mut(&mut self) -> &mut chrono::Duration { + &mut self.0 + } +} + +impl std::convert::TryFrom for XsdDuration { + type Error = XsdDurationError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdDuration { + type Error = XsdDurationError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdDuration { + type Error = XsdDurationError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdDuration { + type Err = XsdDurationError; + + fn from_str(s: &str) -> Result { + if s.find('P') != Some(0) { + return Err(XsdDurationError); + } + + let s = s.trim_start_matches('P'); + + let negative = Some(0) == s.find('-'); + let s = s.trim_start_matches('-'); + + let (large, small) = if let Some(index) = s.find('T') { + let (l, s) = s.split_at(index); + (l, s.trim_start_matches('T')) + } else { + (s, "") + }; + + let (years, large) = parse_next(large, 'Y')?; + let (months, large) = parse_next(large, 'M')?; + let (days, _) = parse_next(large, 'D')?; + + let (hours, small) = parse_next(small, 'H')?; + let (minutes, small) = parse_next(small, 'M')?; + let (seconds, _) = parse_next(small, 'S')?; + + let mut duration = chrono::Duration::days(365 * years); + duration = duration + chrono::Duration::days(31 * months); + duration = duration + chrono::Duration::days(days); + duration = duration + chrono::Duration::hours(hours); + duration = duration + chrono::Duration::minutes(minutes); + duration = duration + chrono::Duration::seconds(seconds); + + duration = if negative { duration * -1 } else { duration }; + + Ok(XsdDuration(duration)) + } +} + +fn parse_next(s: &str, c: char) -> Result<(i64, &str), XsdDurationError> { + let res = if let Some(index) = s.find(c) { + let (beginning, end) = s.split_at(index); + let i = beginning.parse().map_err(|_| XsdDurationError)?; + (i, end.trim_start_matches(c)) + } else { + (0, s) + }; + + Ok(res) +} + +impl std::fmt::Display for XsdDuration { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let (s, mut duration) = if chrono::Duration::seconds(0) > self.0 { + (format!("P-"), self.0 * -1) + } else { + (format!("P"), self.0) + }; + + let s = if duration.num_days() > 0 { + format!("{}{}D", s, duration.num_days()) + } else { + s + }; + + duration = duration - chrono::Duration::days(duration.num_days()); + + let s = if duration.num_seconds() > 0 { + format!("{}T", s) + } else { + s + }; + + let s = if duration.num_hours() > 0 { + format!("{}{}H", s, duration.num_hours()) + } else { + s + }; + + duration = duration - chrono::Duration::hours(duration.num_hours()); + + let s = if duration.num_minutes() > 0 { + format!("{}{}M", s, duration.num_minutes()) + } else { + s + }; + + duration = duration - chrono::Duration::minutes(duration.num_minutes()); + + let s = if duration.num_seconds() > 0 { + format!("{}{}S", s, duration.num_seconds()) + } else { + s + }; + + std::fmt::Display::fmt(&s, f) + } +} + +impl serde::ser::Serialize for XsdDuration { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> serde::de::Deserialize<'de> for XsdDuration { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} diff --git a/activitystreams-types/src/primitives/xsd_float.rs b/activitystreams-types/src/primitives/xsd_float.rs new file mode 100644 index 0000000..cd23990 --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_float.rs @@ -0,0 +1,69 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +#[serde(transparent)] +pub struct XsdFloat(f64); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing Float")] +pub struct XsdFloatError; + +impl AsRef for XsdFloat { + fn as_ref(&self) -> &f64 { + &self.0 + } +} + +impl AsMut for XsdFloat { + fn as_mut(&mut self) -> &mut f64 { + &mut self.0 + } +} + +impl From for XsdFloat { + fn from(f: f64) -> Self { + XsdFloat(f) + } +} + +impl From for f64 { + fn from(f: XsdFloat) -> Self { + f.0 + } +} + +impl std::convert::TryFrom for XsdFloat { + type Error = XsdFloatError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdFloat { + type Error = XsdFloatError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdFloat { + type Error = XsdFloatError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdFloat { + type Err = XsdFloatError; + + fn from_str(s: &str) -> Result { + Ok(XsdFloat(s.parse().map_err(|_| XsdFloatError)?)) + } +} + +impl std::fmt::Display for XsdFloat { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} diff --git a/activitystreams-types/src/primitives/xsd_non_negative_float.rs b/activitystreams-types/src/primitives/xsd_non_negative_float.rs new file mode 100644 index 0000000..a729138 --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_non_negative_float.rs @@ -0,0 +1,73 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +#[serde(transparent)] +pub struct XsdNonNegativeFloat(f64); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing NonNegativeFloat")] +pub struct XsdNonNegativeFloatError; + +impl AsRef for XsdNonNegativeFloat { + fn as_ref(&self) -> &f64 { + &self.0 + } +} + +impl From for f64 { + fn from(f: XsdNonNegativeFloat) -> Self { + f.0 + } +} + +impl std::convert::TryFrom for XsdNonNegativeFloat { + type Error = XsdNonNegativeFloatError; + + fn try_from(f: f64) -> Result { + if f < 0.0 { + return Err(XsdNonNegativeFloatError); + } + + Ok(XsdNonNegativeFloat(f)) + } +} + +impl std::convert::TryFrom for XsdNonNegativeFloat { + type Error = XsdNonNegativeFloatError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdNonNegativeFloat { + type Error = XsdNonNegativeFloatError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdNonNegativeFloat { + type Error = XsdNonNegativeFloatError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdNonNegativeFloat { + type Err = XsdNonNegativeFloatError; + + fn from_str(s: &str) -> Result { + let f = s.parse().map_err(|_| XsdNonNegativeFloatError)?; + if f < 0.0 { + return Err(XsdNonNegativeFloatError); + } + Ok(XsdNonNegativeFloat(f)) + } +} + +impl std::fmt::Display for XsdNonNegativeFloat { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} diff --git a/activitystreams-types/src/primitives/xsd_non_negative_integer.rs b/activitystreams-types/src/primitives/xsd_non_negative_integer.rs new file mode 100644 index 0000000..d9a674f --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_non_negative_integer.rs @@ -0,0 +1,66 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +#[serde(transparent)] +pub struct XsdNonNegativeInteger(u64); + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing NonNegativeInteger")] +pub struct XsdNonNegativeIntegerError; + +impl AsRef for XsdNonNegativeInteger { + fn as_ref(&self) -> &u64 { + &self.0 + } +} + +impl From for u64 { + fn from(i: XsdNonNegativeInteger) -> Self { + i.0 + } +} + +impl std::convert::TryFrom for XsdNonNegativeInteger { + type Error = XsdNonNegativeIntegerError; + + fn try_from(f: u64) -> Result { + Ok(XsdNonNegativeInteger(f)) + } +} + +impl std::convert::TryFrom for XsdNonNegativeInteger { + type Error = XsdNonNegativeIntegerError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdNonNegativeInteger { + type Error = XsdNonNegativeIntegerError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdNonNegativeInteger { + type Error = XsdNonNegativeIntegerError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdNonNegativeInteger { + type Err = XsdNonNegativeIntegerError; + + fn from_str(s: &str) -> Result { + let f = s.parse().map_err(|_| XsdNonNegativeIntegerError)?; + Ok(XsdNonNegativeInteger(f)) + } +} + +impl std::fmt::Display for XsdNonNegativeInteger { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} diff --git a/activitystreams-types/src/primitives/xsd_string.rs b/activitystreams-types/src/primitives/xsd_string.rs new file mode 100644 index 0000000..1096b08 --- /dev/null +++ b/activitystreams-types/src/primitives/xsd_string.rs @@ -0,0 +1,33 @@ +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +#[serde(transparent)] +pub struct XsdString(String); + +impl From for XsdString { + fn from(s: String) -> Self { + XsdString(s) + } +} + +impl From<&str> for XsdString { + fn from(s: &str) -> Self { + XsdString(s.to_owned()) + } +} + +impl From<&mut str> for XsdString { + fn from(s: &mut str) -> Self { + XsdString(s.to_owned()) + } +} + +impl From for String { + fn from(s: XsdString) -> Self { + s.0 + } +} + +impl std::fmt::Display for XsdString { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +}