//! Types and traits for dealing with the base attributes common to all ActivityStreams Objects and //! Links //! //! ```rust //! # fn main() -> Result<(), anyhow::Error> { //! use activitystreams::{ //! context, //! object::Video, //! prelude::*, //! security, //! iri, //! }; //! let mut video = Video::new(); //! //! video //! .set_id(iri!("https://example.com")) //! .set_context(context()) //! .add_context(security()) //! .set_name("Hello"); //! //! let any_base = video.into_any_base()?; //! //! let mut new_video = Video::new(); //! //! new_video.set_preview(any_base); //! # //! # Ok(()) //! # } //! ``` use crate::{ checked::{check, CheckError}, markers, primitives::{AnyString, Either, MimeMediaType, OneOrMany}, unparsed::{Unparsed, UnparsedMut}, }; use iri_string::types::{IriStr, IriString}; use mime::Mime; /// Implements conversion between `Base` and other ActivityStreams objects defined in this /// crate pub trait Extends: Sized { type Kind; /// The erro produced must be a StdError type Error: std::error::Error; /// Produce an object from the Base fn extends(base: Base) -> Result; /// Produce a base from the object fn retracts(self) -> Result, Self::Error>; } /// A helper function implemented for all Extends types to easily produce an AnyBase from a given /// object. /// /// This is important because many APIs in this crate deal with AnyBases. pub trait ExtendsExt: Extends { /// Create an AnyBase from the given object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::{object::Video, prelude::*}; /// let video = Video::new(); /// /// let any_base = video.into_any_base()?; /// # /// # Ok(()) /// # } /// ``` fn into_any_base(self) -> Result where Self::Kind: serde::ser::Serialize, Self::Error: From, { AnyBase::from_extended(self) } /// Create an object from an AnyBase /// /// Before calling this, make sure the `AnyBase::is_base()` and `AnyBase::kind` match your /// expectations /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::{object::Video, prelude::*}; /// # let video = Video::new(); /// # let any_base = video.into_any_base()?; /// let video = Video::from_any_base(any_base)?; /// # /// # Ok(()) /// # } /// ``` fn from_any_base(any_base: AnyBase) -> Result, Self::Error> where Self::Kind: serde::de::DeserializeOwned, Self::Error: From, { if let Some(base) = any_base.take_base() { let my_base = base.solidify()?; let extended = my_base.extend::()?; return Ok(Some(extended)); } Ok(None) } } /// Implementation trait for deriving Base methods for a type /// /// Any type implementating AsBase will automatically gain methods provided by BaseExt pub trait AsBase: markers::Base { type Kind; /// Immutable borrow of `Base` fn base_ref(&self) -> &Base; /// Mutable borrow of Base fn base_mut(&mut self) -> &mut Base; } /// Helper methods for interacting with Base types /// /// This trait represents methods valid for Any ActivityStreams type, regardless of whether it's a /// Link or an Object. /// /// Documentation for the fields related to these methods can be found on the `Base` struct pub trait BaseExt: AsBase { /// Fetch the context for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// let video_context = video.context(); /// ``` fn context<'a>(&'a self) -> Option<&'a OneOrMany> where Self::Kind: 'a, { self.base_ref().context.as_ref() } /// Set the context for the current object /// /// This overwrites the contents of context /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::{context, prelude::*}; /// /// video.set_context(context()); /// ``` fn set_context(&mut self, context: T) -> &mut Self where T: Into, { self.base_mut().context = Some(context.into().into()); self } /// Set many contexts for the current object /// /// This overwrites the contents of context /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::{context, prelude::*, security}; /// /// video.set_many_contexts(vec![context(), security()]); /// ``` fn set_many_contexts(&mut self, items: I) -> &mut Self where I: IntoIterator, T: Into, { let v: Vec<_> = items.into_iter().map(Into::into).collect(); self.base_mut().context = Some(v.into()); self } /// Add a context to the current object /// /// This does not overwrite the contents of context, only appends a new item /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::{context, prelude::*, security}; /// /// video /// .add_context(context()) /// .add_context(security()); /// ``` fn add_context(&mut self, context: T) -> &mut Self where T: Into, { let c = match self.base_mut().context.take() { Some(mut c) => { c.add(context.into()); c } None => vec![context.into()].into(), }; self.base_mut().context = Some(c); self } /// Take the context from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(context) = video.take_context() { /// println!("{:?}", context); /// } /// ``` fn take_context(&mut self) -> Option> { self.base_mut().context.take() } /// Delete the context from the current object /// /// ```rust /// # use activitystreams::{context, object::Video}; /// # let mut video = Video::new(); /// # video.set_context(context()); /// # /// use activitystreams::prelude::*; /// /// assert!(video.context().is_some()); /// video.delete_context(); /// assert!(video.context().is_none()); /// ``` fn delete_context(&mut self) -> &mut Self { self.base_mut().context = None; self } /// Check the authority of a given IRI matches this object's ID /// /// ```rust /// # use activitystreams::{base::BaseExt, object::Video, iri}; /// # fn main() -> anyhow::Result<()> { /// # let video = Video::new(); /// let res = video.check_authority(&iri!("https://example.com")); /// # Ok(()) /// # } /// ``` fn check_authority>(&self, iri: T) -> Result { let authority = self .id_unchecked() .and_then(|id| id.authority_components()) .ok_or_else(|| CheckError(Some(iri.as_ref().to_owned())))?; check(iri, authority.host(), authority.port()) } /// Fetch the id for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Ok(Some(id)) = video.id("exmaple.com", Some("443")) { /// println!("{:?}", id); /// } /// ``` fn id<'a>(&'a self, host: &str, port: Option<&str>) -> Result, CheckError> where Self::Kind: 'a, { self.id_unchecked() .and_then(|id| { let authority = id.authority_components()?; if authority.host() == host && authority.port() == port { Some(Ok(id)) } else { Some(Err(CheckError(Some(id.clone())))) } }) .transpose() } /// Fetch the id for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(id) = video.id_unchecked() { /// println!("{:?}", id); /// } /// ``` fn id_unchecked<'a>(&'a self) -> Option<&'a IriString> where Self::Kind: 'a, { self.base_ref().id.as_ref() } /// Mutably borrow the ID from the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Ok(Some(id)) = video.id_mut("example.com", Some("443")) { /// println!("{:?}", id); /// } /// ``` fn id_mut<'a>( &'a mut self, host: &str, port: Option<&str>, ) -> Result, CheckError> where Self::Kind: 'a, { self.id_mut_unchecked() .and_then(|id| { let authority = id.authority_components()?; if authority.host() == host && authority.port() == port { Some(Ok(id)) } else { Some(Err(CheckError(Some(id.clone())))) } }) .transpose() } /// Mutably borrow the ID from the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(id) = video.id_mut_unchecked() { /// println!("{:?}", id); /// } /// ``` fn id_mut_unchecked<'a>(&'a mut self) -> Option<&'a mut IriString> where Self::Kind: 'a, { self.base_mut().id.as_mut() } /// Check if the provided id is equal to the object's id /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::{object::Video, prelude::*, iri}; /// /// let video: Video = serde_json::from_str(r#"{"type":"Video","id":"https://example.com"}"#)?; /// /// assert!(video.is_id(&iri!("https://example.com"))); /// # Ok(()) /// # } /// ``` fn is_id(&self, id: &IriString) -> bool { self.id_unchecked() == Some(id) } /// Set the id for the current object /// /// This overwrites the contents of id /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::{prelude::*, iri}; /// /// video.set_id(iri!("https://example.com")); /// # Ok(()) /// # } /// ``` fn set_id(&mut self, id: IriString) -> &mut Self { self.base_mut().id = Some(id); self } /// Take the id from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(id) = video.take_id() { /// println!("{:?}", id); /// } /// ``` fn take_id(&mut self) -> Option { self.base_mut().id.take() } /// Delete the id from the current object /// /// ```rust /// # use activitystreams::{context, object::Video}; /// # let mut video = Video::new(); /// # video.set_id(context()); /// # /// use activitystreams::prelude::*; /// /// assert!(video.id_unchecked().is_some()); /// video.delete_id(); /// assert!(video.id_unchecked().is_none()); /// ``` fn delete_id(&mut self) -> &mut Self { self.base_mut().id = None; self } /// Fetch the kind for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(kind) = video.kind() { /// println!("{:?}", kind); /// } /// ``` fn kind<'a>(&'a self) -> Option<&'a Self::Kind> where Self::Kind: 'a, { self.base_ref().kind.as_ref() } /// Check if the provided Kind is equal to the object's Kind /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::{base::Base, prelude::*}; /// /// #[derive(PartialEq, serde::Deserialize)] /// pub enum ValidKinds { /// Video, /// Image, /// } /// /// let video: Base = serde_json::from_str(r#"{"type":"Video"}"#)?; /// /// assert!(video.is_kind(&ValidKinds::Video)); /// # Ok(()) /// # } /// ``` fn is_kind(&self, kind: &Self::Kind) -> bool where Self::Kind: PartialEq, { self.kind() == Some(kind) } /// Set the kind for the current object /// /// This overwrites the contents of kind /// /// ```rust /// # use activitystreams::object::{Video, kind::VideoType}; /// # let mut video = Video::new(); /// use activitystreams::prelude::*; /// /// video.set_kind(VideoType::Video); /// ``` fn set_kind(&mut self, kind: Self::Kind) -> &mut Self { self.base_mut().kind = Some(kind); self } /// Take the kind from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(kind) = video.take_kind() { /// println!("{:?}", kind); /// } /// ``` fn take_kind(&mut self) -> Option { self.base_mut().kind.take() } /// Delete the kind from the current object /// /// ```rust /// # use activitystreams::{object::{Video, kind::VideoType}}; /// # let mut video = Video::new(); /// # video.set_kind(VideoType::Video); /// # /// use activitystreams::prelude::*; /// /// assert!(video.kind().is_some()); /// video.delete_kind(); /// assert!(video.kind().is_none()); /// ``` fn delete_kind(&mut self) -> &mut Self { self.base_mut().kind = None; self } /// Fetch the name for the current object /// /// ``` /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(name) = video.name() { /// println!("{:?}", name); /// } /// ``` fn name<'a>(&'a self) -> Option> where Self::Kind: 'a, { self.base_ref().name.as_ref().map(|o| o.as_ref()) } /// Set the name for the current object /// /// This overwrites the contents of name /// /// ```rust /// use activitystreams::prelude::*; /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// video.set_name("hi"); /// ``` fn set_name(&mut self, name: T) -> &mut Self where T: Into, { self.base_mut().name = Some(name.into().into()); self } /// Set many names for the current object /// /// This overwrites the contents of name /// /// ```rust /// use activitystreams::prelude::*; /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// video.set_many_names(vec!["hi", "hey"]); /// ``` fn set_many_names(&mut self, items: I) -> &mut Self where I: IntoIterator, T: Into, { let v: Vec<_> = items.into_iter().map(Into::into).collect(); self.base_mut().name = Some(v.into()); self } /// Add a name to the current object /// /// This does not overwrite the contents of name, only appends a new item /// /// ```rust /// use activitystreams::prelude::*; /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// video /// .add_name("hi") /// .add_name("hey"); /// ``` fn add_name(&mut self, name: T) -> &mut Self where T: Into, { let a = match self.base_mut().name.take() { Some(mut a) => { a.add(name.into()); a } None => vec![name.into()].into(), }; self.base_mut().name = Some(a); self } /// Take the name from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(name) = video.take_name() { /// println!("{:?}", name); /// } /// ``` fn take_name(&mut self) -> Option> { self.base_mut().name.take() } /// Delete the name from the current object /// /// ```rust /// use activitystreams::prelude::*; /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # video.set_name("hi"); /// # /// /// assert!(video.name().is_some()); /// video.delete_name(); /// assert!(video.name().is_none()); /// ``` fn delete_name(&mut self) -> &mut Self { self.base_mut().name = None; self } /// Fetch the media type for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(media_type) = video.media_type() { /// println!("{:?}", media_type); /// } /// ``` fn media_type<'a>(&'a self) -> Option<&'a Mime> where Self::Kind: 'a, { self.base_ref().media_type.as_ref().map(|m| m.as_ref()) } /// Set the media type for the current object /// /// This overwrites the contents of media_type /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::prelude::*; /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// /// video.set_media_type("video/webm".parse()?); /// # Ok(()) /// # } /// ``` fn set_media_type(&mut self, media_type: Mime) -> &mut Self { self.base_mut().media_type = Some(media_type.into()); self } /// Take the media type from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(media_type) = video.take_media_type() { /// println!("{:?}", media_type); /// } /// ``` 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 /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// # use activitystreams::{object::Video}; /// # let mut video = Video::new(); /// # video.set_media_type("video/webm".parse()?); /// # /// use activitystreams::prelude::*; /// /// assert!(video.media_type().is_some()); /// video.delete_media_type(); /// assert!(video.media_type().is_none()); /// # Ok(()) /// # } /// ``` fn delete_media_type(&mut self) -> &mut Self { self.base_mut().media_type = None; self } /// Fetch the preview for the current object /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(preview) = video.preview() { /// println!("{:?}", preview); /// } /// ``` fn preview<'a>(&'a self) -> Option> where Self::Kind: 'a, { self.base_ref().preview.as_ref().map(|o| o.as_ref()) } /// Set the preview for the current object /// /// This overwrites the contents of preview /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::prelude::*; /// # use activitystreams::{object::Video, iri}; /// # let mut video = Video::new(); /// /// video.set_preview(iri!("https://example.com")); /// # Ok(()) /// # } /// ``` fn set_preview(&mut self, preview: T) -> &mut Self where T: Into, { self.base_mut().preview = Some(preview.into().into()); self } /// Set many previews for the current object /// /// This overwrites the contents of preview /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::prelude::*; /// # use activitystreams::{object::Video, iri}; /// # let mut video = Video::new(); /// /// video.set_many_previews(vec![ /// iri!("https://example.com/one"), /// iri!("https://example.com/two"), /// ]); /// # Ok(()) /// # } /// ``` fn set_many_previews(&mut self, items: I) -> &mut Self where I: IntoIterator, T: Into, { let v: Vec<_> = items.into_iter().map(Into::into).collect(); self.base_mut().preview = Some(v.into()); self } /// Add a preview to the current object /// /// This does not overwrite the contents of preview, only appends an item /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::prelude::*; /// # use activitystreams::{object::Video, iri}; /// # let mut video = Video::new(); /// /// video /// .add_preview(iri!("https://example.com/one")) /// .add_preview(iri!("https://example.com/two")); /// # Ok(()) /// # } /// ``` fn add_preview(&mut self, preview: T) -> &mut Self where T: Into, { let a = match self.base_mut().preview.take() { Some(mut a) => { a.add(preview.into()); a } None => vec![preview.into()].into(), }; self.base_mut().preview = Some(a); self } /// Take the preview from the current object, leaving nothing /// /// ```rust /// # use activitystreams::object::Video; /// # let mut video = Video::new(); /// # /// use activitystreams::prelude::*; /// /// if let Some(preview) = video.take_preview() { /// println!("{:?}", preview); /// } /// ``` fn take_preview(&mut self) -> Option> { self.base_mut().preview.take() } /// Delete the preview from the current object /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// # use activitystreams::{object::Video, iri}; /// # let mut video = Video::new(); /// # video.set_preview(iri!("https://example.com")); /// # /// use activitystreams::prelude::*; /// /// assert!(video.preview().is_some()); /// video.delete_preview(); /// assert!(video.preview().is_none()); /// # Ok(()) /// # } /// ``` fn delete_preview(&mut self) -> &mut Self { self.base_mut().preview = None; self } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(transparent)] struct IdOrBase(Either>>); /// A type that can represent Any ActivityStreams type /// /// A type in activitystreams can be four things /// - An Object /// - A Link /// - The ID of that Link or Object /// - A string representing that Link or Object #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(transparent)] pub struct AnyBase(Either); /// A representation of the common fields between Links and Objects in ActivityStreams /// /// 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)] #[serde(rename_all = "camelCase")] pub struct Base { /// Identifies the context within which the object exists or an activity was performed. /// /// The notion of "context"used is intentionally vague. The intended function is to serve as a /// means of grouping objects and activities that share a common originating context or /// purpose. An example could be all activities relating to a common project or event. /// /// - Range: Object | Link /// - Functional: false #[serde(rename = "@context")] #[serde(skip_serializing_if = "Option::is_none")] context: Option>, /// Provides the globally unique identifier for an Object or Link. /// /// The id property is expressed as an absolute IRI in the spec, but for now is represented as /// a string. /// /// - Range: xsd:anyUri /// - Functional: true /// /// ### From ActivityStreams's deprecation notes /// /// 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")] id: Option, /// The `type` field /// /// ### From JSON-LD's `type` documentation /// /// This section is non-normative. /// /// In Linked Data, it is common to specify the type of a graph node; in many cases, this can /// be inferred based on the properties used within a given node object, or the property for /// which a node is a value. For example, in the schema.org vocabulary, the givenName property /// is associated with a Person. Therefore, one may reason that if a node object contains the /// property givenName, that the type is a Person; making this explicit with @type helps to /// clarify the association. /// /// The type of a particular node can be specified using the @type keyword. In Linked Data, /// types are uniquely identified with an IRI. /// /// ### From ActivityStreams's deprecation notes /// /// When processing Activity Streams 1.0 documents and converting those to 2.0, implementations /// ought to treat [...] the objectType and verb properties as aliases for the JSON-LD @type /// keyword. #[serde(rename = "type")] #[serde(alias = "@type")] #[serde(alias = "objectType")] #[serde(alias = "verb")] #[serde(skip_serializing_if = "Option::is_none")] kind: Option, /// A simple, human-readable, plain-text name for the object. /// /// HTML markup MUST NOT be included. The name MAY be expressed using multiple language-tagged values. /// /// - Range: xsd:string | rdf:langString /// - Functional: false #[serde(skip_serializing_if = "Option::is_none")] name: Option>, /// When used on an Object, identifies the MIME media type of the value of the content property. /// /// If not specified, the content property is assumed to contain text/html content. /// /// - Range: Mime Media Type /// - Functional: true #[serde(skip_serializing_if = "Option::is_none")] 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")] 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)] unparsed: Unparsed, } impl Base { /// Convert this `Base` into a `Base` /// /// This is required before extending Base into the other types found in this crate pub fn solidify(self) -> Result, serde_json::Error> where Kind: serde::de::DeserializeOwned, { self.try_map_kind(serde_json::from_value) } } impl Base { /// Create a new Base /// /// ```rust /// use activitystreams::base::Base; /// /// let base = Base::::new(); /// ``` pub fn new() -> Self where Kind: Default, { Base { context: None, id: None, kind: Some(Kind::default()), name: None, media_type: None, preview: None, unparsed: Default::default(), } } /// Create a new base with `None` for it's `kind` property /// /// This means that no `type` field will be present in serialized JSON /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// use activitystreams::base::Base; /// /// let base = Base::<()>::new_none_type(); /// /// let s = serde_json::to_string(&base)?; /// /// assert_eq!(s, "{}"); /// # Ok(()) /// # } /// ``` pub fn new_none_type() -> Self { Base { context: None, id: None, kind: None, name: None, media_type: None, preview: None, unparsed: Default::default(), } } /// Extend the Base into any other ActivityStreams type provided in this crate /// /// ```rust /// # fn main() -> Result<(), anyhow::Error> { /// # use activitystreams::{base::Base, object::{Video, kind::VideoType}}; /// # let base = Base::::new(); /// # /// let video = base.extend::