diff --git a/Cargo.toml b/Cargo.toml index d34f7c3..36beb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -activitystreams = { version = "0.6.2", default-features = false, features = ["kinds","primitives"] } +chrono = "0.4" +mime = "0.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -typed-builder = "0.6.0" +thiserror = "1.0" +url = "2.1" [dev-dependencies] anyhow = "1.0" diff --git a/examples/basic.rs b/examples/basic.rs index 84ad634..2572576 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -4,6 +4,7 @@ use activitystreams_new::{ prelude::*, uri, }; +use chrono::Duration; fn main() -> Result<(), anyhow::Error> { let mut video = ApObject::new(Video::new()); @@ -14,7 +15,7 @@ fn main() -> Result<(), anyhow::Error> { .set_media_type("video/webm".parse()?) .set_url(uri!("https://example.com/@example/lions/video.webm")) .set_summary("A cool video".to_owned()) - .set_duration("PT4M20S".parse()?) + .set_duration(Duration::minutes(4) + Duration::seconds(20)) .set_shares(uri!("https://example.com/@example/lions/video.webm#shares")); println!("Video, {:#?}", video); diff --git a/src/activity.rs b/src/activity.rs index e93a46f..414fb73 100644 --- a/src/activity.rs +++ b/src/activity.rs @@ -26,11 +26,11 @@ use crate::{ base::{AnyBase, AsBase, Base, Extends}, markers, object::{ApObject, AsObject, Object}, - primitives::{OneOrMany, XsdAnyUri}, + primitives::OneOrMany, unparsed::{Unparsed, UnparsedMut, UnparsedMutExt}, }; use std::convert::TryFrom; -use typed_builder::TypedBuilder; +use url::Url; pub mod kind { //! Kinds of activities defined by the spec @@ -38,7 +38,36 @@ pub mod kind { //! These types exist only to be statically-typed versions of the associated string. e.g. //! `CreateType` -> `"Create"` - pub use activitystreams::activity::kind::*; + use crate::kind; + + kind!(AcceptType, Accept); + kind!(AddType, Add); + kind!(AnnounceType, Announce); + kind!(ArriveType, Arrive); + kind!(BlockType, Block); + kind!(CreateType, Create); + kind!(DeleteType, Delete); + kind!(DislikeType, Dislike); + kind!(FlagType, Flag); + kind!(FollowType, Follow); + kind!(IgnoreType, Ignore); + kind!(InviteType, Invite); + kind!(JoinType, Join); + kind!(LeaveType, Leave); + kind!(LikeType, Like); + kind!(ListenType, Listen); + kind!(MoveType, Move); + kind!(OfferType, Offer); + kind!(QuestionType, Question); + kind!(ReadType, Read); + kind!(RejectType, Reject); + kind!(RemoveType, Remove); + kind!(TentativeAcceptType, TentativeAccept); + kind!(TentativeRejectType, TentativeReject); + kind!(TravelType, Travel); + kind!(UndoType, Undo); + kind!(UpdateType, Update); + kind!(ViewType, View); } use self::kind::*; @@ -445,7 +474,7 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef { /// # Ok(()) /// # } /// ``` - fn actor_is(&self, id: &XsdAnyUri) -> bool { + fn actor_is(&self, id: &Url) -> bool { self.actor().is_single_id(id) } @@ -551,7 +580,7 @@ pub trait ActorAndObjectRefExt: ActorAndObjectRef { /// # Ok(()) /// # } /// ``` - fn object_is(&self, id: &XsdAnyUri) -> bool { + fn object_is(&self, id: &Url) -> bool { self.object().is_single_id(id) } @@ -1562,7 +1591,7 @@ pub type Remove = ActorAndObjectOptOriginAndTarget; /// Activity objects are specializations of the base Object type that provide information about /// actions that have either already occurred, are in the process of occurring, or may occur in the /// future. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Activity { /// Describes the result of the activity. @@ -1573,24 +1602,22 @@ pub struct Activity { /// - Range: Object | Link /// - Funcitonal: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub result: Option>, + result: Option>, /// Identifies one or more objects used (or to be used) in the completion of an Activity. /// /// - Range: Object | Link /// - Funcitonal: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub instrument: Option>, + instrument: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Object, + inner: Object, } /// Activity with actor and object properties -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ActorAndObject { /// Describes one or more entities that either performed or are expected to perform the @@ -1601,8 +1628,7 @@ pub struct ActorAndObject { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// When used within an Activity, describes the direct object of the activity. /// @@ -1611,19 +1637,18 @@ pub struct ActorAndObject { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub object: OneOrMany, + object: OneOrMany, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// An IntransitiveActivity that indicates that the actor has arrived at the location. /// /// The origin can be used to identify the context from which the actor originated. The target /// typically has no defined meaning. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Arrive { /// Describes one or more entities that either performed or are expected to perform the @@ -1634,8 +1659,7 @@ pub struct Arrive { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// Describes an indirect object of the activity from which the activity is directed. /// @@ -1645,17 +1669,16 @@ pub struct Arrive { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub origin: OneOrMany, + origin: OneOrMany, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// A specialization of Offer in which the actor is extending an invitation for the object to the /// target. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Invite { /// Describes one or more entities that either performed or are expected to perform the @@ -1666,8 +1689,7 @@ pub struct Invite { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// When used within an Activity, describes the direct object of the activity. /// @@ -1676,8 +1698,7 @@ pub struct Invite { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub object: OneOrMany, + object: OneOrMany, /// Describes the indirect object, or target, of the activity. /// @@ -1688,18 +1709,17 @@ pub struct Invite { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub target: OneOrMany, + target: OneOrMany, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// Indicates that the actor has deleted the object. /// /// If specified, the origin indicates the context from which the object was deleted. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Delete { /// Describes one or more entities that either performed or are expected to perform the @@ -1710,8 +1730,7 @@ pub struct Delete { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// When used within an Activity, describes the direct object of the activity. /// @@ -1720,8 +1739,7 @@ pub struct Delete { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub object: OneOrMany, + object: OneOrMany, /// Describes an indirect object of the activity from which the activity is directed. /// @@ -1732,16 +1750,15 @@ pub struct Delete { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub origin: Option>, + origin: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// Activity with actor, object, and optional origin and target properties -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ActorAndObjectOptOriginAndTarget { /// Describes one or more entities that either performed or are expected to perform the @@ -1752,8 +1769,7 @@ pub struct ActorAndObjectOptOriginAndTarget { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// When used within an Activity, describes the direct object of the activity. /// @@ -1762,8 +1778,7 @@ pub struct ActorAndObjectOptOriginAndTarget { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub object: OneOrMany, + object: OneOrMany, /// Describes an indirect object of the activity from which the activity is directed. /// @@ -1774,8 +1789,7 @@ pub struct ActorAndObjectOptOriginAndTarget { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub origin: Option>, + origin: Option>, /// Describes the indirect object, or target, of the activity. /// @@ -1787,16 +1801,15 @@ pub struct ActorAndObjectOptOriginAndTarget { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub target: Option>, + target: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// Activity with actor, object, and optional target properties -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ActorAndObjectOptTarget { /// Describes one or more entities that either performed or are expected to perform the @@ -1807,8 +1820,7 @@ pub struct ActorAndObjectOptTarget { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// When used within an Activity, describes the direct object of the activity. /// @@ -1817,8 +1829,7 @@ pub struct ActorAndObjectOptTarget { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub object: OneOrMany, + object: OneOrMany, /// Describes the indirect object, or target, of the activity. /// @@ -1830,19 +1841,18 @@ pub struct ActorAndObjectOptTarget { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub target: Option>, + target: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// Indicates that the actor is traveling to target from origin. /// /// Travel is an IntransitiveObject whose actor specifies the direct object. If the target or /// origin are not specified, either can be determined by context. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Travel { /// Describes one or more entities that either performed or are expected to perform the @@ -1853,8 +1863,7 @@ pub struct Travel { /// /// - Range: Object | Link /// - Functional: false - #[builder(setter(into))] - pub actor: OneOrMany, + actor: OneOrMany, /// Describes an indirect object of the activity from which the activity is directed. /// @@ -1865,8 +1874,7 @@ pub struct Travel { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub origin: Option>, + origin: Option>, /// Describes the indirect object, or target, of the activity. /// @@ -1878,12 +1886,11 @@ pub struct Travel { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub target: Option>, + target: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } /// Represents a question being asked. @@ -1894,7 +1901,7 @@ pub struct Travel { /// /// Either of the anyOf and oneOf properties MAY be used to express possible answers, but a /// Question object MUST NOT have both properties. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Question { /// Identifies an exclusive option for a Question. @@ -1905,8 +1912,7 @@ pub struct Question { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub one_of: Option>, + one_of: Option>, /// Identifies an inclusive option for a Question. /// @@ -1916,12 +1922,11 @@ pub struct Question { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub any_of: Option>, + any_of: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Activity, + inner: Activity, } impl Activity { @@ -1936,7 +1941,11 @@ impl Activity { where Kind: Default, { - Activity::builder().inner(Object::new()).build() + Activity { + result: None, + instrument: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { @@ -1979,11 +1988,11 @@ impl ActorAndObject { U: Into>, Kind: Default, { - Self::builder() - .actor(actor) - .object(object) - .inner(Activity::new()) - .build() + ActorAndObject { + actor: actor.into(), + object: object.into(), + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2025,11 +2034,11 @@ impl Arrive { T: Into>, U: Into>, { - Self::builder() - .actor(actor) - .origin(origin) - .inner(Activity::new()) - .build() + Arrive { + actor: actor.into(), + origin: origin.into(), + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2072,12 +2081,12 @@ impl Invite { U: Into>, V: Into>, { - Self::builder() - .actor(actor) - .object(object) - .target(target) - .inner(Activity::new()) - .build() + Invite { + actor: actor.into(), + object: object.into(), + target: target.into(), + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2125,11 +2134,12 @@ impl Delete { T: Into>, U: Into>, { - Self::builder() - .actor(actor) - .object(object) - .inner(Activity::new()) - .build() + Delete { + actor: actor.into(), + object: object.into(), + origin: None, + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2181,11 +2191,13 @@ impl ActorAndObjectOptOriginAndTarget { U: Into>, Kind: Default, { - Self::builder() - .actor(actor) - .object(object) - .inner(Activity::new()) - .build() + ActorAndObjectOptOriginAndTarget { + actor: actor.into(), + object: object.into(), + origin: None, + target: None, + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2241,11 +2253,12 @@ impl ActorAndObjectOptTarget { U: Into>, Kind: Default, { - Self::builder() - .actor(actor) - .object(object) - .inner(Activity::new()) - .build() + ActorAndObjectOptTarget { + actor: actor.into(), + object: object.into(), + target: None, + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2292,7 +2305,12 @@ impl Travel { where T: Into>, { - Self::builder().actor(actor).inner(Activity::new()).build() + Travel { + actor: actor.into(), + origin: None, + target: None, + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -2336,7 +2354,11 @@ impl Question { /// let activity = Question::new(); /// ``` pub fn new() -> Self { - Question::builder().inner(Activity::new()).build() + Question { + one_of: None, + any_of: None, + inner: Activity::new(), + } } fn extending(object: Object) -> Result { @@ -3189,11 +3211,11 @@ where Inner: AsActivity, { fn activity_ref(&self) -> &Activity { - self.inner.activity_ref() + self.inner().activity_ref() } fn activity_mut(&mut self) -> &mut Activity { - self.inner.activity_mut() + self.inner_mut().activity_mut() } } @@ -3202,19 +3224,19 @@ where Inner: ActorAndObjectRef, { fn actor_field_ref(&self) -> &OneOrMany { - self.inner.actor_field_ref() + self.inner().actor_field_ref() } fn object_field_ref(&self) -> &OneOrMany { - self.inner.object_field_ref() + self.inner().object_field_ref() } fn actor_field_mut(&mut self) -> &mut OneOrMany { - self.inner.actor_field_mut() + self.inner_mut().actor_field_mut() } fn object_field_mut(&mut self) -> &mut OneOrMany { - self.inner.object_field_mut() + self.inner_mut().object_field_mut() } } @@ -3223,11 +3245,11 @@ where Inner: TargetRef, { fn target_field_ref(&self) -> &OneOrMany { - self.inner.target_field_ref() + self.inner().target_field_ref() } fn target_field_mut(&mut self) -> &mut OneOrMany { - self.inner.target_field_mut() + self.inner_mut().target_field_mut() } } @@ -3236,11 +3258,11 @@ where Inner: OriginRef, { fn origin_field_ref(&self) -> &OneOrMany { - self.inner.origin_field_ref() + self.inner().origin_field_ref() } fn origin_field_mut(&mut self) -> &mut OneOrMany { - self.inner.origin_field_mut() + self.inner_mut().origin_field_mut() } } @@ -3249,11 +3271,11 @@ where Inner: OptTargetRef, { fn target_field_ref(&self) -> &Option> { - self.inner.target_field_ref() + self.inner().target_field_ref() } fn target_field_mut(&mut self) -> &mut Option> { - self.inner.target_field_mut() + self.inner_mut().target_field_mut() } } @@ -3262,11 +3284,11 @@ where Inner: OptOriginRef, { fn origin_field_ref(&self) -> &Option> { - self.inner.origin_field_ref() + self.inner().origin_field_ref() } fn origin_field_mut(&mut self) -> &mut Option> { - self.inner.origin_field_mut() + self.inner_mut().origin_field_mut() } } @@ -3275,11 +3297,11 @@ where Inner: AsQuestion, { fn question_ref(&self) -> &Question { - self.inner.question_ref() + self.inner().question_ref() } fn question_mut(&mut self) -> &mut Question { - self.inner.question_mut() + self.inner_mut().question_mut() } } diff --git a/src/actor.rs b/src/actor.rs index 9318420..d35b704 100644 --- a/src/actor.rs +++ b/src/actor.rs @@ -22,13 +22,14 @@ //! # } //! ``` use crate::{ - base::{AsBase, Base, Extends}, + base::{AsBase, Base, BaseExt, Extends}, + error::DomainError, markers, object::{ApObject, AsApObject, AsObject, Object}, primitives::{OneOrMany, XsdAnyUri}, unparsed::{Unparsed, UnparsedMut, UnparsedMutExt}, }; -use typed_builder::TypedBuilder; +use url::Url; pub mod kind { //! Kinds of actors defined by the spec @@ -36,7 +37,13 @@ pub mod kind { //! These types exist only to be statically-typed versions of the associated string. e.g. //! `PersonType` -> `"Person"` - pub use activitystreams::actor::kind::*; + use crate::kind; + + kind!(ApplicationType, Application); + kind!(GroupType, Group); + kind!(OrganizationType, Organization); + kind!(PersonType, Person); + kind!(ServiceType, Service); } use self::kind::*; @@ -58,6 +65,36 @@ pub trait AsApActor: markers::Actor { /// /// Documentation for the fields related to these methods can be found on the `ApActor` struct pub trait ApActorExt: AsApActor { + /// Fetch the inbox for the current actor, erroring if the inbox's domain does not match the + /// ID's domain + /// + /// ``` + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// # person.set_id(context()); + /// use activitystreams_new::prelude::*; + /// + /// let inbox = person.inbox()?; + /// println!("{:?}", inbox); + /// # Ok(()) + /// # } + /// ``` + fn inbox<'a, Kind>(&'a self) -> Result<&'a Url, DomainError> + where + Self: BaseExt + 'a, + Inner: 'a, + Kind: 'a, + { + let unchecked = self.inbox_unchecked(); + + if unchecked.domain() != self.id_unchecked().and_then(|id| id.domain()) { + return Err(DomainError); + } + + Ok(unchecked) + } + /// Fetch the inbox for the current actor /// /// ```rust @@ -65,13 +102,30 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// let inbox_ref = person.inbox(); + /// let inbox_ref = person.inbox_unchecked(); /// ``` - fn inbox<'a>(&'a self) -> &'a XsdAnyUri + fn inbox_unchecked<'a>(&'a self) -> &'a Url where Inner: 'a, { - &self.ap_actor_ref().inbox + self.ap_actor_ref().inbox.as_ref() + } + + /// Fetch a mutable referece to the current actor's inbox + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// let inbox_mut = person.inbox_mut(); + /// inbox_mut.set_path("/inbox"); + /// ``` + fn inbox_mut<'a>(&'a mut self) -> &'a mut Url + where + Inner: 'a, + { + self.ap_actor_mut().inbox.as_mut() } /// Set the inbox for the current actor @@ -86,11 +140,41 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_inbox(&mut self, inbox: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().inbox = inbox; + fn set_inbox(&mut self, inbox: Url) -> &mut Self { + self.ap_actor_mut().inbox = inbox.into(); self } + /// Fetch the outbox for the current user, erroring if the oubox's domain does not match the + /// ID's domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// let outbox_ref = person.outbox()?; + /// # Ok(()) + /// # } + /// ``` + fn outbox<'a, Kind>(&'a self) -> Result, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(unchecked) = self.outbox_unchecked() { + if unchecked.domain() != self.id_unchecked().and_then(|id| id.domain()) { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the outbox for the current actor /// /// ```rust @@ -98,13 +182,32 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// let outbox_ref = person.outbox(); + /// let outbox_ref = person.outbox_unchecked(); /// ``` - fn outbox<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn outbox_unchecked<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_actor_ref().outbox.as_ref() + self.ap_actor_ref().outbox.as_ref().map(|u| u.as_ref()) + } + + /// Mutably fetch the outbox for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(outbox) = person.outbox_mut() { + /// outbox.set_path("/outbox"); + /// println!("{:?}", outbox); + /// } + /// ``` + fn outbox_mut<'a>(&'a mut self) -> Option<&'a mut Url> + where + Inner: 'a, + { + self.ap_actor_mut().outbox.as_mut().map(|u| u.as_mut()) } /// Set the outbox for the current actor @@ -119,8 +222,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_outbox(&mut self, outbox: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().outbox = Some(outbox); + fn set_outbox(&mut self, outbox: Url) -> &mut Self { + self.ap_actor_mut().outbox = Some(outbox.into()); self } @@ -135,8 +238,8 @@ pub trait ApActorExt: AsApActor { /// println!("{:?}", outbox); /// } /// ``` - fn take_outbox(&mut self) -> Option { - self.ap_actor_mut().outbox.take() + fn take_outbox(&mut self) -> Option { + self.ap_actor_mut().outbox.take().map(|u| u.into_inner()) } /// Delete the outbox from the current object @@ -148,9 +251,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_outbox(uri!("https://example.com/outbox")); /// use activitystreams_new::prelude::*; /// - /// assert!(person.outbox().is_some()); + /// assert!(person.outbox_unchecked().is_some()); /// person.delete_outbox(); - /// assert!(person.outbox().is_none()); + /// assert!(person.outbox_unchecked().is_none()); /// # Ok(()) /// # } /// ``` @@ -159,6 +262,38 @@ pub trait ApActorExt: AsApActor { self } + /// Fetch the following link for the current user, erroring if the following link's domain does + /// not match the ID's domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(following) = person.following()? { + /// println!("{:?}", following); + /// } + /// # Ok(()) + /// # } + /// ``` + fn following<'a, Kind>(&'a self) -> Result, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(unchecked) = self.following_unchecked() { + if unchecked.domain() != self.id_unchecked().and_then(|id| id.domain()) { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the following link for the current actor /// /// ```rust @@ -166,15 +301,34 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(following) = person.following() { + /// if let Some(following) = person.following_unchecked() { /// println!("{:?}", following); /// } /// ``` - fn following<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn following_unchecked<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_actor_ref().following.as_ref() + self.ap_actor_ref().following.as_ref().map(|f| f.as_ref()) + } + + /// Mutably fetch the following link for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(following) = person.following_mut() { + /// following.set_path("/following"); + /// println!("{:?}", following); + /// } + /// ``` + fn following_mut<'a>(&'a mut self) -> Option<&'a mut Url> + where + Inner: 'a, + { + self.ap_actor_mut().following.as_mut().map(|f| f.as_mut()) } /// Set the following link for the current actor @@ -189,8 +343,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_following(&mut self, following: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().following = Some(following); + fn set_following(&mut self, following: Url) -> &mut Self { + self.ap_actor_mut().following = Some(following.into()); self } @@ -205,8 +359,8 @@ pub trait ApActorExt: AsApActor { /// println!("{:?}", following); /// } /// ``` - fn take_following(&mut self) -> Option { - self.ap_actor_mut().following.take() + fn take_following(&mut self) -> Option { + self.ap_actor_mut().following.take().map(|u| u.into_inner()) } /// Delete the following link from the current object @@ -218,9 +372,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_following(uri!("https://example.com/following")); /// use activitystreams_new::prelude::*; /// - /// assert!(person.following().is_some()); + /// assert!(person.following_unchecked().is_some()); /// person.delete_following(); - /// assert!(person.following().is_none()); + /// assert!(person.following_unchecked().is_none()); /// # Ok(()) /// # } /// ``` @@ -229,6 +383,38 @@ pub trait ApActorExt: AsApActor { self } + /// Fetch the followers link for the current actor, erroring if the followers link's domain + /// does not match the ID's domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(followers) = person.followers()? { + /// println!("{:?}", followers); + /// } + /// # Ok(()) + /// # } + /// ``` + fn followers<'a, Kind>(&'a self) -> Result, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(unchecked) = self.followers_unchecked() { + if unchecked.domain() != self.id_unchecked().and_then(|id| id.domain()) { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the followers link for the current actor /// /// ```rust @@ -236,15 +422,34 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(followers) = person.take_followers() { + /// if let Some(followers) = person.followers_unchecked() { /// println!("{:?}", followers); /// } /// ``` - fn followers<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn followers_unchecked<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_actor_ref().followers.as_ref() + self.ap_actor_ref().followers.as_ref().map(|u| u.as_ref()) + } + + /// Mutably fetch the followers link for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(followers) = person.followers_mut() { + /// followers.set_path("/followers"); + /// println!("{:?}", followers); + /// } + /// ``` + fn followers_mut<'a>(&'a mut self) -> Option<&'a mut Url> + where + Inner: 'a, + { + self.ap_actor_mut().followers.as_mut().map(|u| u.as_mut()) } /// Set the followers link for the current actor @@ -259,8 +464,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_followers(&mut self, followers: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().followers = Some(followers); + fn set_followers(&mut self, followers: Url) -> &mut Self { + self.ap_actor_mut().followers = Some(followers.into()); self } @@ -275,8 +480,8 @@ pub trait ApActorExt: AsApActor { /// println!("{:?}", followers); /// } /// ``` - fn take_followers(&mut self) -> Option { - self.ap_actor_mut().followers.take() + fn take_followers(&mut self) -> Option { + self.ap_actor_mut().followers.take().map(|u| u.into_inner()) } /// Delete the followers link from the current object @@ -288,9 +493,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_followers(uri!("https://example.com/followers")); /// use activitystreams_new::prelude::*; /// - /// assert!(person.followers().is_some()); + /// assert!(person.followers_unchecked().is_some()); /// person.delete_followers(); - /// assert!(person.followers().is_none()); + /// assert!(person.followers_unchecked().is_none()); /// # Ok(()) /// # } /// ``` @@ -299,6 +504,38 @@ pub trait ApActorExt: AsApActor { self } + /// Fetch the liked link for the current actor, erroring if the liked link's domain does not + /// match the ID's domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(liked) = person.liked()? { + /// println!("{:?}", liked); + /// } + /// # Ok(()) + /// # } + /// ``` + fn liked<'a, Kind>(&'a self) -> Result, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(unchecked) = self.liked_unchecked() { + if unchecked.domain() != self.id_unchecked().and_then(|id| id.domain()) { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the liked link for the current actor /// /// ```rust @@ -306,15 +543,34 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(liked) = person.take_liked() { + /// if let Some(liked) = person.liked_unchecked() { /// println!("{:?}", liked); /// } /// ``` - fn liked<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn liked_unchecked<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_actor_ref().liked.as_ref() + self.ap_actor_ref().liked.as_ref().map(|u| u.as_ref()) + } + + /// Mutably fetch the liked link for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(liked) = person.liked_mut() { + /// liked.set_path("/liked"); + /// println!("{:?}", liked); + /// } + /// ``` + fn liked_mut<'a>(&'a mut self) -> Option<&'a mut Url> + where + Inner: 'a, + { + self.ap_actor_mut().liked.as_mut().map(|u| u.as_mut()) } /// Set the liked link for the current actor @@ -329,8 +585,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_liked(&mut self, liked: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().liked = Some(liked); + fn set_liked(&mut self, liked: Url) -> &mut Self { + self.ap_actor_mut().liked = Some(liked.into()); self } @@ -345,8 +601,8 @@ pub trait ApActorExt: AsApActor { /// println!("{:?}", liked); /// } /// ``` - fn take_liked(&mut self) -> Option { - self.ap_actor_mut().liked.take() + fn take_liked(&mut self) -> Option { + self.ap_actor_mut().liked.take().map(|u| u.into_inner()) } /// Delete the liked link from the current object @@ -358,9 +614,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_liked(uri!("https://example.com/liked")); /// use activitystreams_new::prelude::*; /// - /// assert!(person.liked().is_some()); + /// assert!(person.liked_unchecked().is_some()); /// person.delete_liked(); - /// assert!(person.liked().is_none()); + /// assert!(person.liked_unchecked().is_none()); /// # Ok(()) /// # } /// ``` @@ -369,6 +625,50 @@ pub trait ApActorExt: AsApActor { self } + /// Fetch the streams links for the current actor, erroring if the streams links's domains do + /// not match the ID's domains + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// # person.set_id(context()).add_streams(context()).add_streams(context()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(streams) = person.streams()? { + /// println!("{:?}", streams); + /// } + /// # Ok(()) + /// # } + /// ``` + fn streams<'a, Kind>(&'a self) -> Result>, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(unchecked) = self.streams_unchecked() { + let domain_opt = self.id_unchecked().and_then(|id| id.domain()); + + let one = unchecked + .as_one() + .map(|url| url.domain() == domain_opt) + .unwrap_or(false); + let many = unchecked + .as_many() + .map(|urls| urls.iter().all(|url| url.domain() == domain_opt)) + .unwrap_or(false); + + if !one && !many { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the streams links for the current actor /// /// ```rust @@ -376,15 +676,45 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(streams) = person.take_streams() { + /// if let Some(streams) = person.streams_unchecked() { /// println!("{:?}", streams); /// } /// ``` - fn streams<'a>(&'a self) -> Option<&'a OneOrMany> + fn streams_unchecked<'a>(&'a self) -> Option> where Inner: 'a, { - self.ap_actor_ref().streams.as_ref() + self.ap_actor_ref() + .streams + .as_ref() + .map(|o| o.as_ref().map(|u| u.as_ref())) + } + + /// Mutably fetch the streams links for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(mut streams) = person.streams_mut() { + /// streams.one_mut().map(|url| url.set_path("/streams")); + /// streams.many_mut().map(|urls| { + /// for url in urls.iter_mut() { + /// url.set_path("/streams"); + /// } + /// }); + /// println!("{:?}", streams); + /// } + /// ``` + fn streams_mut<'a>(&'a mut self) -> Option> + where + Inner: 'a, + { + self.ap_actor_mut() + .streams + .as_mut() + .map(|o| o.as_mut().map(|u| u.as_mut())) } /// Set the streams links for the current actor @@ -399,8 +729,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_streams(&mut self, streams: XsdAnyUri) -> &mut Self { - self.ap_actor_mut().streams = Some(streams.into()); + fn set_streams(&mut self, streams: Url) -> &mut Self { + self.ap_actor_mut().streams = Some(XsdAnyUri::from(streams).into()); self } @@ -421,9 +751,9 @@ pub trait ApActorExt: AsApActor { /// ``` fn set_many_streams(&mut self, items: I) -> &mut Self where - I: IntoIterator, + I: IntoIterator, { - let v: Vec<_> = items.into_iter().collect(); + let v: Vec = items.into_iter().map(|u| u.into()).collect(); self.ap_actor_mut().streams = Some(v.into()); self } @@ -442,13 +772,13 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn add_streams(&mut self, stream: XsdAnyUri) -> &mut Self { + fn add_streams(&mut self, stream: Url) -> &mut Self { let v = match self.ap_actor_mut().streams.take() { Some(mut v) => { - v.add(stream); + v.add(XsdAnyUri::from(stream)); v } - None => vec![stream].into(), + None => vec![stream.into()].into(), }; self.ap_actor_mut().streams = Some(v); self @@ -465,8 +795,11 @@ pub trait ApActorExt: AsApActor { /// println!("{:?}", streams); /// } /// ``` - fn take_streams(&mut self) -> Option> { - self.ap_actor_mut().streams.take() + fn take_streams(&mut self) -> Option> { + self.ap_actor_mut() + .streams + .take() + .map(|o| o.map(|u| u.into_inner())) } /// Delete the streams links from the current object @@ -478,9 +811,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_streams(uri!("https://example.com/streams")); /// use activitystreams_new::prelude::*; /// - /// assert!(person.streams().is_some()); + /// assert!(person.streams_unchecked().is_some()); /// person.delete_streams(); - /// assert!(person.streams().is_none()); + /// assert!(person.streams_unchecked().is_none()); /// # Ok(()) /// # } /// ``` @@ -562,6 +895,71 @@ pub trait ApActorExt: AsApActor { self } + /// Fetch the endpoints for the current actor, erroring if the Endpoints' domains do not + /// match the ID's domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{actor::{ApActor, Endpoints, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// # person.set_id(context()).set_endpoints(Endpoints { + /// # shared_inbox: Some(context()), + /// # ..Default::default() + /// # }); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(endpoints) = person.endpoints()? { + /// println!("{:?}", endpoints); + /// } + /// # Ok(()) + /// # } + /// ``` + fn endpoints<'a, Kind>(&'a self) -> Result>, DomainError> + where + Self: BaseExt, + Inner: 'a, + Kind: 'a, + { + if let Some(endpoints) = self.endpoints_unchecked() { + let domain_opt = self.id_unchecked().and_then(|id| id.domain()); + + let mut any_failed = false; + + any_failed |= endpoints + .proxy_url + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + any_failed |= endpoints + .oauth_authorization_endpoint + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + any_failed |= endpoints + .oauth_token_endpoint + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + any_failed |= endpoints + .provide_client_key + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + any_failed |= endpoints + .sign_client_key + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + any_failed |= endpoints + .shared_inbox + .map(|u| u.domain() != domain_opt) + .unwrap_or(false); + + if any_failed { + return Err(DomainError); + } + + return Ok(Some(endpoints)); + } + + Ok(None) + } + /// Fetch the endpoints for the current actor /// /// ```rust @@ -569,15 +967,40 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(endpoints) = person.endpoints() { + /// if let Some(endpoints) = person.endpoints_unchecked() { /// println!("{:?}", endpoints); /// } /// ``` - fn endpoints<'a>(&'a self) -> Option<&'a Endpoints> + fn endpoints_unchecked<'a>(&'a self) -> Option> where Inner: 'a, { - self.ap_actor_ref().endpoints.as_ref() + self.ap_actor_ref() + .endpoints + .as_ref() + .map(|e| e.as_ref().map(|u| u.as_ref())) + } + + /// Mutably fetch the endpoints for the current actor + /// + /// ```rust + /// # use activitystreams_new::{actor::{ApActor, Person}, context}; + /// # let mut person = ApActor::new(context(), Person::new()); + /// use activitystreams_new::prelude::*; + /// + /// if let Some(mut endpoints) = person.endpoints_mut() { + /// endpoints.shared_inbox.as_mut().map(|url| url.set_path("/inbox")); + /// println!("{:?}", endpoints); + /// } + /// ``` + fn endpoints_mut<'a>(&'a mut self) -> Option> + where + Inner: 'a, + { + self.ap_actor_mut() + .endpoints + .as_mut() + .map(|e| e.as_mut().map(|u| u.as_mut())) } /// Set the endpoints for the current actor @@ -595,8 +1018,8 @@ pub trait ApActorExt: AsApActor { /// # Ok(()) /// # } /// ``` - fn set_endpoints(&mut self, endpoints: Endpoints) -> &mut Self { - self.ap_actor_mut().endpoints = Some(endpoints); + fn set_endpoints(&mut self, endpoints: Endpoints) -> &mut Self { + self.ap_actor_mut().endpoints = Some(endpoints.map(|u| u.into())); self } @@ -607,12 +1030,15 @@ pub trait ApActorExt: AsApActor { /// # let mut person = ApActor::new(context(), Person::new()); /// use activitystreams_new::prelude::*; /// - /// if let Some(endpoints) = person.endpoints() { + /// if let Some(endpoints) = person.take_endpoints() { /// println!("{:?}", endpoints); /// } /// ``` - fn take_endpoints(&mut self) -> Option { - self.ap_actor_mut().endpoints.take() + fn take_endpoints(&mut self) -> Option> { + self.ap_actor_mut() + .endpoints + .take() + .map(|e| e.map(|u| u.into_inner())) } /// Delete the endpoints from the current actor @@ -623,9 +1049,9 @@ pub trait ApActorExt: AsApActor { /// # person.set_endpoints(Default::default()); /// use activitystreams_new::prelude::*; /// - /// assert!(person.endpoints().is_some()); + /// assert!(person.endpoints_unchecked().is_some()); /// person.delete_endpoints(); - /// assert!(person.endpoints().is_none()); + /// assert!(person.endpoints_unchecked().is_none()); /// ``` fn delete_endpoints(&mut self) -> &mut Self { self.ap_actor_mut().endpoints = None; @@ -681,62 +1107,56 @@ pub type Service = Actor; /// implementation MUST also specify the core vocabulary type. For instance, some vocabularies /// (e.g. VCard) define their own types for describing people. An implementation that wishes, for /// example, to use a vcard:Individual as an Actor MUST also identify that Actor as a Person. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ApActor { /// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor. /// /// - Range: xsd:anyUri /// - Functional: true - pub inbox: XsdAnyUri, + inbox: XsdAnyUri, /// An ActivityStreams] OrderedCollection comprised of all the messages produced by the actor. /// /// - Range: xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub outbox: Option, + outbox: Option, /// A link to an [ActivityStreams] collection of the actors that this actor is following. /// /// - Range: xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub following: Option, + following: Option, /// A link to an [ActivityStreams] collection of the actors that follow this actor. /// /// - Range: xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub followers: Option, + followers: Option, /// A link to an [ActivityStreams] collection of objects this actor has liked. /// /// - Range: xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub liked: Option, + liked: Option, /// A list of supplementary Collections which may be of interest. /// /// - Range: xsd:anyUri /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub streams: Option>, + streams: Option>, /// A short username which may be used to refer to the actor, with no uniqueness guarantees. /// /// - Range: xsd:string /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub preferred_username: Option, + preferred_username: Option, /// A json object which maps additional (typically server/domain-wide) endpoints which may be /// useful either for this actor or someone referencing this actor. @@ -747,12 +1167,11 @@ pub struct ApActor { /// - Range: Endpoint /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub endpoints: Option, + endpoints: Option>, /// base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Inner, + inner: Inner, } /// A json object which maps additional (typically server/domain-wide) endpoints which may be @@ -760,9 +1179,9 @@ pub struct ApActor { /// /// This mapping may be nested inside the actor document as the value or may be a link to a /// JSON-LD document with these properties. -#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] -pub struct Endpoints { +pub struct Endpoints { /// Endpoint URI so this actor's clients may access remote ActivityStreams objects which /// require authentication to access. /// @@ -772,8 +1191,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub proxy_url: Option, + pub proxy_url: Option, /// If OAuth 2.0 bearer tokens [RFC6749](https://tools.ietf.org/html/rfc6749) /// [RFC6750](https://tools.ietf.org/html/rfc6750) are being used for authenticating client to @@ -783,8 +1201,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub oauth_authorization_endpoint: Option, + pub oauth_authorization_endpoint: Option, /// If OAuth 2.0 bearer tokens [RFC6749](https://tools.ietf.org/html/rfc6749) /// [RFC6750](https://tools.ietf.org/html/rfc6750) are being used for authenticating client to @@ -794,8 +1211,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub oauth_token_endpoint: Option, + pub oauth_token_endpoint: Option, /// If Linked Data Signatures and HTTP Signatures are being used for authentication and /// authorization, this endpoint specifies a URI at which browser-authenticated users may @@ -804,8 +1220,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub provide_client_key: Option, + pub provide_client_key: Option, /// If Linked Data Signatures and HTTP Signatures are being used for authentication and /// authorization, this endpoint specifies a URI at which a client key may be signed by the @@ -815,8 +1230,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub sign_client_key: Option, + pub sign_client_key: Option, /// An optional endpoint used for wide delivery of publicly addressed activities and /// activities sent to followers. @@ -829,8 +1243,7 @@ pub struct Endpoints { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub shared_inbox: Option, + pub shared_inbox: Option, } /// A simple type to create an Actor out of any Object @@ -874,11 +1287,21 @@ impl ApActor { /// # Ok(()) /// # } /// ``` - pub fn new(inbox: XsdAnyUri, inner: Inner) -> Self + pub fn new(inbox: Url, inner: Inner) -> Self where Inner: markers::Actor, { - Self::builder().inbox(inbox).inner(inner).build() + ApActor { + inbox: inbox.into(), + outbox: None, + following: None, + followers: None, + liked: None, + streams: None, + preferred_username: None, + endpoints: None, + inner, + } } fn extending(mut inner: Inner) -> Result @@ -937,6 +1360,94 @@ impl ApActor { } } +impl Endpoints { + /// Borrow the current Endpoints struct + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// use activitystreams_new::{actor::Endpoints, uri}; + /// use url::Url; + /// + /// let uri = uri!("https://example.com"); + /// + /// let endpoints: Endpoints = Endpoints { + /// shared_inbox: Some(uri.clone()), + /// ..Default::default() + /// }; + /// + /// assert_eq!(endpoints.as_ref().shared_inbox, Some(&uri)); + /// # Ok(()) + /// # } + /// ``` + pub fn as_ref(&self) -> Endpoints<&T> { + Endpoints { + proxy_url: self.proxy_url.as_ref(), + oauth_authorization_endpoint: self.oauth_authorization_endpoint.as_ref(), + oauth_token_endpoint: self.oauth_token_endpoint.as_ref(), + provide_client_key: self.provide_client_key.as_ref(), + sign_client_key: self.sign_client_key.as_ref(), + shared_inbox: self.shared_inbox.as_ref(), + } + } + + /// Mutably borrow the endpoints struct + pub fn as_mut(&mut self) -> Endpoints<&mut T> { + Endpoints { + proxy_url: self.proxy_url.as_mut(), + oauth_authorization_endpoint: self.oauth_authorization_endpoint.as_mut(), + oauth_token_endpoint: self.oauth_token_endpoint.as_mut(), + provide_client_key: self.provide_client_key.as_mut(), + sign_client_key: self.sign_client_key.as_mut(), + shared_inbox: self.shared_inbox.as_mut(), + } + } + + /// Map the URLs in Endpoints from T to U + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// use activitystreams_new::{actor::Endpoints, uri}; + /// use url::Url; + /// + /// let endpoints: Endpoints = Endpoints { + /// shared_inbox: Some(uri!("https://example.com")), + /// ..Default::default() + /// }; + /// + /// let endpoint_strings = endpoints.map(|u| u.to_string()); + /// + /// assert_eq!(endpoint_strings.shared_inbox, Some(String::from("https://example.com/"))); + /// # Ok(()) + /// # } + /// ``` + pub fn map(self, f: F) -> Endpoints + where + F: Fn(T) -> U + Copy, + { + Endpoints { + proxy_url: self.proxy_url.map(f), + oauth_authorization_endpoint: self.oauth_authorization_endpoint.map(f), + oauth_token_endpoint: self.oauth_token_endpoint.map(f), + provide_client_key: self.provide_client_key.map(f), + sign_client_key: self.sign_client_key.map(f), + shared_inbox: self.shared_inbox.map(f), + } + } +} + +impl Default for Endpoints { + fn default() -> Self { + Endpoints { + proxy_url: None, + oauth_authorization_endpoint: None, + oauth_token_endpoint: None, + provide_client_key: None, + sign_client_key: None, + shared_inbox: None, + } + } +} + impl markers::Base for Actor {} impl markers::Object for Actor {} impl markers::Actor for Actor {} diff --git a/src/base.rs b/src/base.rs index 4ba623d..2abe3ca 100644 --- a/src/base.rs +++ b/src/base.rs @@ -29,11 +29,13 @@ //! ``` use crate::{ either::Either, + error::DomainError, markers, primitives::{AnyString, MimeMediaType, OneOrMany, XsdAnyUri}, unparsed::{Unparsed, UnparsedMut}, }; -use typed_builder::TypedBuilder; +use mime::Mime; +use url::Url; /// Implements conversion between `Base` and other ActivityStreams objects defined in this /// crate @@ -244,6 +246,34 @@ pub trait BaseExt: AsBase { self } + /// Fetch the id for the current object, checking it against the provided domain + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::{object::Video, uri}; + /// # let mut video = Video::new(); + /// # video.set_id(uri!("https://example.com")); + /// use activitystreams_new::prelude::*; + /// + /// assert_eq!(video.id("example.com")?, Some(&uri!("https://example.com"))); + /// # Ok(()) + /// # } + /// ``` + fn id<'a>(&'a self, domain: &str) -> Result, DomainError> + where + Kind: 'a, + { + if let Some(unchecked) = self.id_unchecked() { + if unchecked.domain() != Some(domain) { + return Err(DomainError); + } + + return Ok(Some(unchecked)); + } + + Ok(None) + } + /// Fetch the id for the current object /// /// ```rust @@ -252,15 +282,35 @@ pub trait BaseExt: AsBase { /// # /// use activitystreams_new::prelude::*; /// - /// if let Some(id) = video.id() { + /// if let Some(id) = video.id_unchecked() { /// println!("{:?}", id); /// } /// ``` - fn id<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn id_unchecked<'a>(&'a self) -> Option<&'a Url> where Kind: 'a, { - self.base_ref().id.as_ref() + self.base_ref().id.as_ref().map(|i| i.as_ref()) + } + + /// Mutably borrow the ID from the current object + /// + /// ```rust + /// # use activitystreams_new::object::Video; + /// # let mut video = Video::new(); + /// # + /// use activitystreams_new::prelude::*; + /// + /// if let Some(id) = video.id_mut() { + /// id.set_path("/actor"); + /// println!("{:?}", id); + /// } + /// ``` + fn id_mut<'a>(&'a mut self) -> Option<&'a mut Url> + where + Kind: 'a, + { + self.base_mut().id.as_mut().map(|i| i.as_mut()) } /// Check if the provided id is equal to the object's id @@ -275,8 +325,8 @@ pub trait BaseExt: AsBase { /// # Ok(()) /// # } /// ``` - fn is_id(&self, id: &XsdAnyUri) -> bool { - self.id() == Some(id) + fn is_id(&self, id: &Url) -> bool { + self.id_unchecked() == Some(id) } /// Set the id for the current object @@ -294,8 +344,8 @@ pub trait BaseExt: AsBase { /// # Ok(()) /// # } /// ``` - fn set_id(&mut self, id: XsdAnyUri) -> &mut Self { - self.base_mut().id = Some(id); + fn set_id(&mut self, id: Url) -> &mut Self { + self.base_mut().id = Some(id.into()); self } @@ -311,8 +361,8 @@ pub trait BaseExt: AsBase { /// println!("{:?}", id); /// } /// ``` - fn take_id(&mut self) -> Option { - self.base_mut().id.take() + fn take_id(&mut self) -> Option { + self.base_mut().id.take().map(|u| u.into_inner()) } /// Delete the id from the current object @@ -324,9 +374,9 @@ pub trait BaseExt: AsBase { /// # /// use activitystreams_new::prelude::*; /// - /// assert!(video.id().is_some()); + /// assert!(video.id_unchecked().is_some()); /// video.delete_id(); - /// assert!(video.id().is_none()); + /// assert!(video.id_unchecked().is_none()); /// ``` fn delete_id(&mut self) -> &mut Self { self.base_mut().id = None; @@ -386,7 +436,7 @@ pub trait BaseExt: AsBase { /// # let mut video = Video::new(); /// use activitystreams_new::prelude::*; /// - /// video.set_kind(VideoType); + /// video.set_kind(VideoType::Video); /// ``` fn set_kind(&mut self, kind: Kind) -> &mut Self { self.base_mut().kind = Some(kind); @@ -414,7 +464,7 @@ pub trait BaseExt: AsBase { /// ```rust /// # use activitystreams_new::{object::{Video, kind::VideoType}}; /// # let mut video = Video::new(); - /// # video.set_kind(VideoType); + /// # video.set_kind(VideoType::Video); /// # /// use activitystreams_new::prelude::*; /// @@ -439,11 +489,11 @@ pub trait BaseExt: AsBase { /// println!("{:?}", name); /// } /// ``` - fn name<'a>(&'a self) -> Option<&'a OneOrMany> + fn name<'a>(&'a self) -> Option> where Kind: 'a, { - self.base_ref().name.as_ref() + self.base_ref().name.as_ref().map(|o| o.as_ref()) } /// Set the name for the current object @@ -560,11 +610,11 @@ pub trait BaseExt: AsBase { /// println!("{:?}", media_type); /// } /// ``` - fn media_type<'a>(&'a self) -> Option<&'a MimeMediaType> + fn media_type<'a>(&'a self) -> Option<&'a Mime> where Kind: 'a, { - self.base_ref().media_type.as_ref() + self.base_ref().media_type.as_ref().map(|m| m.as_ref()) } /// Set the media type for the current object @@ -574,15 +624,15 @@ pub trait BaseExt: AsBase { /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams_new::prelude::*; - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// /// video.set_media_type("video/webm".parse()?); /// # Ok(()) /// # } /// ``` - fn set_media_type(&mut self, media_type: MimeMediaType) -> &mut Self { - self.base_mut().media_type = Some(media_type); + fn set_media_type(&mut self, media_type: Mime) -> &mut Self { + self.base_mut().media_type = Some(media_type.into()); self } @@ -598,8 +648,8 @@ pub trait BaseExt: AsBase { /// println!("{:?}", media_type); /// } /// ``` - fn take_media_type(&mut self) -> Option { - self.base_mut().media_type.take() + fn take_media_type(&mut self) -> Option { + self.base_mut().media_type.take().map(|m| m.into_inner()) } /// Delete the media type from the current object @@ -635,11 +685,11 @@ pub trait BaseExt: AsBase { /// println!("{:?}", preview); /// } /// ``` - fn preview<'a>(&'a self) -> Option<&'a OneOrMany> + fn preview<'a>(&'a self) -> Option> where Kind: 'a, { - self.base_ref().preview.as_ref() + self.base_ref().preview.as_ref().map(|o| o.as_ref()) } /// Set the preview for the current object @@ -780,7 +830,7 @@ pub struct AnyBase(Either); /// Although the spec does not define a type more abstract that Object or Link, it does define /// fields present in both, so for the sake of "Everything derives from something," I've /// implemented a type. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Base { /// Identifies the context within which the object exists or an activity was performed. @@ -794,8 +844,7 @@ pub struct Base { #[serde(rename = "@context")] #[serde(alias = "context")] #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub context: Option>, + context: Option>, /// Provides the globally unique identifier for an Object or Link. /// @@ -810,8 +859,7 @@ pub struct Base { /// When processing Activity Streams 1.0 documents and converting those to 2.0, implementations /// ought to treat id as an alias for the JSON-LD @id key word[.] #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub id: Option, + id: Option, /// The `type` field /// @@ -839,8 +887,7 @@ pub struct Base { #[serde(alias = "objectType")] #[serde(alias = "verb")] #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub kind: Option, + kind: Option, /// A simple, human-readable, plain-text name for the object. /// @@ -849,8 +896,7 @@ pub struct Base { /// - Range: xsd:string | rdf:langString /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub name: Option>, + name: Option>, /// When used on an Object, identifies the MIME media type of the value of the content property. /// @@ -859,23 +905,20 @@ pub struct Base { /// - Range: Mime Media Type /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub media_type: Option, + media_type: Option, /// Identifies an entity that provides a preview of this object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub preview: Option>, + preview: Option>, /// Any additional data present on the object if parsed from JSON /// /// This is used to extend the Base into other kinds of objects and links #[serde(flatten)] - #[builder(default)] - pub unparsed: Unparsed, + unparsed: Unparsed, } impl Base { @@ -902,7 +945,15 @@ impl Base { where Kind: Default, { - Base::builder().kind(Kind::default()).build() + Base { + context: None, + id: None, + kind: Some(Kind::default()), + name: None, + media_type: None, + preview: None, + unparsed: Default::default(), + } } /// Extend the Base into any other ActivityStreams type provided in this crate @@ -1116,9 +1167,11 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn id(&self) -> Option<&XsdAnyUri> { - self.as_xsd_any_uri() - .or_else(|| self.as_base().and_then(|base| base.id.as_ref())) + pub fn id(&self) -> Option<&Url> { + self.as_xsd_any_uri().or_else(|| { + self.as_base() + .and_then(|base| base.id.as_ref().map(|i| i.as_ref())) + }) } /// Check if the current object's id matches the provided id @@ -1138,7 +1191,7 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn is_id(&self, id: &XsdAnyUri) -> bool { + pub fn is_id(&self, id: &Url) -> bool { self.id() == Some(id) } @@ -1153,7 +1206,7 @@ impl AnyBase { /// # }; /// # let mut video = Video::new(); /// # - /// video.set_kind(VideoType); + /// video.set_kind(VideoType::Video); /// /// let any_base = AnyBase::from_extended(video)?; /// @@ -1180,7 +1233,7 @@ impl AnyBase { /// # }; /// # let mut video = Video::new(); /// # - /// video.set_kind(VideoType); + /// video.set_kind(VideoType::Video); /// /// let any_base = AnyBase::from_extended(video)?; /// @@ -1204,7 +1257,7 @@ impl AnyBase { /// # }; /// # let mut video = Video::new(); /// # - /// video.set_kind(VideoType); + /// video.set_kind(VideoType::Video); /// /// let any_base = AnyBase::from_extended(video)?; /// @@ -1229,7 +1282,7 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn as_xsd_any_uri(&self) -> Option<&XsdAnyUri> { + pub fn as_xsd_any_uri(&self) -> Option<&Url> { self.0.as_ref().left().and_then(|l| l.as_xsd_any_uri()) } @@ -1277,7 +1330,7 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn take_xsd_any_uri(self) -> Option { + pub fn take_xsd_any_uri(self) -> Option { self.0.left().and_then(|l| l.id()) } @@ -1327,7 +1380,7 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn set_xsd_any_uri(&mut self, id: XsdAnyUri) { + pub fn set_xsd_any_uri(&mut self, id: Url) { self.0 = Either::Left(IdOrBase::from_xsd_any_uri(id)); } @@ -1382,7 +1435,7 @@ impl AnyBase { /// # Ok(()) /// # } /// ``` - pub fn from_xsd_any_uri(id: XsdAnyUri) -> Self { + pub fn from_xsd_any_uri(id: Url) -> Self { AnyBase(Either::Left(IdOrBase::from_xsd_any_uri(id))) } @@ -1415,24 +1468,24 @@ impl AnyBase { } impl IdOrBase { - fn as_xsd_any_uri(&self) -> Option<&XsdAnyUri> { - self.0.as_ref().left() + fn as_xsd_any_uri(&self) -> Option<&Url> { + self.0.as_ref().left().map(|u| u.as_ref()) } fn as_base(&self) -> Option<&Base> { self.0.as_ref().right().map(|b| b.as_ref()) } - fn id(self) -> Option { - self.0.left() + fn id(self) -> Option { + self.0.left().map(|u| u.into_inner()) } fn base(self) -> Option> { self.0.right().map(|b| *b) } - fn from_xsd_any_uri(id: XsdAnyUri) -> Self { - IdOrBase(Either::Left(id)) + fn from_xsd_any_uri(id: Url) -> Self { + IdOrBase(Either::Left(id.into())) } fn from_base(base: Base) -> Self { @@ -1445,17 +1498,17 @@ impl OneOrMany { /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{base::Base, primitives::OneOrMany, uri}; + /// # use activitystreams_new::{base::{Base, BaseExt}, primitives::OneOrMany, uri}; /// # let mut base = Base::::new(); /// # let id = uri!("https://example.com"); - /// # base.id = Some(id.clone()); + /// # base.set_id(id.clone()); /// # let base = OneOrMany::from_base(base.into_generic()?.into()); /// # /// assert!(base.as_single_id() == Some(&id)); /// # Ok(()) /// # } /// ``` - pub fn as_single_id(&self) -> Option<&XsdAnyUri> { + pub fn as_single_id(&self) -> Option<&Url> { self.as_one().and_then(|one| one.id()) } @@ -1463,17 +1516,17 @@ impl OneOrMany { /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{base::Base, primitives::OneOrMany, uri}; + /// # use activitystreams_new::{base::{Base, BaseExt}, primitives::OneOrMany, uri}; /// # let mut base = Base::::new(); /// # let id = uri!("https://example.com"); - /// # base.id = Some(id.clone()); + /// # base.set_id(id.clone()); /// # let base = OneOrMany::from_base(base.into_generic()?.into()); /// # /// assert!(base.is_single_id(&id)); /// # Ok(()) /// # } /// ``` - pub fn is_single_id(&self, id: &XsdAnyUri) -> bool { + pub fn is_single_id(&self, id: &Url) -> bool { self.as_single_id() == Some(id) } @@ -1481,9 +1534,9 @@ impl OneOrMany { /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{base::Base, primitives::OneOrMany}; - /// # let mut base = Base::new(); - /// # base.kind = Some(String::from("Person")); + /// # use activitystreams_new::{base::{Base, BaseExt}, primitives::OneOrMany}; + /// # let mut base = Base::::new(); + /// # base.set_kind(String::from("Person")); /// # let base = OneOrMany::from_base(base.into_generic()?.into()); /// # /// assert!(base.as_single_kind_str() == Some("Person")); @@ -1499,9 +1552,9 @@ impl OneOrMany { /// This returns None if the kind is not present, or not a String /// ``` /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{base::Base, primitives::OneOrMany}; - /// # let mut base = Base::new(); - /// # base.kind = Some(String::from("Person")); + /// # use activitystreams_new::{base::{Base, BaseExt}, primitives::OneOrMany}; + /// # let mut base = Base::::new(); + /// # base.set_kind(String::from("Person")); /// # let base = OneOrMany::from_base(base.into_generic()?.into()); /// # /// assert!(base.as_single_kind_str() == Some("Person")); @@ -1518,9 +1571,9 @@ impl OneOrMany { /// /// ``` /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{base::Base, primitives::OneOrMany}; + /// # use activitystreams_new::{base::{Base, BaseExt}, primitives::OneOrMany}; /// # let mut base = Base::new(); - /// # base.kind = Some(String::from("Person")); + /// # base.set_kind(String::from("Person")); /// # let base = OneOrMany::from_base(base.into_generic()?.into()); /// # /// assert!(base.is_single_kind("Person")); @@ -1544,7 +1597,7 @@ impl OneOrMany { /// # Ok(()) /// # } /// ``` - pub fn as_single_xsd_any_uri(&self) -> Option<&XsdAnyUri> { + pub fn as_single_xsd_any_uri(&self) -> Option<&Url> { self.as_one().and_then(|inner| inner.as_xsd_any_uri()) } @@ -1588,7 +1641,7 @@ impl OneOrMany { /// # Ok(()) /// # } /// ``` - pub fn single_xsd_any_uri(self) -> Option { + pub fn single_xsd_any_uri(self) -> Option { self.one().and_then(|inner| inner.take_xsd_any_uri()) } @@ -1629,7 +1682,7 @@ impl OneOrMany { /// # Ok(()) /// # } /// ``` - pub fn from_xsd_any_uri(id: XsdAnyUri) -> Self { + pub fn from_xsd_any_uri(id: Url) -> Self { OneOrMany(Either::Left(AnyBase::from_xsd_any_uri(id))) } @@ -1670,7 +1723,7 @@ impl OneOrMany { /// /// assert!(one.as_single_xsd_any_uri().is_some()); /// ``` - pub fn set_single_xsd_any_uri(&mut self, id: XsdAnyUri) -> &mut Self { + pub fn set_single_xsd_any_uri(&mut self, id: Url) -> &mut Self { self.0 = Either::Left(AnyBase::from_xsd_any_uri(id)); self } @@ -1719,7 +1772,7 @@ impl OneOrMany { /// many.add_xsd_any_uri(security()) /// .add_xsd_any_uri(context()); /// ``` - pub fn add_xsd_any_uri(&mut self, id: XsdAnyUri) -> &mut Self { + pub fn add_xsd_any_uri(&mut self, id: Url) -> &mut Self { self.add(AnyBase::from_xsd_any_uri(id)) } @@ -1786,8 +1839,8 @@ impl From> for AnyBase { } } -impl From for AnyBase { - fn from(id: XsdAnyUri) -> Self { +impl From for AnyBase { + fn from(id: Url) -> Self { Self::from_xsd_any_uri(id) } } @@ -1804,8 +1857,8 @@ impl From> for OneOrMany { } } -impl From for OneOrMany { - fn from(xsd_any_uri: XsdAnyUri) -> Self { +impl From for OneOrMany { + fn from(xsd_any_uri: Url) -> Self { Self::from_xsd_any_uri(xsd_any_uri) } } diff --git a/src/collection.rs b/src/collection.rs index c636803..0d6e611 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -14,7 +14,7 @@ //! ]); //! //! collection -//! .set_total_items(1) +//! .set_total_items(1u64) //! .set_current(uri!("https://example.com/notes/1234")) //! .set_first(uri!("https://example.com/notes/1234")) //! .set_last(uri!("https://example.com/notes/1234")) @@ -27,11 +27,10 @@ use crate::{ base::{AnyBase, AsBase, Base, Extends}, markers, object::{ApObject, AsObject, Object}, - primitives::{OneOrMany, XsdNonNegativeInteger}, + primitives::OneOrMany, unparsed::{Unparsed, UnparsedMut, UnparsedMutExt}, }; use std::convert::TryFrom; -use typed_builder::TypedBuilder; pub mod kind { //! Kinds of collections defined by the spec @@ -39,7 +38,12 @@ pub mod kind { //! These types exist only to be statically-typed versions of the associated string. e.g. //! `CollectionType` -> `"Collection"` - pub use activitystreams::collection::kind::*; + use crate::kind; + + kind!(CollectionType, Collection); + kind!(OrderedCollectionType, OrderedCollection); + kind!(CollectionPageType, CollectionPage); + kind!(OrderedCollectionPageType, OrderedCollectionPage); } use self::kind::*; @@ -178,11 +182,11 @@ pub trait CollectionExt: AsCollection { /// println!("{:?}", total_items); /// } /// ``` - fn total_items<'a>(&'a self) -> Option<&'a XsdNonNegativeInteger> + fn total_items<'a>(&'a self) -> Option where Kind: 'a, { - self.collection_ref().total_items.as_ref() + self.collection_ref().total_items } /// Set the total_items for the current object @@ -194,11 +198,11 @@ pub trait CollectionExt: AsCollection { /// # let mut collection = UnorderedCollection::new(vec![context().into()]); /// use activitystreams_new::prelude::*; /// - /// collection.set_total_items(5); + /// collection.set_total_items(5u64); /// ``` fn set_total_items(&mut self, total_items: T) -> &mut Self where - T: Into, + T: Into, { self.collection_mut().total_items = Some(total_items.into()); self @@ -215,7 +219,7 @@ pub trait CollectionExt: AsCollection { /// println!("{:?}", total_items); /// } /// ``` - fn take_total_items(&mut self) -> Option { + fn take_total_items(&mut self) -> Option { self.collection_mut().total_items.take() } @@ -224,7 +228,7 @@ pub trait CollectionExt: AsCollection { /// ```rust /// # use activitystreams_new::{context, collection::UnorderedCollection}; /// # let mut collection = UnorderedCollection::new(vec![context().into()]); - /// # collection.set_total_items(5); + /// # collection.set_total_items(5u64); /// use activitystreams_new::prelude::*; /// /// assert!(collection.total_items().is_some()); @@ -713,8 +717,8 @@ pub trait OrderedCollectionPageExt: AsOrderedCollectionPage { /// println!("{:?}", start_index); /// } /// ``` - fn start_index(&self) -> Option<&XsdNonNegativeInteger> { - self.ordered_collection_page_ref().start_index.as_ref() + fn start_index(&self) -> Option { + self.ordered_collection_page_ref().start_index } /// Set the start_index for the current object @@ -726,11 +730,11 @@ pub trait OrderedCollectionPageExt: AsOrderedCollectionPage { /// # let mut collection = OrderedCollectionPage::new(vec![context().into()]); /// use activitystreams_new::prelude::*; /// - /// collection.set_start_index(5); + /// collection.set_start_index(5u64); /// ``` fn set_start_index(&mut self, start_index: T) -> &mut Self where - T: Into, + T: Into, { self.ordered_collection_page_mut().start_index = Some(start_index.into()); self @@ -747,7 +751,7 @@ pub trait OrderedCollectionPageExt: AsOrderedCollectionPage { /// println!("{:?}", start_index); /// } /// ``` - fn take_start_index(&mut self) -> Option { + fn take_start_index(&mut self) -> Option { self.ordered_collection_page_mut().start_index.take() } @@ -756,7 +760,7 @@ pub trait OrderedCollectionPageExt: AsOrderedCollectionPage { /// ```rust /// # use activitystreams_new::{context, collection::OrderedCollectionPage}; /// # let mut collection = OrderedCollectionPage::new(vec![context().into()]); - /// # collection.set_start_index(5); + /// # collection.set_start_index(5u64); /// use activitystreams_new::prelude::*; /// /// assert!(collection.start_index().is_some()); @@ -795,15 +799,14 @@ pub type UnorderedCollectionPage = CollectionPage; /// used to identify a Collection whose items are always ordered. In the JSON serialization, the /// unordered items of a Collection are represented using the items property while ordered items /// are represented using the orderedItems property. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Collection { /// Identifies the items contained in a collection. The items might be ordered or unordered. /// /// - Range: Object | Link | Ordered List of [ Object | Link ] /// - Functional: false - #[builder(setter(into))] - pub items: OneOrMany, + items: OneOrMany, /// A non-negative integer specifying the total number of objects contained by the logical view /// of the collection. @@ -814,8 +817,7 @@ pub struct Collection { /// - Range: xsd:nonNegativeInteger /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub total_items: Option, + total_items: Option, /// In a paged Collection, indicates the page that contains the most recently updated member /// items. @@ -823,28 +825,25 @@ pub struct Collection { /// - Range: CollectionPage | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub current: Option, + current: Option, /// In a paged Collection, indicates the furthest preceeding page of items in the collection. /// /// - Range: CollectionPage | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub first: Option, + first: Option, /// In a paged Collection, indicates the furthest proceeding page of the collection. /// /// - Range: CollectionPage | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub last: Option, + last: Option, /// Base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Object, + inner: Object, } /// Used to represent distinct subsets of items from a Collection. @@ -853,7 +852,7 @@ pub struct Collection { /// implementation to serialize every item contained by a Collection using the items (or /// ordered_items) property alone. In such cases, the items within a Collection can be divided into /// distinct subsets or "pages". A page is identified using the CollectionPage type. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct CollectionPage { /// Identifies the Collection to which a CollectionPage objects items belong. @@ -861,32 +860,29 @@ pub struct CollectionPage { /// - Range: Collection | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub part_of: Option, + part_of: Option, /// In a paged Collection, indicates the next page of items. /// /// - Range: CollectionPage | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub next: Option, + next: Option, /// In a paged Collection, identifies the previous page of items. /// /// - Range: CollectionPage | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub prev: Option, + prev: Option, /// Base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Collection, + inner: Collection, } /// Used to represent ordered subsets of items from an OrderedCollection. -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct OrderedCollectionPage { /// A non-negative integer value identifying the relative position within the logical view of a strictly ordered collection. @@ -894,12 +890,11 @@ pub struct OrderedCollectionPage { /// - Range: xsd:nonNegativeInteger /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub start_index: Option, + start_index: Option, /// Base fields and unparsed json ends up here #[serde(flatten)] - pub inner: CollectionPage, + inner: CollectionPage, } impl Collection { @@ -915,7 +910,14 @@ impl Collection { T: Into>, Kind: Default, { - Self::builder().items(items).inner(Object::new()).build() + Collection { + items: items.into(), + total_items: None, + current: None, + first: None, + last: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { @@ -969,9 +971,12 @@ impl CollectionPage { T: Into>, Kind: Default, { - let collection = Collection::new(items); - - CollectionPage::builder().inner(collection).build() + CollectionPage { + part_of: None, + next: None, + prev: None, + inner: Collection::new(items), + } } fn extending(object: Object) -> Result { @@ -1018,11 +1023,10 @@ impl OrderedCollectionPage { where T: Into>, { - let collection_page = CollectionPage::new(items); - - OrderedCollectionPage::builder() - .inner(collection_page) - .build() + OrderedCollectionPage { + start_index: None, + inner: CollectionPage::new(items), + } } fn extending(object: Object) -> Result { @@ -1295,11 +1299,11 @@ where Inner: AsCollection, { fn collection_ref(&self) -> &Collection { - self.inner.collection_ref() + self.inner().collection_ref() } fn collection_mut(&mut self) -> &mut Collection { - self.inner.collection_mut() + self.inner_mut().collection_mut() } } @@ -1308,11 +1312,11 @@ where Inner: AsCollectionPage, { fn collection_page_ref(&self) -> &CollectionPage { - self.inner.collection_page_ref() + self.inner().collection_page_ref() } fn collection_page_mut(&mut self) -> &mut CollectionPage { - self.inner.collection_page_mut() + self.inner_mut().collection_page_mut() } } @@ -1321,11 +1325,11 @@ where Inner: AsOrderedCollectionPage, { fn ordered_collection_page_ref(&self) -> &OrderedCollectionPage { - self.inner.ordered_collection_page_ref() + self.inner().ordered_collection_page_ref() } fn ordered_collection_page_mut(&mut self) -> &mut OrderedCollectionPage { - self.inner.ordered_collection_page_mut() + self.inner_mut().ordered_collection_page_mut() } } diff --git a/src/either.rs b/src/either.rs index b1e110d..ee371c1 100644 --- a/src/either.rs +++ b/src/either.rs @@ -30,4 +30,22 @@ impl Either { Either::Right(ref r) => Either::Right(r), } } + + pub fn as_mut(&mut self) -> Either<&mut L, &mut R> { + match self { + Either::Left(ref mut l) => Either::Left(l), + Either::Right(ref mut r) => Either::Right(r), + } + } + + pub fn map(self, f1: F1, f2: F2) -> Either + where + F1: Fn(L) -> L2, + F2: Fn(R) -> R2, + { + match self { + Either::Left(l) => Either::Left((f1)(l)), + Either::Right(r) => Either::Right((f2)(r)), + } + } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..13f2aa7 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,3 @@ +#[derive(Clone, Debug, thiserror::Error)] +#[error("URL did not match expected domain")] +pub struct DomainError; diff --git a/src/lib.rs b/src/lib.rs index e7579f1..edfaf8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,9 +143,9 @@ //! //! For fields with more specific bounds, like `id`, //! ```rust,ignore -//! fn id(&self) -> Option<&XsdAnyUri>; -//! fn set_id(&mut self, XsdAnyUri) -> &mut Self; -//! fn take_id(&self) -> Option; +//! fn id(&self) -> Option<&Url>; +//! fn set_id(&mut self, Url) -> &mut Self; +//! fn take_id(&self) -> Option; //! fn delete_id(&mut self) -> &mut Self; //! ``` //! @@ -207,6 +207,7 @@ //! prelude::*, //! uri, //! }; +//! use chrono::Duration; //! //! fn main() -> Result<(), anyhow::Error> { //! let mut video = ApObject::new(Video::new()); @@ -217,7 +218,7 @@ //! .set_media_type("video/webm".parse()?) //! .set_url(uri!("https://example.com/@example/lions/video.webm")) //! .set_summary("A cool video") -//! .set_duration("PT4M20S".parse()?) +//! .set_duration(Duration::minutes(4) + Duration::seconds(20)) //! .set_shares(uri!("https://example.com/@example/lions/video.webm#shares")); //! //! println!("Video, {:#?}", video); @@ -299,33 +300,30 @@ pub mod actor; pub mod base; pub mod collection; mod either; +pub mod error; pub mod link; +mod macros; +pub mod markers; pub mod object; pub mod primitives; pub mod unparsed; -pub use activitystreams::{context, public, security}; +pub extern crate chrono; +pub extern crate mime; +pub extern crate url; -pub mod markers { - //! Marker traits for bounding methods - //! - //! ```rust - //! use activitystreams_new::{base::BaseExt, markers::Activity}; - //! - //! /// Applies the name "hi" to any given activity - //! fn manipulator(mut some_type: T) -> T - //! where - //! T: Activity + BaseExt, - //! { - //! some_type.set_name("hi"); - //! - //! some_type - //! } - //! ``` +pub fn context() -> url::Url { + "https://www.w3.org/ns/activitystreams".parse().unwrap() +} - pub use activitystreams::{ - Activity, Actor, Base, Collection, CollectionPage, IntransitiveActivity, Link, Object, - }; +pub fn public() -> url::Url { + "https://www.w3.org/ns/activitystreams#Public" + .parse() + .unwrap() +} + +pub fn security() -> url::Url { + "https://w3id.org/security/v1".parse().unwrap() } pub mod prelude { @@ -343,6 +341,7 @@ pub mod prelude { //! security, //! uri, //! }; + //! use chrono::Duration; //! //! let mut person = ApActor::new( //! uri!("http://localhost:8080/inbox"), @@ -370,7 +369,7 @@ pub mod prelude { //! .set_media_type("video/webm".parse()?) //! .set_summary("A cool video") //! .set_preview(preview.into_any_base()?) - //! .set_duration("PT4M20S".parse()?) + //! .set_duration(Duration::minutes(4) + Duration::seconds(20)) //! .set_shares(uri!("http://localhost:8080/video.webm#shares")); //! //! let mut activity = Create::new( @@ -397,24 +396,3 @@ pub mod prelude { object::{ApObjectExt, ObjectExt, PlaceExt, ProfileExt, RelationshipExt, TombstoneExt}, }; } - -/// A macro to shorten the `string.parse::()?` calls inevitably made in downstream code -/// -/// ```rust -/// use activitystreams_new::uri; -/// -/// fn fallible() -> Result<(), anyhow::Error> { -/// let my_uri = uri!("https://example.com"); -/// Ok(()) -/// } -/// -/// # fn main() -> Result<(), anyhow::Error> { fallible() } -/// ``` -#[macro_export] -macro_rules! uri { - ( $x:expr ) => {{ - use activitystreams_new::primitives::XsdAnyUri; - - $x.parse::()? - }}; -} diff --git a/src/link.rs b/src/link.rs index 846f934..b8700a6 100644 --- a/src/link.rs +++ b/src/link.rs @@ -23,11 +23,11 @@ use crate::{ base::{AsBase, Base, Extends}, markers, - primitives::{OneOrMany, XsdAnyUri, XsdNonNegativeInteger}, + primitives::{OneOrMany, XsdAnyUri}, unparsed::{Unparsed, UnparsedMut, UnparsedMutExt}, }; use std::convert::TryFrom; -use typed_builder::TypedBuilder; +use url::Url; pub mod kind { //! Kinds of links defined by the spec @@ -35,7 +35,9 @@ pub mod kind { //! These types exist only to be statically-typed versions of the associated string. e.g. //! `MentionType` -> `"Mention"` - pub use activitystreams::link::kind::MentionType; + use crate::kind; + + kind!(MentionType, Mention); } use self::kind::MentionType; @@ -67,11 +69,11 @@ pub trait LinkExt: AsLink { /// /// let mention_href = mention.href(); /// ``` - fn href<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn href<'a>(&'a self) -> Option<&'a Url> where Kind: 'a, { - self.link_ref().href.as_ref() + self.link_ref().href.as_ref().map(|u| u.as_ref()) } /// Set the href for the current object @@ -89,8 +91,8 @@ pub trait LinkExt: AsLink { /// # Ok(()) /// # } /// ``` - fn set_href(&mut self, href: XsdAnyUri) -> &mut Self { - self.link_mut().href = Some(href); + fn set_href(&mut self, href: Url) -> &mut Self { + self.link_mut().href = Some(href.into()); self } @@ -106,8 +108,8 @@ pub trait LinkExt: AsLink { /// println!("{:?}", href); /// } /// ``` - fn take_href(&mut self) -> Option { - self.link_mut().href.take() + fn take_href(&mut self) -> Option { + self.link_mut().href.take().map(|u| u.into_inner()) } /// Delete the href from the current object @@ -335,11 +337,11 @@ pub trait LinkExt: AsLink { /// println!("{:?}", height); /// } /// ``` - fn height<'a>(&'a self) -> Option<&'a XsdNonNegativeInteger> + fn height<'a>(&'a self) -> Option where Kind: 'a, { - self.link_ref().height.as_ref() + self.link_ref().height } /// Set the height for the current object @@ -352,11 +354,11 @@ pub trait LinkExt: AsLink { /// # /// use activitystreams_new::prelude::*; /// - /// mention.set_height(5); + /// mention.set_height(5u64); /// ``` fn set_height(&mut self, height: T) -> &mut Self where - T: Into, + T: Into, { self.link_mut().height = Some(height.into()); self @@ -374,7 +376,7 @@ pub trait LinkExt: AsLink { /// println!("{:?}", height); /// } /// ``` - fn take_height(&mut self) -> Option { + fn take_height(&mut self) -> Option { self.link_mut().height.take() } @@ -383,7 +385,7 @@ pub trait LinkExt: AsLink { /// ```rust /// # use activitystreams_new::link::Mention; /// # let mut mention = Mention::new(); - /// # mention.set_height(5); + /// # mention.set_height(5u64); /// # /// use activitystreams_new::prelude::*; /// @@ -408,11 +410,11 @@ pub trait LinkExt: AsLink { /// println!("{:?}", width); /// } /// ``` - fn width<'a>(&'a self) -> Option<&'a XsdNonNegativeInteger> + fn width<'a>(&'a self) -> Option where Kind: 'a, { - self.link_ref().width.as_ref() + self.link_ref().width } /// Set the width for the current object @@ -425,11 +427,11 @@ pub trait LinkExt: AsLink { /// # /// use activitystreams_new::prelude::*; /// - /// mention.set_width(5); + /// mention.set_width(5u64); /// ``` fn set_width(&mut self, width: T) -> &mut Self where - T: Into, + T: Into, { self.link_mut().width = Some(width.into()); self @@ -447,7 +449,7 @@ pub trait LinkExt: AsLink { /// println!("{:?}", width); /// } /// ``` - fn take_width(&mut self) -> Option { + fn take_width(&mut self) -> Option { self.link_mut().width.take() } @@ -456,7 +458,7 @@ pub trait LinkExt: AsLink { /// ```rust /// # use activitystreams_new::link::Mention; /// # let mut mention = Mention::new(); - /// # mention.set_width(5); + /// # mention.set_width(5u64); /// # /// use activitystreams_new::prelude::*; /// @@ -492,7 +494,7 @@ pub type Mention = Link; /// object might have multiple such visual representations -- multiple screenshots, for instance, /// or the same image at different resolutions. In Activity Streams 2.0, there are essentially /// three ways of describing such references. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Link { /// The target resource pointed to by a Link. @@ -500,8 +502,7 @@ pub struct Link { /// - Range: xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub href: Option, + href: Option, /// Hints as to the language used by the target resource. /// @@ -510,8 +511,7 @@ pub struct Link { /// - Range: [BCP47] Language Tag /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub hreflang: Option, + hreflang: Option, /// A link relation associated with a Link. /// @@ -522,28 +522,25 @@ pub struct Link { /// - Range: [RFC5988] or [HTML5] Link Relation /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub rel: Option>, + rel: Option>, /// On a Link, specifies a hint as to the rendering height in device-independent pixels of the linked resource. /// /// - Range: xsd:nonNegativeInteger /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub height: Option, + height: Option, /// On a Link, specifies a hint as to the rendering width in device-independent pixels of the linked resource. /// /// Range: xsd:nonNegativeInteger /// Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub width: Option, + width: Option, /// Base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Base, + inner: Base, } impl Link { @@ -558,7 +555,14 @@ impl Link { where Kind: Default, { - Link::builder().inner(Base::new()).build() + Link { + href: None, + hreflang: None, + rel: None, + height: None, + width: None, + inner: Base::new(), + } } fn extending(mut inner: Base) -> Result { diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..92955e9 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,69 @@ +/// Generate an enum implementing serde's Serialize and Deserialize with a single variant +/// +/// This is useful for describing constants +/// +/// ```rust +/// # fn main() -> Result<(), anyhow::Error> { +/// use activitystreams_new::kind; +/// +/// kind!(CustomType, Custom); +/// +/// #[derive(serde::Deserialize)] +/// struct MyStruct { +/// #[serde(rename = "type")] +/// kind: CustomType, +/// } +/// +/// let s: MyStruct = serde_json::from_str(r#"{"type":"Custom"}"#)?; +/// +/// assert_eq!(s.kind, CustomType::Custom); +/// # Ok(()) +/// # } +/// ``` +#[macro_export] +macro_rules! kind { + ($x:ident, $y:ident) => { + #[derive( + Clone, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + serde::Deserialize, + serde::Serialize, + )] + /// A type stand-in for the constant $y, deriving serde traits + pub enum $x { + $y, + } + + impl Default for $x { + fn default() -> Self { + $x::$y + } + } + }; +} + +/// A macro to shorten the `string.parse::()?` calls inevitably made in downstream code +/// +/// ```rust +/// use activitystreams_new::uri; +/// +/// fn fallible() -> Result<(), anyhow::Error> { +/// let my_uri = uri!("https://example.com"); +/// Ok(()) +/// } +/// +/// # fn main() -> Result<(), anyhow::Error> { fallible() } +/// ``` +#[macro_export] +macro_rules! uri { + ( $x:expr ) => {{ + use activitystreams_new::url::Url; + + $x.parse::()? + }}; +} diff --git a/src/markers.rs b/src/markers.rs new file mode 100644 index 0000000..0f72804 --- /dev/null +++ b/src/markers.rs @@ -0,0 +1,89 @@ +//! Marker traits for bounding methods +//! +//! ```rust +//! use activitystreams_new::{base::BaseExt, markers::Activity}; +//! +//! /// Applies the name "hi" to any given activity +//! fn manipulator(mut some_type: T) -> T +//! where +//! T: Activity + BaseExt, +//! { +//! some_type.set_name("hi"); +//! +//! some_type +//! } +//! ``` + +/// The lowermost trait of the trait structure +/// +/// Base exists solely so Object and Link can have impls that don't potentially conflict +pub trait Base {} + +/// Describes an object of any kind. +/// +/// The Object type serves as the base type for most of the other kinds of objects defined in the +/// Activity Vocabulary, including other Core types such as `Activity`, `IntransitiveActivity`, +/// `Collection` and `OrderedCollection`. +pub trait Object: Base {} + +/// A Link is an indirect, qualified reference to a resource identified by a URL. +/// +/// The fundamental model for links is established by +/// [[RFC5988](https://tools.ietf.org/html/rfc5988)]. Many of the properties defined by the +/// Activity Vocabulary allow values that are either instances of Object or Link. When a Link is +/// used, it establishes a qualified relation connecting the subject (the containing object) to the +/// resource identified by the href. Properties of the Link are properties of the reference as +/// opposed to properties of the resource. +pub trait Link: Base {} + +/// A Collection is a subtype of `Object` that represents ordered or unordered sets of `Object` or +/// `Link` instances. +/// +/// The items within a Collection can be ordered or unordered. The OrderedCollection type MAY be +/// used to identify a Collection whose items are always ordered. In the JSON serialization, the +/// unordered items of a Collection are represented using the items property while ordered items +/// are represented using the orderedItems property. +pub trait Collection: Object {} + +/// Used to represent distinct subsets of items from a Collection. +/// +/// A `Collection` can contain a large number of items. Often, it becomes impractical for an +/// implementation to serialize every item contained by a `Collection` using the items (or +/// `ordered_items`) property alone. In such cases, the items within a `Collection` can be divided +/// into distinct subsets or "pages". A page is identified using the `CollectionPage` type. +pub trait CollectionPage: Collection {} + +/// `Actor` types are `Object` types that are capable of performing activities. +/// +/// This specification intentionally defines `Actors` in only the most generalized way, stopping +/// short of defining semantically specific properties for each. All Actor objects are +/// specializations of `Object` and inherit all of the core properties common to all Objects. +/// External vocabularies can be used to express additional detail not covered by the Activity +/// Vocabulary. VCard [[vcard-rdf](https://www.w3.org/TR/vcard-rdf/) SHOULD be used to provide +/// additional metadata for `Person`, `Group`, and `Organization` instances. +/// +/// While implementations are free to introduce new types of Actors beyond those defined by the +/// Activity Vocabulary, interoperability issues can arise when applications rely too much on +/// extension types that are not recognized by other implementations. Care should be taken to not +/// unduly overlap with or duplicate the existing `Actor` types. +/// +/// When an implementation uses an extension type that overlaps with a core vocabulary type, the +/// implementation MUST also specify the core vocabulary type. For instance, some vocabularies +/// (e.g. VCard) define their own types for describing people. An implementation that wishes, for +/// example, to use a `vcard:Individual` as an `Actor` MUST also identify that `Actor` as a +/// `Person`. +pub trait Actor: Object {} + +/// An Activity is a subtype of `Object` that describes some form of action that may happen, is +/// currently happening, or has already happened. +/// +/// The `Activity` type itself serves as an abstract base type for all types of activities. It is +/// important to note that the `Activity` type itself does not carry any specific semantics about +/// the kind of action being taken. +pub trait Activity: Object {} + +/// Instances of `IntransitiveActivity` are a subtype of `Activity` representing intransitive +/// actions. +/// +/// The `object` property is therefore inappropriate for these activities. +pub trait IntransitiveActivity: Activity {} diff --git a/src/object.rs b/src/object.rs index c4774ff..eb1924b 100644 --- a/src/object.rs +++ b/src/object.rs @@ -22,11 +22,12 @@ use crate::{ base::{AnyBase, AsBase, Base, Extends}, markers, - primitives::{AnyString, OneOrMany, Unit, XsdAnyUri, XsdDateTime, XsdDuration, XsdFloat}, + primitives::{AnyString, OneOrMany, Unit, XsdAnyUri, XsdDateTime, XsdDuration}, unparsed::{Unparsed, UnparsedMut, UnparsedMutExt}, }; +use chrono::{DateTime, Duration, FixedOffset}; use std::convert::TryFrom; -use typed_builder::TypedBuilder; +use url::Url; pub mod kind { //! Kinds of objects defined by the spec @@ -34,7 +35,20 @@ pub mod kind { //! These types exist only to be statically-typed versions of the associated string. e.g. //! `PlaceType` -> `"Place"` - pub use activitystreams::object::kind::*; + use crate::kind; + + kind!(ArticleType, Article); + kind!(AudioType, Audio); + kind!(DocumentType, Document); + kind!(EventType, Event); + kind!(ImageType, Image); + kind!(NoteType, Note); + kind!(PageType, Page); + kind!(PlaceType, Place); + kind!(ProfileType, Profile); + kind!(RelationshipType, Relationship); + kind!(TombstoneType, Tombstone); + kind!(VideoType, Video); } use self::kind::*; @@ -1588,11 +1602,14 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", start_time); /// } /// ``` - fn start_time<'a>(&'a self) -> Option<&'a XsdDateTime> + fn start_time<'a>(&'a self) -> Option> where Kind: 'a, { - self.object_ref().start_time.as_ref() + self.object_ref() + .start_time + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the start_time for the current object @@ -1602,15 +1619,15 @@ pub trait ObjectExt: AsObject { /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams_new::prelude::*; - /// # use activitystreams_new::{object::Video, primitives::XsdDateTime}; + /// # use activitystreams_new::{object::Video}; /// # let mut video = Video::new(); /// /// video.set_start_time("2020-04-20T04:20:00Z".parse()?); /// # Ok(()) /// # } /// ``` - fn set_start_time(&mut self, start_time: XsdDateTime) -> &mut Self { - self.object_mut().start_time = Some(start_time); + fn set_start_time(&mut self, start_time: DateTime) -> &mut Self { + self.object_mut().start_time = Some(start_time.into()); self } @@ -1626,15 +1643,15 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", start_time); /// } /// ``` - fn take_start_time(&mut self) -> Option { - self.object_mut().start_time.take() + fn take_start_time(&mut self) -> Option> { + self.object_mut().start_time.take().map(|d| d.into_inner()) } /// Delete the start_time from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// # video.set_start_time("2020-04-20T04:20:00Z".parse()?); /// # @@ -1663,11 +1680,14 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", end_time); /// } /// ``` - fn end_time<'a>(&'a self) -> Option<&'a XsdDateTime> + fn end_time<'a>(&'a self) -> Option> where Kind: 'a, { - self.object_ref().end_time.as_ref() + self.object_ref() + .end_time + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the end_time for the current object @@ -1677,15 +1697,15 @@ pub trait ObjectExt: AsObject { /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams_new::prelude::*; - /// # use activitystreams_new::{object::Video, primitives::XsdDateTime}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// /// video.set_end_time("2020-04-20T04:20:00-05:00".parse()?); /// # Ok(()) /// # } /// ``` - fn set_end_time(&mut self, end_time: XsdDateTime) -> &mut Self { - self.object_mut().end_time = Some(end_time); + fn set_end_time(&mut self, end_time: DateTime) -> &mut Self { + self.object_mut().end_time = Some(end_time.into()); self } @@ -1701,15 +1721,15 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", end_time); /// } /// ``` - fn take_end_time(&mut self) -> Option { - self.object_mut().end_time.take() + fn take_end_time(&mut self) -> Option> { + self.object_mut().end_time.take().map(|d| d.into_inner()) } /// Delete the end_time from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// # video.set_end_time("2020-04-20T04:20:00Z".parse()?); /// # @@ -1738,11 +1758,14 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", duration); /// } /// ``` - fn duration<'a>(&'a self) -> Option<&'a XsdDuration> + fn duration<'a>(&'a self) -> Option where Kind: 'a, { - self.object_ref().duration.as_ref() + self.object_ref() + .duration + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the duration for the current object @@ -1752,15 +1775,16 @@ pub trait ObjectExt: AsObject { /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams_new::prelude::*; - /// # use activitystreams_new::{object::Video, primitives::XsdDateTime}; + /// use chrono::Duration; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// - /// video.set_duration("P3DT1H".parse()?); + /// video.set_duration(Duration::minutes(4) + Duration::seconds(20)); /// # Ok(()) /// # } /// ``` - fn set_duration(&mut self, duration: XsdDuration) -> &mut Self { - self.object_mut().duration = Some(duration); + fn set_duration(&mut self, duration: Duration) -> &mut Self { + self.object_mut().duration = Some(duration.into()); self } @@ -1776,17 +1800,18 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", duration); /// } /// ``` - fn take_duration(&mut self) -> Option { - self.object_mut().duration.take() + fn take_duration(&mut self) -> Option { + self.object_mut().duration.take().map(|d| d.into_inner()) } /// Delete the duration from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; + /// # use chrono::Duration; /// # let mut video = Video::new(); - /// # video.set_duration("P1Y2MT3M30S".parse()?); + /// # video.set_duration(Duration::hours(1)); /// # /// use activitystreams_new::prelude::*; /// @@ -1813,11 +1838,14 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", published); /// } /// ``` - fn published<'a>(&'a self) -> Option<&'a XsdDateTime> + fn published<'a>(&'a self) -> Option> where Kind: 'a, { - self.object_ref().published.as_ref() + self.object_ref() + .published + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the published for the current object @@ -1827,15 +1855,15 @@ pub trait ObjectExt: AsObject { /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams_new::prelude::*; - /// # use activitystreams_new::{object::Video, primitives::XsdDateTime}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// /// video.set_published("2020-04-20T04:20:00Z".parse()?); /// # Ok(()) /// # } /// ``` - fn set_published(&mut self, published: XsdDateTime) -> &mut Self { - self.object_mut().published = Some(published); + fn set_published(&mut self, published: DateTime) -> &mut Self { + self.object_mut().published = Some(published.into()); self } @@ -1851,15 +1879,15 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", published); /// } /// ``` - fn take_published(&mut self) -> Option { - self.object_mut().published.take() + fn take_published(&mut self) -> Option> { + self.object_mut().published.take().map(|d| d.into_inner()) } /// Delete the published from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// # video.set_published("2020-04-20T04:20:00Z".parse()?); /// # @@ -1888,11 +1916,14 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", updated); /// } /// ``` - fn updated<'a>(&'a self) -> Option<&'a XsdDateTime> + fn updated<'a>(&'a self) -> Option> where Kind: 'a, { - self.object_ref().updated.as_ref() + self.object_ref() + .updated + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the updated for the current object @@ -1909,8 +1940,8 @@ pub trait ObjectExt: AsObject { /// # Ok(()) /// # } /// ``` - fn set_updated(&mut self, updated: XsdDateTime) -> &mut Self { - self.object_mut().updated = Some(updated); + fn set_updated(&mut self, updated: DateTime) -> &mut Self { + self.object_mut().updated = Some(updated.into()); self } @@ -1926,15 +1957,15 @@ pub trait ObjectExt: AsObject { /// println!("{:?}", updated); /// } /// ``` - fn take_updated(&mut self) -> Option { - self.object_mut().updated.take() + fn take_updated(&mut self) -> Option> { + self.object_mut().updated.take().map(|d| d.into_inner()) } /// Delete the updated from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Video, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Video; /// # let mut video = Video::new(); /// # video.set_updated("2020-04-20T04:20:00Z".parse()?); /// # @@ -2786,11 +2817,11 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", shares); /// } /// ``` - fn shares<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn shares<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_object_ref().shares.as_ref() + self.ap_object_ref().shares.as_ref().map(|u| u.as_ref()) } /// Set the shares for the current object @@ -2808,8 +2839,8 @@ pub trait ApObjectExt: AsApObject { /// # Ok(()) /// # } /// ``` - fn set_shares(&mut self, shares: XsdAnyUri) -> &mut Self { - self.ap_object_mut().shares = Some(shares); + fn set_shares(&mut self, shares: Url) -> &mut Self { + self.ap_object_mut().shares = Some(shares.into()); self } @@ -2825,8 +2856,8 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", shares); /// } /// ``` - fn take_shares(&mut self) -> Option { - self.ap_object_mut().shares.take() + fn take_shares(&mut self) -> Option { + self.ap_object_mut().shares.take().map(|u| u.into_inner()) } /// Delete the shares from the current object @@ -2859,11 +2890,11 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", likes); /// } /// ``` - fn likes<'a>(&'a self) -> Option<&'a XsdAnyUri> + fn likes<'a>(&'a self) -> Option<&'a Url> where Inner: 'a, { - self.ap_object_ref().likes.as_ref() + self.ap_object_ref().likes.as_ref().map(|u| u.as_ref()) } /// Set the likes for the current object @@ -2881,8 +2912,8 @@ pub trait ApObjectExt: AsApObject { /// # Ok(()) /// # } /// ``` - fn set_likes(&mut self, likes: XsdAnyUri) -> &mut Self { - self.ap_object_mut().likes = Some(likes); + fn set_likes(&mut self, likes: Url) -> &mut Self { + self.ap_object_mut().likes = Some(likes.into()); self } @@ -2898,8 +2929,8 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", likes); /// } /// ``` - fn take_likes(&mut self) -> Option { - self.ap_object_mut().likes.take() + fn take_likes(&mut self) -> Option { + self.ap_object_mut().likes.take().map(|u| u.into_inner()) } /// Delete the likes from the current object @@ -3008,11 +3039,14 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", upload_media); /// } /// ``` - fn upload_media<'a>(&'a self) -> Option<&'a OneOrMany> + fn upload_media<'a>(&'a self) -> Option> where Inner: 'a, { - self.ap_object_ref().upload_media.as_ref() + self.ap_object_ref() + .upload_media + .as_ref() + .map(|o| o.as_ref().map(|u| u.as_ref())) } /// Set the upload_media for the current object @@ -3030,8 +3064,8 @@ pub trait ApObjectExt: AsApObject { /// # Ok(()) /// # } /// ``` - fn set_upload_media(&mut self, upload_media: XsdAnyUri) -> &mut Self { - self.ap_object_mut().upload_media = Some(upload_media.into()); + fn set_upload_media(&mut self, upload_media: Url) -> &mut Self { + self.ap_object_mut().upload_media = Some(XsdAnyUri::from(upload_media).into()); self } @@ -3055,9 +3089,9 @@ pub trait ApObjectExt: AsApObject { /// ``` fn set_many_upload_medias(&mut self, items: I) -> &mut Self where - I: IntoIterator, + I: IntoIterator, { - let v: Vec<_> = items.into_iter().collect(); + let v: Vec = items.into_iter().map(|u| u.into()).collect(); self.ap_object_mut().upload_media = Some(v.into()); self } @@ -3079,13 +3113,13 @@ pub trait ApObjectExt: AsApObject { /// # Ok(()) /// # } /// ``` - fn add_upload_media(&mut self, upload_media: XsdAnyUri) -> &mut Self { + fn add_upload_media(&mut self, upload_media: Url) -> &mut Self { let v = match self.ap_object_mut().upload_media.take() { Some(mut v) => { - v.add(upload_media); + v.add(XsdAnyUri::from(upload_media)); v } - None => vec![upload_media].into(), + None => vec![upload_media.into()].into(), }; self.ap_object_mut().upload_media = Some(v); self @@ -3103,8 +3137,11 @@ pub trait ApObjectExt: AsApObject { /// println!("{:?}", upload_media); /// } /// ``` - fn take_upload_media(&mut self) -> Option> { - self.ap_object_mut().upload_media.take() + fn take_upload_media(&mut self) -> Option> { + self.ap_object_mut() + .upload_media + .take() + .map(|o| o.map(|u| u.into_inner())) } /// Delete the upload_media from the current object @@ -3144,8 +3181,8 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", accuracy); /// } /// ``` - fn accuracy(&self) -> Option<&XsdFloat> { - self.place_ref().accuracy.as_ref() + fn accuracy(&self) -> Option { + self.place_ref().accuracy } /// Set the accuracy for the current object @@ -3162,7 +3199,7 @@ pub trait PlaceExt: AsPlace { /// ``` fn set_accuracy(&mut self, float: T) -> &mut Self where - T: Into, + T: Into, { self.place_mut().accuracy = Some(float.into()); self @@ -3180,7 +3217,7 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", accuracy); /// } /// ``` - fn take_accuracy(&mut self) -> Option { + fn take_accuracy(&mut self) -> Option { self.place_mut().accuracy.take() } @@ -3214,8 +3251,8 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", altitude); /// } /// ``` - fn altitude(&self) -> Option<&XsdFloat> { - self.place_ref().altitude.as_ref() + fn altitude(&self) -> Option { + self.place_ref().altitude } /// Set the altitude for the current object @@ -3232,7 +3269,7 @@ pub trait PlaceExt: AsPlace { /// ``` fn set_altitude(&mut self, float: T) -> &mut Self where - T: Into, + T: Into, { self.place_mut().altitude = Some(float.into()); self @@ -3250,7 +3287,7 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", altitude); /// } /// ``` - fn take_altitude(&mut self) -> Option { + fn take_altitude(&mut self) -> Option { self.place_mut().altitude.take() } @@ -3284,8 +3321,8 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", latitude); /// } /// ``` - fn latitude(&self) -> Option<&XsdFloat> { - self.place_ref().latitude.as_ref() + fn latitude(&self) -> Option { + self.place_ref().latitude } /// Set the latitude for the current object @@ -3302,7 +3339,7 @@ pub trait PlaceExt: AsPlace { /// ``` fn set_latitude(&mut self, float: T) -> &mut Self where - T: Into, + T: Into, { self.place_mut().latitude = Some(float.into()); self @@ -3320,7 +3357,7 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", latitude); /// } /// ``` - fn take_latitude(&mut self) -> Option { + fn take_latitude(&mut self) -> Option { self.place_mut().latitude.take() } @@ -3354,8 +3391,8 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", longitude); /// } /// ``` - fn longitude(&self) -> Option<&XsdFloat> { - self.place_ref().longitude.as_ref() + fn longitude(&self) -> Option { + self.place_ref().longitude } /// Set the longitude for the current object @@ -3372,7 +3409,7 @@ pub trait PlaceExt: AsPlace { /// ``` fn set_longitude(&mut self, float: T) -> &mut Self where - T: Into, + T: Into, { self.place_mut().longitude = Some(float.into()); self @@ -3390,7 +3427,7 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", longitude); /// } /// ``` - fn take_longitude(&mut self) -> Option { + fn take_longitude(&mut self) -> Option { self.place_mut().longitude.take() } @@ -3424,8 +3461,8 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", radius); /// } /// ``` - fn radius(&self) -> Option<&XsdFloat> { - self.place_ref().radius.as_ref() + fn radius(&self) -> Option { + self.place_ref().radius } /// Set the radius for the current object @@ -3442,7 +3479,7 @@ pub trait PlaceExt: AsPlace { /// ``` fn set_radius(&mut self, float: T) -> &mut Self where - T: Into, + T: Into, { self.place_mut().radius = Some(float.into()); self @@ -3460,7 +3497,7 @@ pub trait PlaceExt: AsPlace { /// println!("{:?}", radius); /// } /// ``` - fn take_radius(&mut self) -> Option { + fn take_radius(&mut self) -> Option { self.place_mut().radius.take() } @@ -4130,8 +4167,11 @@ pub trait TombstoneExt: AsTombstone { /// println!("{:?}", deleted); /// } /// ``` - fn deleted(&self) -> Option<&XsdDateTime> { - self.tombstone_ref().deleted.as_ref() + fn deleted(&self) -> Option> { + self.tombstone_ref() + .deleted + .as_ref() + .map(|d| d.clone().into_inner()) } /// Set the deleted for the current object @@ -4148,7 +4188,7 @@ pub trait TombstoneExt: AsTombstone { /// # Ok(()) /// # } /// ``` - fn set_deleted(&mut self, deleted: XsdDateTime) -> &mut Self { + fn set_deleted(&mut self, deleted: DateTime) -> &mut Self { self.tombstone_mut().deleted = Some(deleted.into()); self } @@ -4165,15 +4205,15 @@ pub trait TombstoneExt: AsTombstone { /// println!("{:?}", deleted); /// } /// ``` - fn take_deleted(&mut self) -> Option { - self.tombstone_mut().deleted.take() + fn take_deleted(&mut self) -> Option> { + self.tombstone_mut().deleted.take().map(|d| d.into_inner()) } /// Delete the deleted from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::{object::Tombstone, primitives::XsdAnyUri}; + /// # use activitystreams_new::object::Tombstone; /// # let mut tombstone = Tombstone::new(); /// # tombstone.set_deleted("2020-04-20T04:20:00Z".parse()?); /// # @@ -4244,7 +4284,7 @@ pub type Video = Object; /// The Object type serves as the base type for most of the other kinds of objects defined in the /// Activity Vocabulary, including other Core types such as Activity, IntransitiveActivity, /// Collection and OrderedCollection. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct Object { /// Identifies a resource attached or related to an object that potentially requires special @@ -4256,8 +4296,7 @@ pub struct Object { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub attachment: Option>, + attachment: Option>, /// Identifies one or more entities to which this object is attributed. /// @@ -4267,8 +4306,7 @@ pub struct Object { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub attributed_to: Option>, + attributed_to: Option>, /// Identifies one or more entities that represent the total population of entities for which /// the object can considered to be relevant. @@ -4276,8 +4314,7 @@ pub struct Object { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub audience: Option>, + audience: Option>, /// The content or textual representation of the Object encoded as a JSON string. /// @@ -4289,8 +4326,7 @@ pub struct Object { /// - Range: xsd:string | rdf:langString /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub content: Option>, + content: Option>, /// A natural language summarization of the object encoded as HTML. /// @@ -4299,24 +4335,21 @@ pub struct Object { /// - Range: xsd:string | rdf:langString /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub summary: Option>, + summary: Option>, /// Identifies one or more links to representations of the object. /// /// - Range: xsd:anyUri | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub url: Option>, + url: Option>, /// Identifies the entity (e.g. an application) that generated the object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub generator: Option>, + generator: Option>, /// Indicates an entity that describes an icon for this object. /// @@ -4326,8 +4359,7 @@ pub struct Object { /// - Range: Image | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub icon: Option>, + icon: Option>, /// Indicates an entity that describes an image for this object. /// @@ -4336,16 +4368,14 @@ pub struct Object { /// - Range: Image | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub image: Option>, + image: Option>, /// Indicates one or more physical or logical locations associated with the object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub location: Option>, + location: Option>, /// One or more "tags" that have been associated with an objects. A tag can be any kind of Object. /// @@ -4355,8 +4385,7 @@ pub struct Object { /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub tag: Option>, + tag: Option>, /// The date and time describing the actual or expected starting time of the object. /// @@ -4366,8 +4395,7 @@ pub struct Object { /// - Range: xsd:DateTime /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub start_time: Option, + start_time: Option, /// The date and time describing the actual or expected ending time of the object. /// @@ -4377,8 +4405,7 @@ pub struct Object { /// - Range: xsd:dateTime /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub end_time: Option, + end_time: Option, /// When the object describes a time-bound resource, such as an audio or video, a meeting, etc, /// the duration property indicates the object's approximate duration. @@ -4390,80 +4417,71 @@ pub struct Object { /// - Range: xsd:duration /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub duration: Option, + duration: Option, /// The date and time at which the object was published. /// /// - Range: xsd:dateTime /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub published: Option, + published: Option, /// The date and time at which the object was updated, /// /// - Range: xsd:dateTime /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub updated: Option, + updated: Option, /// Indicates one or more entities for which this object is considered a response. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub in_reply_to: Option>, + in_reply_to: Option>, /// Identifies a Collection containing objects considered to be responses to this object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub replies: Option>, + replies: Option>, /// Identifies an entity considered to be part of the public primary audience of an Object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub to: Option>, + to: Option>, /// Identifies an Object that is part of the private primary audience of this Object. /// /// Range: Object | Link /// Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub bto: Option>, + bto: Option>, /// Identifies an Object that is part of the public secondary audience of this Object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub cc: Option>, + cc: Option>, /// Identifies one or more Objects that are part of the private secondary audience of this Object. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub bcc: Option>, + bcc: Option>, /// Base fields and unparsed json ends up here #[serde(flatten)] - pub inner: Base, + inner: Base, } /// Define activitypub properties for the Object type as described by the Activity Pub vocabulary. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ApObject { /// This is a list of all Announce activities with this object as the object property, added as @@ -4476,8 +4494,7 @@ pub struct ApObject { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub shares: Option, + shares: Option, /// This is a list of all Like activities with this object as the object property, added as a /// side effect. @@ -4489,8 +4506,7 @@ pub struct ApObject { /// - Range: anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub likes: Option, + likes: Option, /// The source property is intended to convey some sort of source from which the content markup /// was derived, as a form of provenance, or to support future editing by clients. @@ -4503,8 +4519,7 @@ pub struct ApObject { /// - Range: Object /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub source: Option, + source: Option, /// Servers MAY support uploading document types to be referenced in activites, such as images, /// video or other binary data, but the precise mechanism is out of scope for this version of @@ -4516,12 +4531,11 @@ pub struct ApObject { /// - Range: anyUri /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub upload_media: Option>, + upload_media: Option>, /// The ActivityStreams object being extended #[serde(flatten)] - pub inner: Inner, + inner: Inner, } /// Represents a logical or physical location. @@ -4542,7 +4556,7 @@ pub struct ApObject { /// 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, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct Place { /// Indicates the accuracy of position coordinates on a Place objects. /// @@ -4551,8 +4565,7 @@ pub struct Place { /// - Range: xsd:float [>= 0.0f, <= 100.0f] /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub accuracy: Option, + accuracy: Option, /// Indicates the altitude of a place. The measurement units is indicated using the units /// property. @@ -4562,24 +4575,21 @@ pub struct Place { /// - Range: xsd:float /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub altitude: Option, + altitude: Option, ///The latitude of a place. /// /// - Range: xsd:float /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub latitude: Option, + latitude: Option, /// The longitude of a place. /// /// - Range: xsd:float /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub longitude: Option, + longitude: Option, /// The radius from the given latitude and longitude for a Place. /// @@ -4589,8 +4599,7 @@ pub struct Place { /// - Range: xsd:float /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub radius: Option, + radius: Option, /// Specifies the measurement units for the radius and altitude properties on a Place object. /// @@ -4599,31 +4608,29 @@ pub struct Place { /// - Range: "cm" | "feet" | "inches" | "km" | "m" | xsd:anyUri | xsd:anyUri /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option))] - pub units: Option, + units: Option, /// The object being extended #[serde(flatten)] - pub inner: Object, + inner: Object, } /// 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, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct Profile { /// On a Profile object, the describes property identifies the object described by the Profile. /// /// - Range: Object /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub describes: Option, + describes: Option, /// The object being extended #[serde(flatten)] - pub inner: Object, + inner: Object, } /// Describes a relationship between two individuals. @@ -4640,7 +4647,7 @@ pub struct Profile { /// of 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, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct Relationship { /// On a Relationship object, the subject property identifies one of the connected individuals. /// @@ -4650,16 +4657,14 @@ pub struct Relationship { /// - Range: Object | Link /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub subject: Option, + subject: Option, /// When used within a Relationship describes the entity to which the subject is related. /// /// - Range: Object | Link /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub object: Option>, + object: Option>, /// On a Relationship object, the relationship property identifies the kind of relationship /// that exists between subject and object. @@ -4667,19 +4672,18 @@ pub struct Relationship { /// - Range: Object /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub relationship: Option>, + relationship: Option>, /// The object being extended #[serde(flatten)] - pub inner: Object, + inner: Object, } /// 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, serde::Deserialize, serde::Serialize, TypedBuilder)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct Tombstone { /// On a Tombstone object, the formerType property identifies the type of the object that was /// deleted. @@ -4687,20 +4691,18 @@ pub struct Tombstone { /// - Range: Object /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub former_type: Option>, + former_type: Option>, /// On a Tombstone object, the deleted property is a timestamp for when the object was deleted. /// /// - Range: xsd:dateTime /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] - #[builder(default, setter(strip_option, into))] - pub deleted: Option, + deleted: Option, /// The object being extended #[serde(flatten)] - pub inner: Object, + inner: Object, } impl Object { @@ -4715,7 +4717,31 @@ impl Object { where Kind: Default, { - Object::builder().inner(Base::new()).build() + Object { + attachment: None, + attributed_to: None, + audience: None, + content: None, + summary: None, + url: None, + generator: None, + icon: None, + image: None, + location: None, + tag: None, + start_time: None, + end_time: None, + duration: None, + published: None, + updated: None, + in_reply_to: None, + replies: None, + to: None, + bto: None, + cc: None, + bcc: None, + inner: Base::new(), + } } fn extending(mut base: Base) -> Result { @@ -4813,7 +4839,13 @@ impl ApObject { where Inner: markers::Object, { - ApObject::builder().inner(inner).build() + ApObject { + shares: None, + likes: None, + source: None, + upload_media: None, + inner, + } } fn extending(mut inner: Inner) -> Result @@ -4854,6 +4886,16 @@ impl ApObject { Ok(inner) } + + /// Borrow inner + pub fn inner(&self) -> &Inner { + &self.inner + } + + /// Mutably borrow Inner + pub fn inner_mut(&mut self) -> &mut Inner { + &mut self.inner + } } impl Place { @@ -4865,7 +4907,15 @@ impl Place { /// let object = Place::new(); /// ``` pub fn new() -> Self { - Place::builder().inner(Object::new()).build() + Place { + accuracy: None, + altitude: None, + latitude: None, + longitude: None, + radius: None, + units: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { @@ -4919,7 +4969,10 @@ impl Profile { /// let object = Profile::new(); /// ``` pub fn new() -> Self { - Profile::builder().inner(Object::new()).build() + Profile { + describes: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { @@ -4948,7 +5001,12 @@ impl Relationship { /// let object = Relationship::new(); /// ``` pub fn new() -> Self { - Relationship::builder().inner(Object::new()).build() + Relationship { + subject: None, + object: None, + relationship: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { @@ -4990,7 +5048,11 @@ impl Tombstone { /// let object = Tombstone::new(); /// ``` pub fn new() -> Self { - Tombstone::builder().inner(Object::new()).build() + Tombstone { + former_type: None, + deleted: None, + inner: Object::new(), + } } fn extending(mut inner: Object) -> Result { diff --git a/src/primitives.rs b/src/primitives.rs deleted file mode 100644 index 61b2415..0000000 --- a/src/primitives.rs +++ /dev/null @@ -1,796 +0,0 @@ -//! Types creating the base for most ActivityStreams fields. -//! -//! These types are not themselves defined by ActivityStreams, but are referenced by the -//! specification. -//! -//! ```rust -//! use activitystreams_new::primitives::{AnyString, OneOrMany, Unit}; -//! -//! let any_string = AnyString::from_xsd_string("hey"); -//! -//! let one_or_many = OneOrMany::::from_one(1234); -//! -//! let cm = Unit::centimeters(); -//! ``` -use crate::either::Either; - -pub use activitystreams::primitives::{ - MimeMediaType, MimeMediaTypeError, RdfLangString, XsdAnyUri, XsdAnyUriError, XsdDateTime, - XsdDateTimeError, XsdDuration, XsdDurationError, XsdFloat, XsdFloatError, - XsdNonNegativeInteger, -}; - -use activitystreams::primitives::Length; - -/// A type representing any kind of string -/// -/// In the ActivityStreams specification, string types are often defined as either an xsd:String or -/// and rdf:langString. The AnyString type represents this union. -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(transparent)] -pub struct AnyString(Either); - -/// A type representing units of length -/// -/// It can be any of the following -/// - Centimeters -/// - Meters -/// - Kilometers -/// - Inches -/// - Feet -/// - A custom value -#[derive( - Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize, -)] -#[serde(transparent)] -pub struct Unit(Either); - -/// A type representing at least one value -/// -/// When translated to JSON, it can represent the following structures: -/// ```json -/// { -/// "key": value -/// } -/// ``` -/// ```json -/// { -/// "key": [], -/// } -/// ``` -/// ```json -/// { -/// "key": [value, ...] -/// } -/// ``` -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(transparent)] -pub struct OneOrMany(pub(crate) Either>); - -impl AnyString { - /// Borrow the AnyString as an &str - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::AnyString; - /// # let any_string = AnyString::from_xsd_string("hi"); - /// # - /// let s_borrow = any_string - /// .as_xsd_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn as_xsd_string(&self) -> Option<&str> { - self.0.as_ref().left().map(|l| l.as_str()) - } - - /// Borrow the AnyString as an RdfLangString - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{AnyString, RdfLangString}; - /// # let any_string = AnyString::from_rdf_lang_string(RdfLangString { - /// # value: "hi".into(), - /// # language: "en".into(), - /// # }); - /// # - /// let s_borrow = any_string - /// .as_rdf_lang_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn as_rdf_lang_string(&self) -> Option<&RdfLangString> { - self.0.as_ref().right() - } - - /// Take the AnyString as a String - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::AnyString; - /// # let any_string = AnyString::from_xsd_string("hi"); - /// # - /// let xsd_string = any_string - /// .xsd_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn xsd_string(self) -> Option { - self.0.left() - } - - /// Take the AnyString as an RdfLangString - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{AnyString, RdfLangString}; - /// # let any_string = AnyString::from_rdf_lang_string(RdfLangString { - /// # value: "hi".into(), - /// # language: "en".into(), - /// # }); - /// # - /// let rdf_lang_string = any_string - /// .rdf_lang_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn rdf_lang_string(self) -> Option { - self.0.right() - } - - /// Create a new AnyString from an `Into` - /// - /// ```rust - /// use activitystreams_new::primitives::AnyString; - /// - /// let any_string = AnyString::from_xsd_string("hi"); - /// ``` - pub fn from_xsd_string(string: T) -> Self - where - T: Into, - { - AnyString(Either::Left(string.into())) - } - - /// Create a new AnyString from an RdfLangString - /// - /// ```rust - /// use activitystreams_new::primitives::{AnyString, RdfLangString}; - /// - /// let any_string = AnyString::from_rdf_lang_string(RdfLangString { - /// value: "hi".into(), - /// language: "en".into(), - /// }); - /// ``` - pub fn from_rdf_lang_string(string: T) -> Self - where - T: Into, - { - AnyString(Either::Right(string.into())) - } - - /// Replace the contents of self with a String - /// - /// ```rust - /// use activitystreams_new::primitives::{AnyString, RdfLangString}; - /// - /// let mut any_string = AnyString::from_rdf_lang_string(RdfLangString { - /// value: "hi".into(), - /// language: "en".into(), - /// }); - /// - /// any_string.set_xsd_string("hi"); - /// - /// assert!(any_string.as_xsd_string().is_some()); - /// ``` - pub fn set_xsd_string(&mut self, string: T) - where - T: Into, - { - self.0 = Either::Left(string.into()); - } - - /// Replace the contents of self with an RdfLangString - /// - /// ```rust - /// use activitystreams_new::primitives::{AnyString, RdfLangString}; - /// - /// let mut any_string = AnyString::from_xsd_string("hi"); - /// - /// any_string.set_rdf_lang_string(RdfLangString { - /// value: "hi".into(), - /// language: "en".into(), - /// }); - /// - /// assert!(any_string.as_rdf_lang_string().is_some()); - /// ``` - pub fn set_rdf_lang_string(&mut self, string: T) - where - T: Into, - { - self.0 = Either::Right(string.into()); - } -} - -impl OneOrMany { - /// Try to borrow a single String from the current object - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{OneOrMany, AnyString}; - /// # let string = OneOrMany::::from_xsd_string("Hey"); - /// string - /// .as_single_xsd_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn as_single_xsd_string(&self) -> Option<&str> { - self.as_one() - .and_then(|any_string| any_string.as_xsd_string()) - } - - /// Try to borrow a single RdfLangString from the current object - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{OneOrMany, RdfLangString}; - /// # let string = OneOrMany::from_rdf_lang_string(RdfLangString { - /// # value: "hi".into(), - /// # language: "en".into(), - /// # }); - /// string - /// .as_single_rdf_lang_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn as_single_rdf_lang_string(&self) -> Option<&RdfLangString> { - self.as_one() - .and_then(|any_string| any_string.as_rdf_lang_string()) - } - - /// Try to take a single String from the current object - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{OneOrMany, AnyString}; - /// # let string = OneOrMany::::from_xsd_string("Hey"); - /// string - /// .single_xsd_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn single_xsd_string(self) -> Option { - self.one().and_then(|any_string| any_string.xsd_string()) - } - - /// Try to take a single RdfLangString from the current object - /// - /// ```rust - /// # fn main() -> Result<(), anyhow::Error> { - /// # use activitystreams_new::primitives::{OneOrMany, RdfLangString}; - /// # let string = OneOrMany::from_rdf_lang_string(RdfLangString { - /// # value: "hi".into(), - /// # language: "en".into(), - /// # }); - /// string - /// .single_rdf_lang_string() - /// .ok_or(anyhow::Error::msg("Wrong string type"))?; - /// # Ok(()) - /// # } - /// ``` - pub fn single_rdf_lang_string(self) -> Option { - self.one() - .and_then(|any_string| any_string.rdf_lang_string()) - } - - /// Create the object from a single String - /// - /// ```rust - /// use activitystreams_new::primitives::{OneOrMany, AnyString}; - /// - /// let string = OneOrMany::::from_xsd_string("hi"); - /// ``` - pub fn from_xsd_string(string: T) -> Self - where - T: Into, - { - Self::from_one(AnyString::from_xsd_string(string)) - } - - /// Create the object from a single RdfLangString - /// - /// ```rust - /// use activitystreams_new::primitives::{OneOrMany, RdfLangString}; - /// - /// let string = OneOrMany::from_rdf_lang_string(RdfLangString { - /// value: "hi".into(), - /// language: "en".into(), - /// }); - /// ``` - pub fn from_rdf_lang_string(string: T) -> Self - where - T: Into, - { - Self::from_one(AnyString::from_rdf_lang_string(string)) - } - - /// Add a String to the object, appending to whatever is currently included - /// - /// ```rust - /// use activitystreams_new::primitives::{OneOrMany, AnyString}; - /// - /// let mut string = OneOrMany::::from_xsd_string("Hello"); - /// - /// string - /// .add_xsd_string("Hey") - /// .add_xsd_string("hi"); - /// ``` - pub fn add_xsd_string(&mut self, string: T) -> &mut Self - where - T: Into, - { - self.add(string.into()) - } - - /// Add an RdfLangString to the object, appending to whatever is currently included - /// - /// ```rust - /// use activitystreams_new::primitives::{AnyString, OneOrMany, RdfLangString}; - /// - /// let mut string = OneOrMany::::from_xsd_string("Hello"); - /// - /// string - /// .add_rdf_lang_string(RdfLangString { - /// value: "Hey".into(), - /// language: "en".into(), - /// }) - /// .add_rdf_lang_string(RdfLangString { - /// value: "hi".into(), - /// language: "en".into(), - /// }); - /// ``` - pub fn add_rdf_lang_string(&mut self, string: T) -> &mut Self - where - T: Into, - { - self.add(string.into()) - } -} - -impl OneOrMany { - /// Get a reference to a single value - /// - /// ```rust - /// # use activitystreams_new::primitives::OneOrMany; - /// # let value = OneOrMany::from_one(1); - /// if let Some(v) = value.as_one() { - /// println!("{:?}", v); - /// } - /// ``` - pub fn as_one(&self) -> Option<&T> { - self.0.as_ref().left() - } - - /// Take a single value - /// - /// ```rust - /// # use activitystreams_new::primitives::OneOrMany; - /// # let value = OneOrMany::from_one(1); - /// if let Some(v) = value.one() { - /// println!("{:?}", v); - /// } - /// ``` - pub fn one(self) -> Option { - self.0.left() - } - - /// Get a slice of values - /// - /// ```rust - /// # use activitystreams_new::primitives::OneOrMany; - /// # let value = OneOrMany::from_many(vec![1, 2, 3]); - /// if let Some(v) = value.as_many() { - /// for item in v.iter() { - /// println!("{:?}", item); - /// } - /// } - /// ``` - pub fn as_many(&self) -> Option<&[T]> { - self.0.as_ref().right().map(|v| v.as_ref()) - } - - /// Take a Vec of values - /// - /// ```rust - /// # use activitystreams_new::primitives::OneOrMany; - /// # let value = OneOrMany::from_many(vec![1, 2, 3]); - /// if let Some(v) = value.many() { - /// for item in v.into_iter() { - /// println!("{:?}", item); - /// } - /// } - /// ``` - pub fn many(self) -> Option> { - self.0.right() - } - - /// Consume the type, returning a vec - /// - /// ```rust - /// # use activitystreams_new::primitives::OneOrMany; - /// # let value = OneOrMany::from_many(vec![1, 2, 3]); - /// for item in value.unwrap_to_vec() { - /// println!("{:?}", item); - /// } - /// ``` - pub fn unwrap_to_vec(self) -> Vec { - match self.0 { - Either::Left(t) => vec![t], - Either::Right(v) => v, - } - } - - /// Produce a new object from one value - /// - /// ``` - /// use activitystreams_new::primitives::OneOrMany; - /// let v = OneOrMany::from_one(1234); - /// ``` - pub fn from_one(t: T) -> Self { - OneOrMany(Either::Left(t)) - } - - /// Produce a new object from a vec of values - /// - /// ``` - /// use activitystreams_new::primitives::OneOrMany; - /// let v = OneOrMany::from_many(vec![1, 2, 3, 4]); - /// ``` - pub fn from_many(items: Vec) -> Self { - OneOrMany(Either::Right(items)) - } - - /// Overwrite the contents with a single value - /// - /// ``` - /// # use activitystreams_new::primitives::OneOrMany; - /// # let mut value = OneOrMany::from_many(vec![1, 2, 3]); - /// value.set_one(3); - /// - /// assert!(value.as_one().is_some()); - /// ``` - pub fn set_one(&mut self, u: U) -> &mut Self - where - U: Into, - { - self.0 = Either::Left(u.into()); - self - } - - /// Overwrite the contents with vec of values - /// - /// ``` - /// # use activitystreams_new::primitives::OneOrMany; - /// # let mut value = OneOrMany::from_one(1234); - /// value.set_many(vec![1, 2, 3, 4]); - /// - /// assert!(value.as_many().is_some()); - /// ``` - pub fn set_many(&mut self, items: impl IntoIterator) -> &mut Self - where - U: Into, - { - self.0 = Either::Right(items.into_iter().map(Into::into).collect()); - self - } - - /// Add a value to the object - /// - /// This appends the value to the existing vec, or converts the single value into a vec, and - /// then appends the new value - /// - /// ``` - /// use activitystreams_new::primitives::OneOrMany; - /// let mut value = OneOrMany::from_one(1234); - /// value.add(4321); - /// - /// assert!(value.as_many().is_some()); - /// ``` - pub fn add(&mut self, u: U) -> &mut Self - where - U: Into, - { - let mut v = match std::mem::replace(&mut self.0, Either::Right(vec![])) { - Either::Left(one) => vec![one], - Either::Right(v) => v, - }; - v.push(u.into()); - self.0 = Either::Right(v); - self - } - - /// Add many values to the object - /// - /// This appends the values to the existing vec, or converts the single value into a vec, and - /// then appends the new values - /// - /// ``` - /// use activitystreams_new::primitives::OneOrMany; - /// let mut value = OneOrMany::from_one(1234); - /// value.add_many(vec![4321, 2345]); - /// - /// assert!(value.as_many().is_some()); - /// ``` - pub fn add_many(&mut self, items: impl IntoIterator) -> &mut Self - where - U: Into, - { - let mut v = match std::mem::replace(&mut self.0, Either::Right(vec![])) { - Either::Left(one) => vec![one], - Either::Right(v) => v, - }; - v.extend(items.into_iter().map(Into::into)); - self.0 = Either::Right(v); - self - } -} - -impl Unit { - /// Create a new unit measuring Centimeters - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::centimeters(); - /// ``` - pub fn centimeters() -> Self { - Unit(Either::Left(Length::Centimeters)) - } - - /// Check if the unit is Centimeters - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::centimeters().is_centimeters()); - /// ``` - pub fn is_centimeters(&self) -> bool { - self.0 - .as_ref() - .left() - .map(|l| l.is_centimeters()) - .unwrap_or(false) - } - - /// Create a new unit measuring Meters - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::meters(); - /// ``` - pub fn meters() -> Self { - Unit(Either::Left(Length::Meters)) - } - - /// Check if the unit is Meters - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::meters().is_meters()); - /// ``` - pub fn is_meters(&self) -> bool { - self.0 - .as_ref() - .left() - .map(|l| l.is_meters()) - .unwrap_or(false) - } - - /// Create a new unit measuring Kilometers - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::kilometers(); - /// ``` - pub fn kilometers() -> Self { - Unit(Either::Left(Length::Kilometers)) - } - - /// Check if the unit is Kilometers - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::kilometers().is_kilometers()); - /// ``` - pub fn is_kilometers(&self) -> bool { - self.0 - .as_ref() - .left() - .map(|l| l.is_kilometers()) - .unwrap_or(false) - } - - /// Create a new unit measuring Feet - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::feet(); - /// ``` - pub fn feet() -> Self { - Unit(Either::Left(Length::Feet)) - } - - /// Check if the unit is Feet - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::feet().is_feet()); - /// ``` - pub fn is_feet(&self) -> bool { - self.0.as_ref().left().map(|l| l.is_feet()).unwrap_or(false) - } - - /// Create a new unit measuring Inches - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::inches(); - /// ``` - pub fn inches() -> Self { - Unit(Either::Left(Length::Inches)) - } - - /// Check if the unit is Inches - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::inches().is_inches()); - /// ``` - pub fn is_inches(&self) -> bool { - self.0 - .as_ref() - .left() - .map(|l| l.is_inches()) - .unwrap_or(false) - } - - /// Create a new custom unit - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// Unit::custom("Yards"); - /// ``` - pub fn custom(string: T) -> Self - where - T: Into, - { - Unit(Either::Right(string.into())) - } - - /// Check if a unit is custom - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::custom("Yards").is_custom()); - /// ``` - pub fn is_custom(&self) -> bool { - self.as_custom().is_some() - } - - /// Fetch a custom unit - /// - /// ```rust - /// use activitystreams_new::primitives::Unit; - /// - /// assert!(Unit::custom("Yards").as_custom() == Some("Yards")); - /// ``` - pub fn as_custom(&self) -> Option<&str> { - self.0.as_ref().right().map(|r| r.as_str()) - } -} - -impl Default for Unit { - fn default() -> Self { - Self::meters() - } -} - -impl std::str::FromStr for Unit { - type Err = std::convert::Infallible; - - fn from_str(s: &str) -> Result { - let unit = match s { - "cm" => Self::centimeters(), - "m" => Self::meters(), - "km" => Self::kilometers(), - "inches" => Self::inches(), - "feet" => Self::feet(), - other => Self::custom(other), - }; - - Ok(unit) - } -} - -impl From for Unit { - fn from(s: String) -> Self { - match s.parse() { - Ok(u) => u, - Err(e) => match e {}, - } - } -} - -impl From<&str> for Unit { - fn from(s: &str) -> Self { - match s.parse() { - Ok(u) => u, - Err(e) => match e {}, - } - } -} - -impl From for OneOrMany { - fn from(t: T) -> Self { - OneOrMany::from_one(t) - } -} - -impl From> for OneOrMany { - fn from(t: Vec) -> Self { - OneOrMany::from_many(t) - } -} - -impl From<&str> for AnyString { - fn from(s: &str) -> Self { - AnyString::from_xsd_string(s.to_owned()) - } -} - -impl From for AnyString { - fn from(s: String) -> Self { - AnyString::from_xsd_string(s) - } -} - -impl From for AnyString { - fn from(s: RdfLangString) -> Self { - AnyString::from_rdf_lang_string(s) - } -} - -impl From<&str> for OneOrMany { - fn from(s: &str) -> Self { - OneOrMany::::from_xsd_string(s.to_owned()) - } -} - -impl From for OneOrMany { - fn from(s: String) -> Self { - OneOrMany::::from_xsd_string(s) - } -} - -impl From for OneOrMany { - fn from(s: RdfLangString) -> Self { - OneOrMany::::from_rdf_lang_string(s) - } -} diff --git a/src/primitives/any_string.rs b/src/primitives/any_string.rs new file mode 100644 index 0000000..091ba7b --- /dev/null +++ b/src/primitives/any_string.rs @@ -0,0 +1,345 @@ +use crate::{ + either::Either, + primitives::{OneOrMany, RdfLangString}, +}; + +/// A type representing any kind of string +/// +/// In the ActivityStreams specification, string types are often defined as either an xsd:String or +/// and rdf:langString. The AnyString type represents this union. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(transparent)] +pub struct AnyString(Either); + +impl AnyString { + /// Borrow the AnyString as an &str + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::AnyString; + /// # let any_string = AnyString::from_xsd_string("hi"); + /// # + /// let s_borrow = any_string + /// .as_xsd_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn as_xsd_string(&self) -> Option<&str> { + self.0.as_ref().left().map(|l| l.as_str()) + } + + /// Borrow the AnyString as an RdfLangString + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{AnyString, RdfLangString}; + /// # let any_string = AnyString::from_rdf_lang_string(RdfLangString { + /// # value: "hi".into(), + /// # language: "en".into(), + /// # }); + /// # + /// let s_borrow = any_string + /// .as_rdf_lang_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn as_rdf_lang_string(&self) -> Option<&RdfLangString> { + self.0.as_ref().right() + } + + /// Take the AnyString as a String + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::AnyString; + /// # let any_string = AnyString::from_xsd_string("hi"); + /// # + /// let xsd_string = any_string + /// .xsd_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn xsd_string(self) -> Option { + self.0.left() + } + + /// Take the AnyString as an RdfLangString + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{AnyString, RdfLangString}; + /// # let any_string = AnyString::from_rdf_lang_string(RdfLangString { + /// # value: "hi".into(), + /// # language: "en".into(), + /// # }); + /// # + /// let rdf_lang_string = any_string + /// .rdf_lang_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn rdf_lang_string(self) -> Option { + self.0.right() + } + + /// Create a new AnyString from an `Into` + /// + /// ```rust + /// use activitystreams_new::primitives::AnyString; + /// + /// let any_string = AnyString::from_xsd_string("hi"); + /// ``` + pub fn from_xsd_string(string: T) -> Self + where + T: Into, + { + AnyString(Either::Left(string.into())) + } + + /// Create a new AnyString from an RdfLangString + /// + /// ```rust + /// use activitystreams_new::primitives::{AnyString, RdfLangString}; + /// + /// let any_string = AnyString::from_rdf_lang_string(RdfLangString { + /// value: "hi".into(), + /// language: "en".into(), + /// }); + /// ``` + pub fn from_rdf_lang_string(string: T) -> Self + where + T: Into, + { + AnyString(Either::Right(string.into())) + } + + /// Replace the contents of self with a String + /// + /// ```rust + /// use activitystreams_new::primitives::{AnyString, RdfLangString}; + /// + /// let mut any_string = AnyString::from_rdf_lang_string(RdfLangString { + /// value: "hi".into(), + /// language: "en".into(), + /// }); + /// + /// any_string.set_xsd_string("hi"); + /// + /// assert!(any_string.as_xsd_string().is_some()); + /// ``` + pub fn set_xsd_string(&mut self, string: T) + where + T: Into, + { + self.0 = Either::Left(string.into()); + } + + /// Replace the contents of self with an RdfLangString + /// + /// ```rust + /// use activitystreams_new::primitives::{AnyString, RdfLangString}; + /// + /// let mut any_string = AnyString::from_xsd_string("hi"); + /// + /// any_string.set_rdf_lang_string(RdfLangString { + /// value: "hi".into(), + /// language: "en".into(), + /// }); + /// + /// assert!(any_string.as_rdf_lang_string().is_some()); + /// ``` + pub fn set_rdf_lang_string(&mut self, string: T) + where + T: Into, + { + self.0 = Either::Right(string.into()); + } +} + +impl OneOrMany { + /// Try to borrow a single String from the current object + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{OneOrMany, AnyString}; + /// # let string = OneOrMany::::from_xsd_string("Hey"); + /// string + /// .as_single_xsd_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn as_single_xsd_string(&self) -> Option<&str> { + self.as_one() + .and_then(|any_string| any_string.as_xsd_string()) + } + + /// Try to borrow a single RdfLangString from the current object + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{OneOrMany, RdfLangString}; + /// # let string = OneOrMany::from_rdf_lang_string(RdfLangString { + /// # value: "hi".into(), + /// # language: "en".into(), + /// # }); + /// string + /// .as_single_rdf_lang_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn as_single_rdf_lang_string(&self) -> Option<&RdfLangString> { + self.as_one() + .and_then(|any_string| any_string.as_rdf_lang_string()) + } + + /// Try to take a single String from the current object + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{OneOrMany, AnyString}; + /// # let string = OneOrMany::::from_xsd_string("Hey"); + /// string + /// .single_xsd_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn single_xsd_string(self) -> Option { + self.one().and_then(|any_string| any_string.xsd_string()) + } + + /// Try to take a single RdfLangString from the current object + /// + /// ```rust + /// # fn main() -> Result<(), anyhow::Error> { + /// # use activitystreams_new::primitives::{OneOrMany, RdfLangString}; + /// # let string = OneOrMany::from_rdf_lang_string(RdfLangString { + /// # value: "hi".into(), + /// # language: "en".into(), + /// # }); + /// string + /// .single_rdf_lang_string() + /// .ok_or(anyhow::Error::msg("Wrong string type"))?; + /// # Ok(()) + /// # } + /// ``` + pub fn single_rdf_lang_string(self) -> Option { + self.one() + .and_then(|any_string| any_string.rdf_lang_string()) + } + + /// Create the object from a single String + /// + /// ```rust + /// use activitystreams_new::primitives::{OneOrMany, AnyString}; + /// + /// let string = OneOrMany::::from_xsd_string("hi"); + /// ``` + pub fn from_xsd_string(string: T) -> Self + where + T: Into, + { + Self::from_one(AnyString::from_xsd_string(string)) + } + + /// Create the object from a single RdfLangString + /// + /// ```rust + /// use activitystreams_new::primitives::{OneOrMany, RdfLangString}; + /// + /// let string = OneOrMany::from_rdf_lang_string(RdfLangString { + /// value: "hi".into(), + /// language: "en".into(), + /// }); + /// ``` + pub fn from_rdf_lang_string(string: T) -> Self + where + T: Into, + { + Self::from_one(AnyString::from_rdf_lang_string(string)) + } + + /// Add a String to the object, appending to whatever is currently included + /// + /// ```rust + /// use activitystreams_new::primitives::{OneOrMany, AnyString}; + /// + /// let mut string = OneOrMany::::from_xsd_string("Hello"); + /// + /// string + /// .add_xsd_string("Hey") + /// .add_xsd_string("hi"); + /// ``` + pub fn add_xsd_string(&mut self, string: T) -> &mut Self + where + T: Into, + { + self.add(string.into()) + } + + /// Add an RdfLangString to the object, appending to whatever is currently included + /// + /// ```rust + /// use activitystreams_new::primitives::{AnyString, OneOrMany, RdfLangString}; + /// + /// let mut string = OneOrMany::::from_xsd_string("Hello"); + /// + /// string + /// .add_rdf_lang_string(RdfLangString { + /// value: "Hey".into(), + /// language: "en".into(), + /// }) + /// .add_rdf_lang_string(RdfLangString { + /// value: "hi".into(), + /// language: "en".into(), + /// }); + /// ``` + pub fn add_rdf_lang_string(&mut self, string: T) -> &mut Self + where + T: Into, + { + self.add(string.into()) + } +} + +impl From<&str> for AnyString { + fn from(s: &str) -> Self { + AnyString::from_xsd_string(s.to_owned()) + } +} + +impl From for AnyString { + fn from(s: String) -> Self { + AnyString::from_xsd_string(s) + } +} + +impl From for AnyString { + fn from(s: RdfLangString) -> Self { + AnyString::from_rdf_lang_string(s) + } +} + +impl From<&str> for OneOrMany { + fn from(s: &str) -> Self { + OneOrMany::::from_xsd_string(s.to_owned()) + } +} + +impl From for OneOrMany { + fn from(s: String) -> Self { + OneOrMany::::from_xsd_string(s) + } +} + +impl From for OneOrMany { + fn from(s: RdfLangString) -> Self { + OneOrMany::::from_rdf_lang_string(s) + } +} diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs new file mode 100644 index 0000000..37e682d --- /dev/null +++ b/src/primitives/mod.rs @@ -0,0 +1,39 @@ +//! Types creating the base for most ActivityStreams fields. +//! +//! These types are not themselves defined by ActivityStreams, but are referenced by the +//! specification. +//! +//! ```rust +//! use activitystreams_new::primitives::{AnyString, OneOrMany, Unit}; +//! +//! let any_string = AnyString::from_xsd_string("hey"); +//! +//! let one_or_many = OneOrMany::::from_one(1234); +//! +//! let cm = Unit::centimeters(); +//! ``` + +mod any_string; +mod one_or_many; +mod rdf_lang_string; +mod serde_parse; +mod unit; +mod xsd_datetime; +mod xsd_duration; + +pub use self::{ + any_string::AnyString, + one_or_many::OneOrMany, + rdf_lang_string::RdfLangString, + unit::Unit, + xsd_datetime::XsdDateTime, + xsd_duration::{XsdDuration, XsdDurationError}, +}; + +use self::serde_parse::SerdeParse; + +/// An alias for the url::Url struct with serde compatibility +pub type XsdAnyUri = SerdeParse; + +/// An alias for the mime::Mime struct with serde compatibility +pub(crate) type MimeMediaType = SerdeParse; diff --git a/src/primitives/one_or_many.rs b/src/primitives/one_or_many.rs new file mode 100644 index 0000000..3650f8b --- /dev/null +++ b/src/primitives/one_or_many.rs @@ -0,0 +1,292 @@ +use crate::either::Either; + +/// A type representing at least one value +/// +/// When translated to JSON, it can represent the following structures: +/// ```json +/// { +/// "key": value +/// } +/// ``` +/// ```json +/// { +/// "key": [], +/// } +/// ``` +/// ```json +/// { +/// "key": [value, ...] +/// } +/// ``` +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(transparent)] +pub struct OneOrMany(pub(crate) Either>); + +impl OneOrMany { + /// Create a OneOrMany referencing the existing one + /// + /// ```rust + /// use activitystreams_new::primitives::OneOrMany; + /// + /// let value = OneOrMany::from_one(String::from("hi")); + /// let value_ref = value.as_ref(); + /// + /// assert_eq!(value_ref.one(), Some(&String::from("hi"))); + /// ``` + pub fn as_ref(&self) -> OneOrMany<&T> { + OneOrMany(self.0.as_ref().map(|l| l, |r| r.iter().collect())) + } + + /// Map the value inside the OneOrMany from T to U + /// + /// ```rust + /// use activitystreams_new::primitives::OneOrMany; + /// + /// let value = OneOrMany::from_one("Jake from StateFarm"); + /// let new_value = value.map(|s| format!("Hi, {}", s)); + /// + /// assert_eq!(new_value.one(), Some(String::from("Hi, Jake from StateFarm"))); + /// ``` + pub fn map(self, f: F) -> OneOrMany + where + F: Fn(T) -> U + Copy, + { + OneOrMany(self.0.map(f, |v| v.into_iter().map(f).collect())) + } + + /// Create a OneOrMany mutably referencing the existing one + /// + /// ```rust + /// use activitystreams_new::primitives::OneOrMany; + /// + /// let mut value = OneOrMany::from_one(5); + /// let value_mut = value.as_mut(); + /// ``` + pub fn as_mut(&mut self) -> OneOrMany<&mut T> { + OneOrMany(self.0.as_mut().map(|l| l, |r| r.iter_mut().collect())) + } + + /// Get a reference to a single value + /// + /// ```rust + /// # use activitystreams_new::primitives::OneOrMany; + /// # let value = OneOrMany::from_one(1); + /// if let Some(v) = value.as_one() { + /// println!("{:?}", v); + /// } + /// ``` + pub fn as_one(&self) -> Option<&T> { + self.0.as_ref().left() + } + + /// Borrow one as mutable + /// + /// ```rust + /// use activitystreams_new::primitives::OneOrMany; + /// + /// let mut value = OneOrMany::from_one(1); + /// + /// if let Some(i) = value.one_mut() { + /// *i += 1; + /// } + /// + /// assert_eq!(value.one(), Some(2)); + /// ``` + pub fn one_mut(&mut self) -> Option<&mut T> { + self.0.as_mut().left() + } + + /// Take a single value + /// + /// ```rust + /// # use activitystreams_new::primitives::OneOrMany; + /// # let value = OneOrMany::from_one(1); + /// if let Some(v) = value.one() { + /// println!("{:?}", v); + /// } + /// ``` + pub fn one(self) -> Option { + self.0.left() + } + + /// Get a slice of values + /// + /// ```rust + /// # use activitystreams_new::primitives::OneOrMany; + /// # let value = OneOrMany::from_many(vec![1, 2, 3]); + /// if let Some(v) = value.as_many() { + /// for item in v.iter() { + /// println!("{:?}", item); + /// } + /// } + /// ``` + pub fn as_many(&self) -> Option<&[T]> { + self.0.as_ref().right().map(|v| v.as_ref()) + } + + /// Borrow many as mutable + /// + /// ```rust + /// use activitystreams_new::primitives::OneOrMany; + /// + /// let mut value = OneOrMany::from_many(vec![1, 2, 3]); + /// + /// if let Some(v) = value.many_mut() { + /// for i in v.iter_mut() { + /// *i += 3; + /// } + /// } + /// + /// assert_eq!(value.many(), Some(vec![4, 5, 6])); + /// ``` + pub fn many_mut(&mut self) -> Option<&mut [T]> { + self.0.as_mut().right().map(|v| v.as_mut()) + } + + /// Take a Vec of values + /// + /// ```rust + /// # use activitystreams_new::primitives::OneOrMany; + /// # let value = OneOrMany::from_many(vec![1, 2, 3]); + /// if let Some(v) = value.many() { + /// for item in v.into_iter() { + /// println!("{:?}", item); + /// } + /// } + /// ``` + pub fn many(self) -> Option> { + self.0.right() + } + + /// Consume the type, returning a vec + /// + /// ```rust + /// # use activitystreams_new::primitives::OneOrMany; + /// # let value = OneOrMany::from_many(vec![1, 2, 3]); + /// for item in value.unwrap_to_vec() { + /// println!("{:?}", item); + /// } + /// ``` + pub fn unwrap_to_vec(self) -> Vec { + match self.0 { + Either::Left(t) => vec![t], + Either::Right(v) => v, + } + } + + /// Produce a new object from one value + /// + /// ``` + /// use activitystreams_new::primitives::OneOrMany; + /// let v = OneOrMany::from_one(1234); + /// ``` + pub fn from_one(t: T) -> Self { + OneOrMany(Either::Left(t)) + } + + /// Produce a new object from a vec of values + /// + /// ``` + /// use activitystreams_new::primitives::OneOrMany; + /// let v = OneOrMany::from_many(vec![1, 2, 3, 4]); + /// ``` + pub fn from_many(items: Vec) -> Self { + OneOrMany(Either::Right(items)) + } + + /// Overwrite the contents with a single value + /// + /// ``` + /// # use activitystreams_new::primitives::OneOrMany; + /// # let mut value = OneOrMany::from_many(vec![1, 2, 3]); + /// value.set_one(3); + /// + /// assert!(value.as_one().is_some()); + /// ``` + pub fn set_one(&mut self, u: U) -> &mut Self + where + U: Into, + { + self.0 = Either::Left(u.into()); + self + } + + /// Overwrite the contents with vec of values + /// + /// ``` + /// # use activitystreams_new::primitives::OneOrMany; + /// # let mut value = OneOrMany::from_one(1234); + /// value.set_many(vec![1, 2, 3, 4]); + /// + /// assert!(value.as_many().is_some()); + /// ``` + pub fn set_many(&mut self, items: impl IntoIterator) -> &mut Self + where + U: Into, + { + self.0 = Either::Right(items.into_iter().map(Into::into).collect()); + self + } + + /// Add a value to the object + /// + /// This appends the value to the existing vec, or converts the single value into a vec, and + /// then appends the new value + /// + /// ``` + /// use activitystreams_new::primitives::OneOrMany; + /// let mut value = OneOrMany::from_one(1234); + /// value.add(4321); + /// + /// assert!(value.as_many().is_some()); + /// ``` + pub fn add(&mut self, u: U) -> &mut Self + where + U: Into, + { + let mut v = match std::mem::replace(&mut self.0, Either::Right(vec![])) { + Either::Left(one) => vec![one], + Either::Right(v) => v, + }; + v.push(u.into()); + self.0 = Either::Right(v); + self + } + + /// Add many values to the object + /// + /// This appends the values to the existing vec, or converts the single value into a vec, and + /// then appends the new values + /// + /// ``` + /// use activitystreams_new::primitives::OneOrMany; + /// let mut value = OneOrMany::from_one(1234); + /// value.add_many(vec![4321, 2345]); + /// + /// assert!(value.as_many().is_some()); + /// ``` + pub fn add_many(&mut self, items: impl IntoIterator) -> &mut Self + where + U: Into, + { + let mut v = match std::mem::replace(&mut self.0, Either::Right(vec![])) { + Either::Left(one) => vec![one], + Either::Right(v) => v, + }; + v.extend(items.into_iter().map(Into::into)); + self.0 = Either::Right(v); + self + } +} + +impl From for OneOrMany { + fn from(t: T) -> Self { + OneOrMany::from_one(t) + } +} + +impl From> for OneOrMany { + fn from(t: Vec) -> Self { + OneOrMany::from_many(t) + } +} diff --git a/src/primitives/rdf_lang_string.rs b/src/primitives/rdf_lang_string.rs new file mode 100644 index 0000000..bc01d7b --- /dev/null +++ b/src/primitives/rdf_lang_string.rs @@ -0,0 +1,42 @@ +/* + * This file is part of ActivityStreams. + * + * Copyright © 2020 Riley Trautman + * + * ActivityStreams is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ActivityStreams is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ActivityStreams. If not, see . + */ + +/// The rdf.langString type extends xs.string, and represents a language tagged string in RDF. +#[derive( + Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize, +)] +pub struct RdfLangString { + /// The content of the langstring + /// + /// Represented in json as "@value" + #[serde(rename = "@value")] + pub value: String, + + /// The language identifier + /// + /// Represented in json as "@language" + #[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/src/primitives/serde_parse.rs b/src/primitives/serde_parse.rs new file mode 100644 index 0000000..810cde1 --- /dev/null +++ b/src/primitives/serde_parse.rs @@ -0,0 +1,89 @@ +/// A struct that wraps a type implementing FromStr and Display implements Serde's Deserialize and +/// Serialize +#[derive(Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +pub struct SerdeParse(T); + +impl SerdeParse { + /// Extract the inner item from SerdeParse + pub fn into_inner(self) -> T { + self.0 + } +} + +impl std::ops::Deref for SerdeParse { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for SerdeParse { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl AsRef for SerdeParse { + fn as_ref(&self) -> &T { + &self.0 + } +} + +impl AsMut for SerdeParse { + fn as_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl std::str::FromStr for SerdeParse +where + T: std::str::FromStr, +{ + type Err = ::Err; + + fn from_str(s: &str) -> Result { + T::from_str(s).map(SerdeParse) + } +} + +impl std::fmt::Display for SerdeParse +where + T: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl serde::ser::Serialize for SerdeParse +where + T: std::fmt::Display, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + self.0.to_string().serialize(serializer) + } +} + +impl<'de, T> serde::de::Deserialize<'de> for SerdeParse +where + T: std::str::FromStr, + T::Err: std::fmt::Display, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} + +impl From for SerdeParse { + fn from(t: T) -> Self { + SerdeParse(t) + } +} diff --git a/src/primitives/unit.rs b/src/primitives/unit.rs new file mode 100644 index 0000000..cf365ce --- /dev/null +++ b/src/primitives/unit.rs @@ -0,0 +1,342 @@ +use crate::either::Either; + +/// A type representing units of length +/// +/// It can be any of the following +/// - Centimeters +/// - Meters +/// - Kilometers +/// - Inches +/// - Feet +/// - A custom value +#[derive( + Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize, +)] +#[serde(transparent)] +pub struct Unit(Either); + +impl Unit { + /// Create a new unit measuring Centimeters + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::centimeters(); + /// ``` + pub fn centimeters() -> Self { + Unit(Either::Left(Length::Centimeters)) + } + + /// Check if the unit is Centimeters + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::centimeters().is_centimeters()); + /// ``` + pub fn is_centimeters(&self) -> bool { + self.0 + .as_ref() + .left() + .map(|l| l.is_centimeters()) + .unwrap_or(false) + } + + /// Create a new unit measuring Meters + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::meters(); + /// ``` + pub fn meters() -> Self { + Unit(Either::Left(Length::Meters)) + } + + /// Check if the unit is Meters + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::meters().is_meters()); + /// ``` + pub fn is_meters(&self) -> bool { + self.0 + .as_ref() + .left() + .map(|l| l.is_meters()) + .unwrap_or(false) + } + + /// Create a new unit measuring Kilometers + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::kilometers(); + /// ``` + pub fn kilometers() -> Self { + Unit(Either::Left(Length::Kilometers)) + } + + /// Check if the unit is Kilometers + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::kilometers().is_kilometers()); + /// ``` + pub fn is_kilometers(&self) -> bool { + self.0 + .as_ref() + .left() + .map(|l| l.is_kilometers()) + .unwrap_or(false) + } + + /// Create a new unit measuring Feet + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::feet(); + /// ``` + pub fn feet() -> Self { + Unit(Either::Left(Length::Feet)) + } + + /// Check if the unit is Feet + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::feet().is_feet()); + /// ``` + pub fn is_feet(&self) -> bool { + self.0.as_ref().left().map(|l| l.is_feet()).unwrap_or(false) + } + + /// Create a new unit measuring Inches + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::inches(); + /// ``` + pub fn inches() -> Self { + Unit(Either::Left(Length::Inches)) + } + + /// Check if the unit is Inches + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::inches().is_inches()); + /// ``` + pub fn is_inches(&self) -> bool { + self.0 + .as_ref() + .left() + .map(|l| l.is_inches()) + .unwrap_or(false) + } + + /// Create a new custom unit + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// Unit::custom("Yards"); + /// ``` + pub fn custom(string: T) -> Self + where + T: Into, + { + Unit(Either::Right(string.into())) + } + + /// Check if a unit is custom + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::custom("Yards").is_custom()); + /// ``` + pub fn is_custom(&self) -> bool { + self.as_custom().is_some() + } + + /// Fetch a custom unit + /// + /// ```rust + /// use activitystreams_new::primitives::Unit; + /// + /// assert!(Unit::custom("Yards").as_custom() == Some("Yards")); + /// ``` + pub fn as_custom(&self) -> Option<&str> { + self.0.as_ref().right().map(|r| r.as_str()) + } +} + +impl Default for Unit { + fn default() -> Self { + Self::meters() + } +} + +impl std::str::FromStr for Unit { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result { + let unit = match s { + "cm" => Self::centimeters(), + "m" => Self::meters(), + "km" => Self::kilometers(), + "inches" => Self::inches(), + "feet" => Self::feet(), + other => Self::custom(other), + }; + + Ok(unit) + } +} + +impl From for Unit { + fn from(s: String) -> Self { + match s.parse() { + Ok(u) => u, + Err(e) => match e {}, + } + } +} + +impl From<&str> for Unit { + fn from(s: &str) -> Self { + match s.parse() { + Ok(u) => u, + Err(e) => match e {}, + } + } +} + +/// A list of units of length that represent valid units for certain ActivityStreams objects +#[derive( + Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize, +)] +#[serde(untagged)] +enum Length { + #[serde(rename = "cm")] + Centimeters, + + #[serde(rename = "feet")] + Feet, + + #[serde(rename = "inches")] + Inches, + + #[serde(rename = "km")] + Kilometers, + + #[serde(rename = "m")] + Meters, +} + +#[derive(Clone, Debug, thiserror::Error)] +#[error("Could not parse units")] +/// The error type produced when a Length cannot be parsed +struct LengthError; + +impl Length { + fn is_centimeters(&self) -> bool { + match self { + Length::Centimeters => true, + _ => false, + } + } + + fn is_feet(&self) -> bool { + match self { + Length::Feet => true, + _ => false, + } + } + + fn is_inches(&self) -> bool { + match self { + Length::Inches => true, + _ => false, + } + } + + fn is_kilometers(&self) -> bool { + match self { + Length::Kilometers => true, + _ => false, + } + } + + fn is_meters(&self) -> bool { + match self { + Length::Meters => true, + _ => false, + } + } +} + +impl Default for Length { + fn default() -> Self { + Length::Meters + } +} + +impl std::str::FromStr for Length { + type Err = LengthError; + + fn from_str(s: &str) -> Result { + match s { + "cm" => Ok(Length::Centimeters), + "feet" => Ok(Length::Feet), + "inches" => Ok(Length::Inches), + "km" => Ok(Length::Kilometers), + "m" => Ok(Length::Meters), + _ => Err(LengthError), + } + } +} + +impl std::convert::TryFrom<&str> for Length { + type Error = LengthError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for Length { + type Error = LengthError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom for Length { + type Error = LengthError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::fmt::Display for Length { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Length::Centimeters => write!(f, "cm"), + Length::Feet => write!(f, "feet"), + Length::Inches => write!(f, "inches"), + Length::Kilometers => write!(f, "km"), + Length::Meters => write!(f, "meters"), + } + } +} diff --git a/src/primitives/xsd_datetime.rs b/src/primitives/xsd_datetime.rs new file mode 100644 index 0000000..bea151a --- /dev/null +++ b/src/primitives/xsd_datetime.rs @@ -0,0 +1,138 @@ +/* + * This file is part of ActivityStreams. + * + * Copyright © 2020 Riley Trautman + * + * ActivityStreams is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ActivityStreams is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ActivityStreams. If not, see . + */ + +/// The type xsd:dateTime represents a specific date and time in the format +/// CCYY-MM-DDThh:mm:ss.sss, which is a concatenation of the date and time forms, separated by a +/// literal letter "T". +/// +/// All of the same rules that apply to the date and time types are applicable +/// to xsd:dateTime as well. +/// +/// An optional time zone expression may be added at the end of the value. The letter Z is used to +/// indicate Coordinated Universal Time (UTC). All other time zones are represented by their +/// difference from Coordinated Universal Time in the format +hh:mm, or -hh:mm. These values may +/// range from -14:00 to 14:00. For example, US Eastern Standard Time, which is five hours behind +/// UTC, is represented as -05:00. If no time zone value is present, it is considered unknown; it +/// is not assumed to be UTC. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct XsdDateTime(pub chrono::DateTime); + +impl XsdDateTime { + /// Create a XsdDateTime from a chrono::DateTime + pub fn new(d: chrono::DateTime) -> Self { + XsdDateTime(d) + } + + /// Extract the chrono::DateTime from XsdDateTime + pub fn into_inner(self) -> chrono::DateTime { + self.0 + } + + /// Borrow the underlying `chrono::DateTime` + pub fn as_datetime(&self) -> &chrono::DateTime { + self.as_ref() + } + + /// Mutably borrow the underlying `chrono::DateTime` + pub fn as_datetime_mut(&mut self) -> &mut chrono::DateTime { + self.as_mut() + } +} + +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 = chrono::format::ParseError; + + fn try_from(s: String) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&str> for XsdDateTime { + type Error = chrono::format::ParseError; + + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl std::convert::TryFrom<&mut str> for XsdDateTime { + type Error = chrono::format::ParseError; + + fn try_from(s: &mut str) -> Result { + s.parse() + } +} + +impl std::str::FromStr for XsdDateTime { + type Err = chrono::format::ParseError; + + fn from_str(s: &str) -> Result { + Ok(XsdDateTime(chrono::DateTime::parse_from_rfc3339(s)?)) + } +} + +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/src/primitives/xsd_duration.rs b/src/primitives/xsd_duration.rs new file mode 100644 index 0000000..fc9ae4f --- /dev/null +++ b/src/primitives/xsd_duration.rs @@ -0,0 +1,240 @@ +/* + * This file is part of ActivityStreams. + * + * Copyright © 2020 Riley Trautman + * + * ActivityStreams is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ActivityStreams is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ActivityStreams. If not, see . + */ + +/// The type xsd:duration represents a duration of time expressed as a number of years, months, +/// days, hours, minutes, and seconds. +/// +/// The format of xsd:duration is PnYnMnDTnHnMnS, where P is a literal value that starts the +/// expression, nY is the number of years followed by a literal Y, nM is the number of months +/// followed by a literal M, nD is the number of days followed by a literal D, T is a literal value +/// that separates the date and time, nH is the number of hours followed by a literal H, nM is the +/// number of minutes followed by a literal M, and nS is the number of seconds followed by a +/// literal S. The following rules apply to xsd:duration values: +/// +/// - Any of these numbers and corresponding designators may be absent if they are equal to 0, but +/// at least one number and designator must appear. +/// - The numbers may be any unsigned integer, with the exception of the number of seconds, which +/// may be an unsigned decimal number. +/// - If a decimal point appears in the number of seconds, there must be at least one digit after +/// the decimal point. +/// - A minus sign may appear before the P to specify a negative duration. +/// - If no time items (hour, minute, second) are present, the letter T must not appear. +/// +/// ### Note +/// +/// This implementation converts Months to Days by multiplying by 31, and converts Years to days by +/// multiplying by 365. If this is an issue for your application, look into specifying days +/// directly. +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct XsdDuration(pub chrono::Duration); + +/// The error type produced when an XsdDuration cannot be parsed +#[derive(Clone, Debug, thiserror::Error)] +#[error("Error parsing Duration")] +pub struct XsdDurationError; + +impl XsdDuration { + /// Create a new XsdDuration from a chrono::Duration + pub fn new(duration: chrono::Duration) -> Self { + XsdDuration(duration) + } + + /// Extract the chrono::Duration from an XsdDuration + pub fn into_inner(self) -> chrono::Duration { + self.0 + } + + /// Borrow the underlying `chrono::Duration` + pub fn as_duration(&self) -> &chrono::Duration { + self.as_ref() + } + + /// Mutably borrow the underlying `chrono::Duration` + pub fn as_duration_mut(&mut self) -> &mut chrono::Duration { + self.as_mut() + } +} + +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 { + ("P-".to_string(), self.0 * -1) + } else { + ("P".to_string(), 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/src/unparsed.rs b/src/unparsed.rs index f8922ab..76e42c6 100644 --- a/src/unparsed.rs +++ b/src/unparsed.rs @@ -23,11 +23,12 @@ //! prelude::*, //! primitives::*, //! unparsed::*, +//! url::Url, //! }; //! //! /// First, we'll define our public key types //! -//! #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +//! #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] //! #[serde(rename_all = "camelCase")] //! pub struct PublicKeyValues { //! pub id: XsdAnyUri, @@ -167,30 +168,30 @@ //! /// And now create helper methods //! pub trait PublicKeyExt: AsPublicKey { //! /// Borrow the public key's ID -//! fn key_id<'a>(&'a self) -> &'a XsdAnyUri +//! fn key_id<'a>(&'a self) -> &'a Url //! where //! Inner: 'a, //! { -//! &self.public_key_ref().public_key.id +//! &self.public_key_ref().public_key.id.as_ref() //! } //! //! /// Set the public key's ID -//! fn set_key_id(&mut self, id: XsdAnyUri) -> &mut Self { -//! self.public_key_mut().public_key.id = id; +//! fn set_key_id(&mut self, id: Url) -> &mut Self { +//! self.public_key_mut().public_key.id = id.into(); //! self //! } //! //! /// Borrow the public key's Owner -//! fn key_owner<'a>(&'a self) -> &'a XsdAnyUri +//! fn key_owner<'a>(&'a self) -> &'a Url //! where //! Inner: 'a, //! { -//! &self.public_key_ref().public_key.owner +//! &self.public_key_ref().public_key.owner.as_ref() //! } //! //! /// Set the public key's Owner -//! fn set_key_owner(&mut self, owner: XsdAnyUri) -> &mut Self { -//! self.public_key_mut().public_key.owner = owner; +//! fn set_key_owner(&mut self, owner: Url) -> &mut Self { +//! self.public_key_mut().public_key.owner = owner.into(); //! self //! } //! @@ -220,24 +221,33 @@ //! //! //! /// Now that eveything is implemented, we can use it like so: -//! use activitystreams_new::actor::{kind::PersonType, Person}; +//! use activitystreams_new::{actor::{kind::PersonType, Person}, uri}; //! //! pub type ExtendedPerson = PublicKey>; //! //! impl ExtendedPerson { -//! pub fn new(inbox: XsdAnyUri) -> Self { +//! pub fn new(inbox: Url, mut owner: Url) -> Self { +//! let id = owner.clone(); +//! owner.set_fragment(Some("main-key")); //! PublicKey { -//! public_key: Default::default(), +//! public_key: PublicKeyValues { +//! id: id.into(), +//! owner: owner.into(), +//! public_key_pem: String::new(), +//! }, //! inner: ApActor::new(inbox, Person::new()), //! } //! } //! } //! //! fn main() -> Result<(), anyhow::Error> { -//! let mut extended_person = ExtendedPerson::new("https://example.com/user/inbox".parse()?); +//! let mut extended_person = ExtendedPerson::new( +//! uri!("https://example.com/user/inbox"), +//! uri!("https://example.com/user"), +//! ); //! //! extended_person -//! .set_kind(PersonType) +//! .set_kind(PersonType::Person) //! .set_id("https://example.com/user".parse()?) //! .set_name("Demo User") //! .set_preferred_username("user")