From 62c8d245ffcbbc9eeeedd7ab960232aff437f56c Mon Sep 17 00:00:00 2001 From: Aode Date: Mon, 9 Mar 2020 12:56:59 -0500 Subject: [PATCH] Bring in new properties derive --- Cargo.toml | 4 +- activitystreams-derive/Cargo.toml | 4 +- activitystreams-derive/src/lib.rs | 1188 ++++++++++------- activitystreams-traits/Cargo.toml | 9 +- activitystreams-traits/src/error.rs | 40 - activitystreams-traits/src/lib.rs | 19 +- activitystreams-traits/src/link.rs | 9 +- activitystreams-traits/src/object.rs | 9 +- activitystreams-traits/src/properties.rs | 89 -- activitystreams-types/Cargo.toml | 8 +- activitystreams-types/src/link/mod.rs | 44 + activitystreams-types/src/object/mod.rs | 64 +- .../src/object/properties.rs | 872 +++++++----- 13 files changed, 1361 insertions(+), 998 deletions(-) delete mode 100644 activitystreams-traits/src/error.rs delete mode 100644 activitystreams-traits/src/properties.rs diff --git a/Cargo.toml b/Cargo.toml index 3eea3bf..8d6345b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["activitystreams", "activitypub"] edition = "2018" [dependencies] -activitystreams-traits = { version = "0.2", path = "activitystreams-traits" } +activitystreams-traits = { version = "0.3", path = "activitystreams-traits" } activitystreams-types = { version = "0.3.0", path = "activitystreams-types" } [dev-dependencies] @@ -18,7 +18,7 @@ anyhow = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -activitystreams-derive = { version = "0.2", path = "activitystreams-derive" } +activitystreams-derive = { version = "0.3", path = "activitystreams-derive" } [workspace] members = [ diff --git a/activitystreams-derive/Cargo.toml b/activitystreams-derive/Cargo.toml index a07873c..1e52f69 100644 --- a/activitystreams-derive/Cargo.toml +++ b/activitystreams-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "activitystreams-derive" description = "Derive macros for activitystreams" -version = "0.2.0" +version = "0.3.0" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/activitystreams" @@ -15,7 +15,7 @@ syn = "1.0" proc-macro2 = "1.0" [dev-dependencies] -activitystreams-traits = { version = "0.2", path = "../activitystreams-traits" } +activitystreams-traits = { version = "0.3", path = "../activitystreams-traits" } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" diff --git a/activitystreams-derive/src/lib.rs b/activitystreams-derive/src/lib.rs index 17eeef5..345a471 100644 --- a/activitystreams-derive/src/lib.rs +++ b/activitystreams-derive/src/lib.rs @@ -21,10 +21,15 @@ //! //! ## Examples //! +//! First, add `serde` and `activitystreams-derive` to your Cargo.toml +//! ```toml +//! activitystreams-derive = "3.0" +//! serde = { version = "1.0", features = ["derive"] } +//! ``` +//! //! ```rust -//! use activitystreams_derive::{Properties, UnitString}; -//! use activitystreams_traits::{Link, Object}; -//! use serde_derive::{Deserialize, Serialize}; +//! use activitystreams_derive::{properties, UnitString}; +//! use serde_json::Value; //! //! /// Using the UnitString derive macro //! /// @@ -34,39 +39,62 @@ //! #[activitystreams(SomeKind)] //! pub struct MyKind; //! -//! /// Using the Properties derive macro +//! /// Using the properties macro //! /// //! /// This macro generates getters and setters for the associated fields. -//! #[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -//! #[serde(rename_all = "camelCase")] -//! pub struct MyProperties { -//! /// Derive getters and setters for @context with Link and Object traits. -//! #[serde(rename = "@context")] -//! #[activitystreams(ab(Object, Link))] -//! pub context: Option, -//! -//! /// Use the UnitString MyKind to enforce the type of the object by "SomeKind" -//! #[serde(rename = "type")] -//! pub kind: MyKind, -//! -//! /// Derive getters and setters for required_key with String type. -//! /// -//! /// In the Activity Streams spec, 'functional' means there can only be one item for this -//! /// key. This means all fields not labeled 'functional' can also be serialized/deserialized -//! /// as Vec. -//! #[activitystreams(concrete(String), functional)] -//! pub required_key: serde_json::Value, +//! properties! { +//! My { +//! context { +//! types [ +//! Value, +//! ], +//! rename("@context"), +//! }, +//! kind { +//! types [ +//! MyKind, +//! ], +//! functional, +//! required, +//! rename("type"), +//! }, +//! required_key { +//! types [ +//! Value, +//! ], +//! functional, +//! required, +//! }, +//! } //! } //! # -//! # fn main () {} +//! # fn main () -> Result<(), Box { +//! let s = r#"{ +//! "@context": "http://www.w3c.org/ns#activitystreams", +//! "type": "SomeKind", +//! "required_key": { +//! "key": "value" +//! } +//! }"# +//! +//! let m: MyProperties = serde_json::from_str(s)?; +//! println!("{:?}", m.get_kind()); +//! # Ok(()) +//! # } //! ``` extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenTree; -use quote::quote; -use syn::{Attribute, Data, DeriveInput, Fields, Ident, Type}; +use quote::{quote, ToTokens}; +use syn::{ + braced, bracketed, parenthesized, + parse::{Parse, ParseStream, Peek}, + parse_macro_input, + punctuated::Punctuated, + token, Attribute, Data, DeriveInput, Fields, Ident, LitStr, Result, Token, Type, +}; use std::iter::FromIterator; @@ -110,12 +138,21 @@ pub fn ref_derive(input: TokenStream) -> TokenStream { }) }) .flat_map(move |(ident, ty, attr)| { - let object = object(attr); + let object = from_value(attr); let name = name.clone(); let ext_trait = Ident::new(&format!("{}Ext", object), name.span()); let activity_impls = quote! { - impl #object for #name {} + #[typetag::serde] + impl #object for #name { + fn as_any(&self) -> &std::any::Any { + self + } + + fn as_any_mut(&self) -> &mut std::any::Any { + self + } + } impl #ext_trait for #name { fn props(&self) -> &#ty { @@ -161,30 +198,6 @@ pub fn ref_derive(input: TokenStream) -> TokenStream { full.into() } -fn object(attr: Attribute) -> Ident { - let group = attr - .tokens - .clone() - .into_iter() - .filter_map(|token_tree| match token_tree { - TokenTree::Group(group) => Some(group), - _ => None, - }) - .next() - .unwrap(); - - group - .stream() - .clone() - .into_iter() - .filter_map(|token_tree| match token_tree { - TokenTree::Ident(ident) => Some(ident), - _ => None, - }) - .next() - .unwrap() -} - #[proc_macro_derive(UnitString, attributes(activitystreams))] pub fn unit_string(input: TokenStream) -> TokenStream { let input: DeriveInput = syn::parse(input).unwrap(); @@ -294,470 +307,705 @@ fn from_value(attr: Attribute) -> Ident { .unwrap() } -#[proc_macro_derive(Properties, attributes(activitystreams))] -pub fn properties_derive(input: TokenStream) -> TokenStream { - let input: DeriveInput = syn::parse(input).unwrap(); +#[proc_macro] +pub fn properties(tokens: TokenStream) -> TokenStream { + let Properties { name, fields } = parse_macro_input!(tokens as Properties); - let name = input.ident; + let name = Ident::new(&format!("{}Properties", name), name.span()); - let data = match input.data { - Data::Struct(s) => s, - _ => panic!("Can only derive for structs"), - }; + let (fields, deps): (Vec<_>, Vec<_>) = fields.iter().filter_map(|field| { + if field.description.types.is_empty() { + return None; + } - let fields = match data.fields { - Fields::Named(fields) => fields, - _ => panic!("Can only derive for named fields"), - }; - - let impls = fields - .named - .iter() - .filter_map(|field| { - let our_attr = field.attrs.iter().find(|attribute| { - attribute - .path - .segments - .last() - .map(|segment| { - segment.ident == Ident::new("activitystreams", segment.ident.span()) - }) - .unwrap_or(false) - }); - - if our_attr.is_some() { - let our_attr = our_attr.unwrap(); - - let is_option = match field.ty { - Type::Path(ref path) => path.path - .segments - .last() - .map(|seg| { - seg.ident == Ident::new("Option", seg.ident.span()) - }) - .unwrap_or(false), - _ => false, - }; - - let is_vec = match field.ty { - Type::Path(ref path) => path.path - .segments - .last() - .map(|seg| { - seg.ident == Ident::new("Vec", seg.ident.span()) - }) - .unwrap_or(false), - _ => false, - }; - - Some(( - field.ident.clone().unwrap(), - is_option, - is_vec, - is_functional(our_attr.clone()), - our_attr.clone(), - )) + let fname = field.name.clone(); + let (ty, deps) = if field.description.types.len() == 1 { + let ty = Ident::new(&field.description.types.first().unwrap().to_token_stream().to_string(), fname.span()); + if field.description.functional { + (ty, None) } else { - None + let enum_ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span()); + let deps = quote! { + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[serde(rename_all = "camelCase")] + #[serde(untagged)] + pub enum #enum_ty { + Term(#ty), + Array(Vec<#ty>), + } + + impl From<#ty> for #enum_ty { + fn from(t: #ty) -> Self { + #enum_ty::Term(t) + } + } + + impl From> for #enum_ty { + fn from(v: Vec<#ty>) -> Self { + #enum_ty::Array(v) + } + } + }; + + (enum_ty, Some(deps)) } - }) - .flat_map(|(ident, is_option, is_vec, is_functional, attr)| { - variants(attr) - .into_iter() - .map(move |(variant, is_concrete)| { - let lower_variant = variant.to_string().to_lowercase(); - let fn_name = Ident::new(&format!("{}_{}", ident, lower_variant), variant.span()); - let fn_plural = Ident::new(&format!("{}_{}_vec", ident, lower_variant), variant.span()); - let set_fn_name = Ident::new(&format!("set_{}_{}", ident, lower_variant), variant.span()); - let set_fn_plural = Ident::new(&format!("set_{}_{}_vec", ident, lower_variant), variant.span()); + } else { + let ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span()); - if is_concrete { - if is_option { - let single_1 = quote! { - /// Retrieve a value from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::NotFound` and - /// `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result<#variant> { - ::activitystreams_traits::properties::from_item(&self.#ident) - } - }; + let v_tys: Vec<_> = field + .description + .types + .iter() + .map(|v_ty| { + quote! { + #v_ty(#v_ty), + } + }) + .collect(); - let single_2 = quote! { - /// Set a value in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this a - /// lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: #variant) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_item(item)?; - Ok(()) - } - }; + let v_tokens = proc_macro2::TokenStream::from_iter(v_tys); - let single = quote! { - #single_1 - #single_2 - }; + let deps = if !field.description.functional { + let term_ty = Ident::new(&camelize(&format!("{}_{}_term_enum", name, fname)), fname.span()); - if is_functional { - single - } else { - let plural_1 = quote! { - /// Retrieve many values from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::NotFound` and - /// `Error::Deserialize` - pub fn #fn_plural(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_item(&self.#ident) - } - }; - - let plural_2 = quote! { - /// Set many values in the given struct - /// - /// This method serializes the item to JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_plural(&mut self, item: Vec<#variant>) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_item(item)?; - Ok(()) - } - }; - - quote! { - #single - #plural_1 - #plural_2 - } - } - } else if is_vec { - let single_1 = quote! { - /// Retrieve many values from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_vec(&self.#ident) - } - }; - - let single_2 = quote! { - /// Set many values in the given struct - /// - /// This method serializes the item to JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: Vec<#variant>>) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_vec(item)?; - Ok(()) - } - }; - - quote! { - #single_1 - #single_2 - } - } else { - let single = quote! { - /// Retrieve a value from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result<#variant> { - ::activitystreams_traits::properties::from_value(&self.#ident) - } - - /// Set a value in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this a - /// lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: #variant) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_value(item)?; - Ok(()) - } - }; - - if is_functional { - single - } else { - let plural_1 = quote! { - /// Retrieve many values from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_plural(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_value(&self.#ident) - } - }; - - let plural_2 = quote! { - /// Set many values in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this - /// a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_plural(&mut self, item: Vec<#variant>) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_value(item)?; - Ok(()) - } - }; - - quote! { - #single - #plural_1 - #plural_2 + let froms: Vec<_> = field + .description + .types + .iter() + .map(|v_ty| { + quote! { + impl From<#v_ty> for #term_ty { + fn from(item: #v_ty) -> #term_ty { + #term_ty::#v_ty(item) } } } - } else if is_option { - let single_1 = quote! { - /// Retrieve a value of type T from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::NotFound` and - /// `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result { - ::activitystreams_traits::properties::from_item(&self.#ident) - } - }; + }) + .collect(); - let single_2 = quote! { - /// Set a value of type T in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this a - /// lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: T) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_item(item)?; - Ok(()) - } - }; + let from_tokens = proc_macro2::TokenStream::from_iter(froms); - let single = quote!{ - #single_1 - #single_2 - }; + quote! { + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[serde(rename_all = "camelCase")] + #[serde(untagged)] + pub enum #term_ty { + #v_tokens + } - if is_functional { - single - } else { - let plural_1 = quote! { - /// Retrieve many values of type T from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::NotFound` and - /// `Error::Deserialize` - pub fn #fn_plural(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_item(&self.#ident) + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[serde(rename_all = "camelCase")] + #[serde(untagged)] + pub enum #ty { + Term(#term_ty), + Array(Vec<#term_ty>), + } + + impl From<#term_ty> for #ty { + fn from(term: #term_ty) -> Self { + #ty::Term(term) + } + } + + impl From> for #ty { + fn from(v: Vec<#term_ty>) -> Self { + #ty::Array(v) + } + } + + #from_tokens + } + } else { + let froms: Vec<_> = field + .description + .types + .iter() + .map(|v_ty| { + quote! { + impl From<#v_ty> for #ty { + fn from(item: #v_ty) -> #ty { + #ty::#v_ty(item) } - }; - - let plural_2 = quote! { - /// Set many values of type T in the given struct - /// - /// This method serializes the item to JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_plural(&mut self, item: Vec) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_item(item)?; - Ok(()) - } - }; - - quote! { - #single - #plural_1 - #plural_2 } } - } else if is_vec { - let single_1 = quote! { - /// Retrieve many values of type T from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_vec(&self.#ident) + }) + .collect(); + + let from_tokens = proc_macro2::TokenStream::from_iter(froms); + + quote! { + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[serde(rename_all = "camelCase")] + #[serde(untagged)] + pub enum #ty { + #v_tokens + } + + #from_tokens + } + }; + + (ty, Some(deps)) + }; + + let field_tokens = if field.description.required { + if let Some(ref rename) = field.description.rename { + quote! { + #[serde(rename = #rename)] + pub #fname: #ty, + } + } else { + quote! { + pub #fname: #ty, + } + } + } else { + if let Some(ref rename) = field.description.rename { + quote! { + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = #rename)] + pub #fname: Option<#ty>, + } + } else { + quote! { + #[serde(skip_serializing_if = "Option::is_none")] + pub #fname: Option<#ty>, + } + } + }; + + let fns = if field.description.types.len() == 1 { + let v_ty = field.description.types.first().unwrap().clone(); + + let set_ident = + Ident::new(&format!("set_{}", fname), fname.span()); + let get_ident = + Ident::new(&format!("get_{}", fname), fname.span()); + + let enum_ty = Ident::new(&camelize(&format!("{}_{}_enum", name, fname)), fname.span()); + + let set_many_ident = + Ident::new(&format!("set_many_{}s", fname), fname.span()); + let get_many_ident = + Ident::new(&format!("get_many_{}s", fname), fname.span()); + + if field.description.required { + if field.description.functional { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty> + { + self.#fname = item.into(); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> &#v_ty { + &self.#fname + } + }; + + quote!{ + #get + #set + } + } else { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty> + { + self.#fname = #enum_ty::Term(item.into()); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + #enum_ty::Term(ref term) => Some(term), + _ => None, + } + } + }; + + let set_many = quote! { + pub fn #set_many_ident(&mut self, item: Vec) -> &mut Self + where + T: Into<#v_ty>, + { + let item: Vec<#v_ty> = item.into_iter().map(Into::into).collect(); + self.#fname = #enum_ty::Array(item); + self + } + }; + + let get_many = quote! { + pub fn #get_many_ident(&self) -> Option<&[#v_ty]> { + match self.#fname { + #enum_ty::Array(ref array) => Some(array), + _ => None, + } + } + }; + + quote! { + #get + #set + #get_many + #set_many + } + } + } else { + if field.description.functional { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty> + { + self.#fname = Some(item.into()); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + self.#fname.as_ref() + } + }; + + quote!{ + #get + #set + } + } else { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty> + { + self.#fname = Some(#enum_ty::Term(item.into())); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + Some(#enum_ty::Term(ref term)) => Some(term), + _ => None, + } + } + }; + + let set_many = quote! { + pub fn #set_many_ident(&mut self, item: Vec) -> &mut Self + where + T: Into<#v_ty>, + { + let item: Vec<#v_ty> = item.into_iter().map(Into::into).collect(); + self.#fname = Some(#enum_ty::Array(item)); + self + } + }; + + let get_many = quote! { + pub fn #get_many_ident(&self) -> Option<&[#v_ty]> { + match self.#fname { + Some(#enum_ty::Array(ref a)) => Some(a), + _ => None, + } + } + }; + + quote! { + #get + #set + #get_many + #set_many + } + } + } + } else if field.description.functional { + let impls: Vec<_> = field + .description + .types + .iter() + .map(|v_ty| { + let set_ident = + Ident::new(&format!("set_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + let get_ident = + Ident::new(&format!("get_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + + if field.description.required { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty>, + { + let item: #v_ty = item.into(); + self.#fname = item.into(); + self } }; - let single_2 = quote! { - /// Set many values of type T in the given struct - /// - /// This method serializes the item to JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: Vec) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_vec(item)?; - Ok(()) + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + #ty::#v_ty(ref term) => Some(term), + _ => None, + } } }; quote! { - #single_1 - #single_2 + #get + #set } } else { - let single_1 = quote! { - /// Retrieve a value of type T from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_name(&self) -> ::activitystreams_traits::Result { - ::activitystreams_traits::properties::from_value(&self.#ident) + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty>, + { + let item: #v_ty = item.into(); + self.#fname = Some(item.into()); + self } }; - let single_2 = quote! { - /// Set a value of type T in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this a - /// lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_name(&mut self, item: T) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_value(item)?; - Ok(()) - } - }; - - let single = quote! { - #single_1 - #single_2 - }; - - if is_functional { - single - } else { - let plural_1 = quote! { - /// Retrieve many values of type T from the given struct - /// - /// This method deserializes the item from JSON, so be wary of using - /// this a lot. - /// - /// Possible errors from this method are `Error::Deserialize` - pub fn #fn_plural(&self) -> ::activitystreams_traits::Result> { - ::activitystreams_traits::properties::from_value(&self.#ident) + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + Some(#ty::#v_ty(ref term)) => Some(term), + _ => None, } - }; - - let plural_2 = quote! { - /// Set many values of type T in the given struct - /// - /// This method serializes the item to JSON, so be wary of using this - /// a lot. - /// - /// Possible errors from this method are `Error::Serialize` - pub fn #set_fn_plural(&mut self, item: Vec) -> ::activitystreams_traits::Result<()> { - self.#ident = ::activitystreams_traits::properties::to_value(item)?; - Ok(()) - } - }; - - quote! { - #single - #plural_1 - #plural_2 } + }; + + quote! { + #get + #set } } }) - }); + .collect(); - let tokens = proc_macro2::TokenStream::from_iter(impls); + let tokens = proc_macro2::TokenStream::from_iter(impls); + + quote! { + #tokens + } + } else { + let term_ty = Ident::new(&camelize(&format!("{}_{}_term_enum", name, fname)), fname.span()); + let impls: Vec<_> = field + .description + .types + .iter() + .map(|v_ty| { + let set_ident = + Ident::new(&format!("set_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + let get_ident = + Ident::new(&format!("get_{}_{}", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + + let set_many_ident = + Ident::new(&format!("set_many_{}_{}s", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + let get_many_ident = + Ident::new(&format!("get_many_{}_{}s", fname, snakize(&v_ty.to_token_stream().to_string())), fname.span()); + + if field.description.required { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty>, + { + let item: #v_ty = item.into(); + let item: #term_ty = item.into(); + self.#fname = item.into(); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + #ty::Term(#term_ty::#v_ty(ref term)) => Some(term), + _ => None, + } + } + }; + + let set_many = quote! { + pub fn #set_many_ident(&mut self, item: Vec) -> &mut Self + where + T: Into<#v_ty>, + { + let item: Vec<#v_ty> = item.into_iter().map(Into::into).collect(); + let item: Vec<#term_ty> = item.into_iter().map(Into::into).collect(); + self.#fname = item.into(); + self + } + }; + + let get_many = quote! { + pub fn #get_many_ident(&self) -> Option<&[#term_ty]> { + match self.#fname { + #ty::Array(ref array) => Some(array), + _ => None, + } + } + }; + + quote! { + #get + #set + #get_many + #set_many + } + } else { + let set = quote! { + pub fn #set_ident(&mut self, item: T) -> &mut Self + where + T: Into<#v_ty>, + { + let item: #v_ty = item.into(); + let item: #term_ty = item.into(); + self.#fname = Some(item.into()); + self + } + }; + + let get = quote! { + pub fn #get_ident(&self) -> Option<&#v_ty> { + match self.#fname { + Some(#ty::Term(#term_ty::#v_ty(ref term))) => Some(term), + _ => None, + } + } + }; + + let set_many = quote! { + pub fn #set_many_ident(&mut self, item: Vec) -> &mut Self + where + T: Into<#v_ty>, + { + let item: Vec<#v_ty> = item.into_iter().map(Into::into).collect(); + let item: Vec<#term_ty> = item.into_iter().map(Into::into).collect(); + self.#fname = Some(item.into()); + self + } + }; + + let get_many = quote! { + pub fn #get_many_ident(&self) -> Option<&[#term_ty]> { + match self.#fname { + Some(#ty::Array(ref array)) => Some(array), + _ => None, + } + } + }; + + quote! { + #get + #set + #get_many + #set_many + } + } + }) + .collect(); + + let tokens = proc_macro2::TokenStream::from_iter(impls); + + let delete = if !field.description.required { + let delete_ident = + Ident::new(&format!("delete_{}", fname), fname.span()); + + quote! { + pub fn #delete_ident(&mut self) -> &mut Self { + self.#fname = None; + self + } + } + } else { + quote! {} + }; + + quote! { + #tokens + + #delete + } + }; + + Some(((field_tokens, fns), deps)) + }).unzip(); + + let (fields, fns): (Vec<_>, Vec<_>) = fields.into_iter().unzip(); + let deps: Vec<_> = deps.into_iter().filter_map(|d| d).collect(); + + let field_tokens = proc_macro2::TokenStream::from_iter(fields); + let fn_tokens = proc_macro2::TokenStream::from_iter(fns); + let deps_tokens = proc_macro2::TokenStream::from_iter(deps); + + let q = quote! { + #deps_tokens + + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] + #[serde(rename_all = "camelCase")] + pub struct #name { + #field_tokens + } - let full = quote! { impl #name { - #tokens + #fn_tokens } }; - - full.into() + q.into() } -fn variants(attr: Attribute) -> Vec<(Ident, bool)> { - let group = attr - .tokens - .clone() - .into_iter() - .filter_map(|token_tree| match token_tree { - TokenTree::Group(group) => Some(group), - _ => None, +mod kw { + syn::custom_keyword!(types); + syn::custom_keyword!(functional); + syn::custom_keyword!(required); + syn::custom_keyword!(rename); +} + +struct Properties { + name: Ident, + fields: Punctuated, +} + +struct Field { + name: Ident, + description: Description, +} + +struct Description { + types: Punctuated, + functional: bool, + required: bool, + rename: Option, +} + +impl Parse for Properties { + fn parse(input: ParseStream) -> Result { + let name: Ident = input.parse()?; + + let content; + let _: token::Brace = braced!(content in input); + let fields = Punctuated::::parse_terminated(&content)?; + + Ok(Properties { name, fields }) + } +} + +impl Parse for Field { + fn parse(input: ParseStream) -> Result { + let name: Ident = input.parse()?; + + let content; + let _: token::Brace = braced!(content in input); + + let description = content.parse()?; + + Ok(Field { name, description }) + } +} + +impl Parse for Description { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if !lookahead.peek(kw::types) { + return Err(lookahead.error()); + } + input.parse::()?; + + let content; + let _: token::Bracket = bracketed!(content in input); + let types = Punctuated::::parse_terminated(&content)?; + optional_comma(&input)?; + + let functional = parse_kw::<_, kw::functional>(&input, kw::functional)?; + let required = parse_kw::<_, kw::required>(&input, kw::required)?; + let rename = parse_rename::<_, kw::rename>(&input, kw::rename)?; + + Ok(Description { + types, + functional, + required, + rename, }) - .next() - .unwrap(); + } +} - let mut is_concrete = false; +fn parse_kw(input: &ParseStream, t: T) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(t) { + input.parse::()?; + optional_comma(&input)?; - group - .stream() - .clone() - .into_iter() - .filter_map(|token_tree| match token_tree { - TokenTree::Ident(ident) => { - is_concrete = ident.to_string() == "concrete"; - None + return Ok(true); + } + + Ok(false) +} + +fn parse_rename(input: &ParseStream, t: T) -> Result> { + let lookahead = input.lookahead1(); + if lookahead.peek(t) { + input.parse::()?; + let content; + parenthesized!(content in input); + let s: LitStr = content.parse()?; + optional_comma(&input)?; + + return Ok(Some(s.value())); + } + + Ok(None) +} + +fn optional_comma(input: &ParseStream) -> Result<()> { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![,]) { + input.parse::()?; + } + Ok(()) +} + +fn camelize(s: &str) -> String { + let (s, _) = s + .chars() + .fold((String::new(), true), |(mut acc, should_upper), c| { + if c == '_' { + (acc, true) + } else { + if should_upper { + acc += &c.to_uppercase().to_string(); + } else { + acc += &c.to_string(); + } + + (acc, false) } - TokenTree::Group(group) => Some(group.stream().into_iter().filter_map( - move |token_tree| match token_tree { - TokenTree::Ident(ident) => Some((ident, is_concrete)), - _ => None, - }, - )), - _ => None, - }) - .flat_map(|i| i) - .collect() + }); + + s } -fn is_functional(attr: Attribute) -> bool { - let group = attr - .tokens - .clone() - .into_iter() - .filter_map(|token_tree| match token_tree { - TokenTree::Group(group) => Some(group), - _ => None, - }) - .next() - .unwrap(); - - group - .stream() - .clone() - .into_iter() - .any(|token_tree| match token_tree { - TokenTree::Ident(ident) => ident.to_string() == "functional", - _ => false, - }) +fn snakize(s: &str) -> String { + s.chars().fold(String::new(), |mut acc, c| { + if c.is_uppercase() && !acc.is_empty() { + acc += "_"; + acc += &c.to_lowercase().to_string(); + } else if c.is_uppercase() { + acc += &c.to_lowercase().to_string(); + } else { + acc += &c.to_string(); + } + acc + }) } diff --git a/activitystreams-traits/Cargo.toml b/activitystreams-traits/Cargo.toml index ad7a11c..e99094f 100644 --- a/activitystreams-traits/Cargo.toml +++ b/activitystreams-traits/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "activitystreams-traits" -description = "Traits for ActivityStreams objects" -version = "0.2.0" +description = "Traits for ActivityStreams 2.0 objects" +version = "0.3.0" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/activitystreams" @@ -10,9 +10,8 @@ keywords = ["activitystreams", "activitypub"] edition = "2018" [dependencies] -serde = "1.0" -serde_json = "1.0" thiserror = "1.0" +typetag = "0.1.4" [dev-dependencies] -serde_derive = "1.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/activitystreams-traits/src/error.rs b/activitystreams-traits/src/error.rs deleted file mode 100644 index d6a4e84..0000000 --- a/activitystreams-traits/src/error.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of ActivityStreams Traits. - * - * Copyright © 2018 Riley Trautman - * - * ActivityStreams Traits 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 Traits 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 Traits. If not, see . - */ - -use std::result; - -/// The Error type -#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)] -pub enum Error { - /// This error occurs when an Activity Streams type does not contain a requested value - #[error("Key not present")] - NotFound, - - /// This error occurs when a requested value could not be deserialized into the requested type - #[error("Failed to deserialize data as requested type")] - Deserialize, - - /// This error occurs when a provided item could not be serialized into an Activity Streams - /// type - #[error("Failed to serialize data")] - Serialize, -} - -/// An alias for Result -pub type Result = result::Result; diff --git a/activitystreams-traits/src/lib.rs b/activitystreams-traits/src/lib.rs index 24e3e92..275122b 100644 --- a/activitystreams-traits/src/lib.rs +++ b/activitystreams-traits/src/lib.rs @@ -26,7 +26,7 @@ //! //! ```rust //! use activitystreams_traits::{Object, Actor}; -//! use serde_derive::{Deserialize, Serialize}; +//! use serde::{Deserialize, Serialize}; //! //! #[derive(Clone, Debug, Default, Deserialize, Serialize)] //! #[serde(rename_all = "camelCase")] @@ -38,7 +38,9 @@ //! kind: String, //! } //! +//! #[typetag::serde] //! impl Object for Persona {} +//! #[typetag::serde] //! impl Actor for Persona {} //! //! # fn main() {} @@ -47,14 +49,13 @@ mod activity; mod actor; mod collection; -mod error; mod link; mod object; -pub mod properties; -pub use self::activity::*; -pub use self::actor::*; -pub use self::collection::*; -pub use self::error::*; -pub use self::link::*; -pub use self::object::*; +pub use self::{ + activity::{Activity, IntransitiveActivity}, + actor::Actor, + collection::{Collection, CollectionPage}, + link::Link, + object::Object, +}; diff --git a/activitystreams-traits/src/link.rs b/activitystreams-traits/src/link.rs index 23e6cee..aa62697 100644 --- a/activitystreams-traits/src/link.rs +++ b/activitystreams-traits/src/link.rs @@ -17,7 +17,7 @@ * along with ActivityStreams Traits. If not, see . */ -use serde::{de::DeserializeOwned, ser::Serialize}; +use std::any::Any; /// A Link is an indirect, qualified reference to a resource identified by a URL. /// @@ -27,4 +27,9 @@ use serde::{de::DeserializeOwned, ser::Serialize}; /// 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: DeserializeOwned + Serialize {} +#[typetag::serde(tag = "type")] +pub trait Link: std::fmt::Debug { + fn as_any(&self) -> &dyn Any; + + fn as_any_mut(&mut self) -> &mut dyn Any; +} diff --git a/activitystreams-traits/src/object.rs b/activitystreams-traits/src/object.rs index 958f1db..5ed8cc7 100644 --- a/activitystreams-traits/src/object.rs +++ b/activitystreams-traits/src/object.rs @@ -17,11 +17,16 @@ * along with ActivityStreams Traits. If not, see . */ -use serde::{de::DeserializeOwned, ser::Serialize}; +use std::any::Any; /// 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: DeserializeOwned + Serialize {} +#[typetag::serde(tag = "type")] +pub trait Object: std::fmt::Debug { + fn as_any(&self) -> &dyn Any; + + fn as_any_mut(&mut self) -> &mut dyn Any; +} diff --git a/activitystreams-traits/src/properties.rs b/activitystreams-traits/src/properties.rs deleted file mode 100644 index 3f42888..0000000 --- a/activitystreams-traits/src/properties.rs +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This file is part of ActivityStreams Traits. - * - * Copyright © 2018 Riley Trautman - * - * ActivityStreams Traits 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 Traits 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 Traits. If not, see . - */ - -//! A module containing helpers for tranlsating common JSON representations to and from concrete -//! types - -use serde::{de::DeserializeOwned, ser::Serialize}; - -use crate::error::{Error, Result}; - -/// Deserialize a `Value` into concrete type I -pub fn from_value(item: &serde_json::Value) -> Result -where - I: DeserializeOwned, -{ - serde_json::from_value(item.clone()).map_err(|_| Error::Deserialize) -} - -/// Serialize concrete type I into a `Value` -pub fn to_value(item: I) -> Result -where - I: Serialize, -{ - serde_json::to_value(item).map_err(|_| Error::Serialize) -} - -/// Deserialize an `Option` into concrete type I -pub fn from_item(item: &Option) -> Result -where - I: DeserializeOwned, -{ - if let &Some(ref item) = item { - from_value(item) - } else { - Err(Error::NotFound) - } -} - -/// Serialize concrete type I into an `Option` -pub fn to_item(item: I) -> Result> -where - I: Serialize, -{ - to_value(item).map(Some) -} - -/// Deserialize a `Vec` into a `Vec` -pub fn from_vec(v: &Vec) -> Result> -where - I: DeserializeOwned, -{ - v.iter().fold(Ok(Vec::new()), |acc, item| match acc { - Ok(mut acc) => from_value(item).map(|item| { - acc.push(item); - acc - }), - e => e, - }) -} - -/// Serialize a `Vec` into a `Vec` -pub fn to_vec(v: Vec) -> Result> -where - I: Serialize, -{ - v.into_iter().fold(Ok(Vec::new()), |acc, item| match acc { - Ok(mut acc) => to_value(item).map(|item| { - acc.push(item); - acc - }), - e => e, - }) -} diff --git a/activitystreams-types/Cargo.toml b/activitystreams-types/Cargo.toml index b157a3b..998bd5d 100644 --- a/activitystreams-types/Cargo.toml +++ b/activitystreams-types/Cargo.toml @@ -11,13 +11,13 @@ edition = "2018" [dependencies] thiserror = "1.0.11" -activitystreams-derive = { version = "0.2", path = "../activitystreams-derive" } -activitystreams-traits = { version = "0.2", path = "../activitystreams-traits" } +activitystreams-derive = { version = "0.3", path = "../activitystreams-derive" } +activitystreams-traits = { version = "0.3", path = "../activitystreams-traits" } chrono = { version = "0.4", features = ["serde"] } mime = "0.3" -serde = "1.0" -serde_derive = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +typetag = "0.1.4" [dev-dependencies] anyhow = "1.0" diff --git a/activitystreams-types/src/link/mod.rs b/activitystreams-types/src/link/mod.rs index 175aabb..52b64f7 100644 --- a/activitystreams-types/src/link/mod.rs +++ b/activitystreams-types/src/link/mod.rs @@ -59,3 +59,47 @@ impl LinkExt for Mention { &mut self.link_props } } + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(transparent)] +pub struct LinkBox(pub Box); + +impl LinkBox { + pub fn is(&self) -> bool + where + T: Link, + { + self.0.as_any().is::() + } + + pub fn downcast_ref(&self) -> Option<&T> + where + T: Link, + { + self.0.as_any().downcast_ref() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: Link, + { + self.0.as_any_mut().downcast_mut() + } + + pub fn downcast(self) -> Option + where + T: Link, + { + let any: Box = self; + any.downcast() + } +} + +impl From for LinkBox +where + T: Link, +{ + fn from(t: T) -> Self { + LinkBox(Box::new(t)) + } +} diff --git a/activitystreams-types/src/object/mod.rs b/activitystreams-types/src/object/mod.rs index 355c004..126823c 100644 --- a/activitystreams-types/src/object/mod.rs +++ b/activitystreams-types/src/object/mod.rs @@ -182,7 +182,7 @@ pub struct Place { /// `Actor` Type objects. /// /// The `describes` property is used to reference the object being described by the profile. -#[derive(Clone, Debug, Default, Deserialize, Serialize, PropRefs, Properties)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Profile { #[serde(rename = "type")] @@ -215,7 +215,7 @@ pub struct Profile { /// individuals that are directly connected within a person's social graph. Suppose we have a user, /// Sally, with direct relationships to users Joe and Jane. Sally follows Joe's updates while Sally /// and Jane have a mutual relationship. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties, PropRefs)] +#[derive(Clone, Debug, Default, Deserialize, PropRefs, Serialize)] #[serde(rename_all = "camelCase")] pub struct Relationship { #[serde(rename = "type")] @@ -271,3 +271,63 @@ pub struct Video { #[activitystreams(Object)] pub object_props: ObjectProperties, } + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(transparent)] +pub struct ImageBox(pub Box); + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(transparent)] +pub struct ObjectBox(pub Box); + +impl ObjectBox { + pub fn is(&self) -> bool + where + T: Object, + { + self.0.as_any().is::() + } + + pub fn downcast_ref(&self) -> Option<&T> + where + T: Object, + { + self.0.as_any().downcast_ref() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: Object, + { + self.0.as_any_mut().downcast_mut() + } + + pub fn downcast(self) -> Option + where + T: Object, + { + let any: Box = self; + any.downcast() + } +} + +impl From for ImageBox { + fn from(i: Image) -> Self { + ImageBox(Box::new(i)) + } +} + +impl From for Image { + fn from(i: ImageBox) -> Self { + *i.0 + } +} + +impl From for ObjectBox +where + T: Object, +{ + fn from(t: T) -> Self { + ObjectBox(Box::new(t)) + } +} diff --git a/activitystreams-types/src/object/properties.rs b/activitystreams-types/src/object/properties.rs index bd9ffcf..2db4dbd 100644 --- a/activitystreams-types/src/object/properties.rs +++ b/activitystreams-types/src/object/properties.rs @@ -32,7 +32,7 @@ //! #[serde(rename = "type")] //! pub kind: String, //! -//! /// Define a require property for the MyObject type +//! /// Define a required property for the MyObject type //! pub my_property: String, //! //! #[serde(flatten)] @@ -44,15 +44,16 @@ //! # fn main() {} //! ``` -use activitystreams_derive::Properties; +use activitystreams_derive::properties; use activitystreams_traits::{Collection, Error, Link, Object, Result}; use chrono::{offset::Utc, DateTime}; use serde_derive::{Deserialize, Serialize}; -use crate::object::Image; - -/// Alias chrono::DateTime for use in derive macros -pub type UtcTime = DateTime; +use crate::{ + link::LinkBox, + object::{Image, ObjectBox}, + primitives::*, +}; /// Define all the properties of the Object base type as described by the Activity Streams /// vocabulary. @@ -66,417 +67,546 @@ pub type UtcTime = DateTime; /// own `type` properties as Unit Structs with custom serde behaviour. /// /// All properties are optional (including the id and type). -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct ObjectProperties { - // TODO: IRI type - /// 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: `anyUri` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(alias = "@id")] - #[activitystreams(concrete(String), functional)] - pub id: Option, +properties! { + Object { + id { + /// 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 + types [ + XsdAnyUri, + ], + functional, + }, - /// Identifies a resource attached or related to an object that potentially requires special - /// handling. - /// - /// The intent is to provide a model that is at least semantically similar to attachments in - /// email. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub attachment: Option, + attachment { + /// Identifies a resource attached or related to an object that potentially requires special + /// handling. + /// + /// The intent is to provide a model that is at least semantically similar to attachments in + /// email. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// Identifies one or more entities to which this object is attributed. - /// - /// The attributed entities might not be Actors. For instance, an object might be attributed to - /// the completion of another activity. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub attributed_to: Option, + attributed_to { + /// Identifies one or more entities to which this object is attributed. + /// + /// The attributed entities might not be Actors. For instance, an object might be attributed to + /// the completion of another activity. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// Identifies one or more entities that represent the total population of entities for which - /// the object can considered to be relevant. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub audience: Option, + audience { + /// Identifies one or more entities that represent the total population of entities for which + /// the object can considered to be relevant. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - // TODO: rdf:langString - /// The content or textual representation of the Object encoded as a JSON string. - /// - /// By default, the value of content is HTML. The mediaType property can be used in the object - /// to indicate a different content type. - /// - /// The content MAY be expressed using multiple language-tagged values. - /// - /// - Range: `xsd:string` | `rdf:langString` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String))] - pub content: Option, + content { + /// The content or textual representation of the Object encoded as a JSON string. + /// + /// By default, the value of content is HTML. The mediaType property can be used in the object + /// to indicate a different content type. + /// + /// The content MAY be expressed using multiple language-tagged values. + /// + /// - Range: `xsd:string` | `rdf:langString` + /// - Functional: false + types [ + XsdString, + RdfLangString, + ], + }, - /// 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(skip_serializing_if = "Option::is_none", rename = "@context")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub context: Option, + context { + /// 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 + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - // TODO: rdf:langString - /// 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")] - #[serde(alias = "displayName")] - #[activitystreams(concrete(String))] - pub name: Option, + name { + /// 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 + types [ + XsdString, + RdfLangString, + ], + alias("displayName"), + }, - /// The date and time describing the actual or expected ending time of the object. - /// - /// When used with an Activity object, for instance, the endTime property specifies the moment - /// the activity concluded or is expected to conclude. - /// - /// - Range: `xsd:dateTime` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String, UtcTime), functional)] - pub end_time: Option, + end_time { + /// The date and time describing the actual or expected ending time of the object. + /// + /// When used with an Activity object, for instance, the endTime property specifies the moment + /// the activity concluded or is expected to conclude. + /// + /// - Range: `xsd:dateTime` + /// - Functional: true + types [ + XsdDateTime, + ], + functional, + }, - /// Identifies the entity (e.g. an application) that generated the object. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub generator: Option, + generator { + /// Identifies the entity (e.g. an application) that generated the object. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// Indicates an entity that describes an icon for this object. - /// - /// The image should have an aspect ratio of one (horizontal) to one (vertical) and should be - /// suitable for presentation at a small size. - /// - /// - Range: `Image` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(Image, String))] - pub icon: Option, + icon { + /// Indicates an entity that describes an icon for this object. + /// + /// The image should have an aspect ratio of one (horizontal) to one (vertical) and should be + /// suitable for presentation at a small size. + /// + /// - Range: `Image` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ImageBox, + LinkBox, + ], + }, - /// Indicates an entity that describes an image for this object. - /// - /// Unlike the icon property, there are no aspect ratio or display size limitations assumed. - /// - /// - Range: `Image` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(Image, String))] - pub image: Option, + image { + /// Indicates an entity that describes an image for this object. + /// + /// Unlike the icon property, there are no aspect ratio or display size limitations assumed. + /// + /// - Range: `Image` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ImageBox, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub in_reply_to: Option, + in_reply_to { + /// Indicates one or more entities for which this object is considered a response. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// Indicates one or more physical or logical locations associated with the object. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub location: Option, + location { + /// Indicates one or more physical or logical locations associated with the object. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// Identifies an entity that provides a preview of this object. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub preview: Option, + preview { + /// Identifies an entity that provides a preview of this object. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// The date and time at which the object was published. - /// - /// - Range: `xsd:dateTime` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String, UtcTime), functional)] - pub published: Option, + published { + /// The date and time at which the object was published. + /// + /// - Range: `xsd:dateTime` + /// - Functional: true + types [ + XsdDateTime, + ], + functional, + }, - /// Identifies a `Collection` containing objects considered to be responses to this object. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Collection), concrete(String), functional)] - pub replies: Option, + replies { + /// Identifies a `Collection` containing objects considered to be responses to this object. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// The date and time describing the actual or expected starting time of the object. - /// - /// When used with an `Activity` object, for instance, the `start_time` property specifies the - /// moment the activity began or is scheduled to begin. - /// - /// - Range: `Object` | `Link` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String, UtcTime), functional)] - pub start_time: Option, + start_time { + /// The date and time describing the actual or expected starting time of the object. + /// + /// When used with an `Activity` object, for instance, the `start_time` property specifies the + /// moment the activity began or is scheduled to begin. + /// + /// - Range: `xsd:DateTime` + /// - Functional: true + types [ + XsdDateTime, + ], + functional, + }, - // TODO: rdf:langString - /// A natural language summarization of the object encoded as HTML. - /// - /// Multiple language tagged summaries MAY be provided. - /// - /// - Range: `xsd:string` | `rdf:langString` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String))] - pub summary: Option, + summary { + /// A natural language summarization of the object encoded as HTML. + /// + /// Multiple language tagged summaries MAY be provided. + /// + /// - Range: `xsd:string` | `rdf:langString` + /// - Functional: false + types [ + XsdString, + RdfLangString, + ], + }, - /// One or more "tags" that have been associated with an objects. A tag can be any kind of - /// `Object`. - /// - /// The key difference between attachment and tag is that the former implies association by - /// inclusion, while the latter implies associated by reference. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub tag: Option, + tag { + /// One or more "tags" that have been associated with an objects. A tag can be any kind of + /// `Object`. + /// + /// The key difference between attachment and tag is that the former implies association by + /// inclusion, while the latter implies associated by reference. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// The date and time at which the object was updated, - /// - /// - Range: `xsd:dateTime` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String, UtcTime), functional)] - pub updated: Option, + updated { + /// The date and time at which the object was updated, + /// + /// - Range: `xsd:dateTime` + /// - Functional: true + types [ + XsdDateTime, + ], + functional, + }, - /// Identifies one or more links to representations of the object. - /// - /// - Range: `xsd:anyUri` | `Link` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String), ab(Link))] - pub url: Option, + url { + /// Identifies one or more links to representations of the object. + /// + /// - Range: `xsd:anyUri` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub to: Option, + to { + /// Identifies an entity considered to be part of the public primary audience of an `Object`. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub bto: Option, + bto { + /// Identifies an `Object` that is part of the private primary audience of this `Object`. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub cc: Option, + cc { + /// Identifies an `Object` that is part of the public secondary audience of this `Object`. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(ab(Object, Link), concrete(String))] - pub bcc: Option, + bcc { + /// Identifies one or more `Objects` that are part of the private secondary audience of this + /// `Object`. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// 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")] - #[activitystreams(concrete(String), functional)] - pub media_type: Option, + media_type { + /// 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 + types [ + MimeMediaType, + ], + functional, + }, - // TODO: xsd:duration - /// 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. - /// - /// The value MUST be expressed as an xsd:duration as defined by - /// [[xmlschema11-2](https://www.w3.org/TR/xmlschema11-2/)], section - /// 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S"). - /// - /// - Range: `xsd:duration` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String), functional)] - pub duration: Option, -} - -impl ObjectProperties { - /// Fetch a typed `Mime` struct from the `media_type` field. - pub fn media_type(&self) -> Result { - self.media_type_string() - .and_then(|s| s.parse().map_err(|_| Error::Deserialize)) + duration { + /// 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. + /// + /// The value MUST be expressed as an xsd:duration as defined by + /// [[xmlschema11-2](https://www.w3.org/TR/xmlschema11-2/)], section + /// 3.3.6 (e.g. a period of 5 seconds is represented as "PT5S"). + /// + /// - Range: `xsd:duration` + /// - Functional: true + types [ + XsdDuration, + ], + functional, + }, } } /// Define all the properties of the Location type as described by the Activity Streams vocabulary. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct PlaceProperties { - /// Indicates the accuracy of position coordinates on a `Place` objects. - /// - /// Expressed in properties of percentage. e.g. "94.0" means "94.0% accurate". - /// - /// - Range: `xsd:float` [>= 0.0f, <= 100.0f] - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(f64), functional)] - pub accuracy: Option, +properties! { + Place { + accuracy { + /// Indicates the accuracy of position coordinates on a `Place` objects. + /// + /// Expressed in properties of percentage. e.g. "94.0" means "94.0% accurate". + /// + /// - Range: `xsd:float` [>= 0.0f, <= 100.0f] + /// - Functional: true + types [ + XsdFloat, + ], + functional, + }, - /// Indicates the altitude of a place. The measurement units is indicated using the units - /// property. - /// - /// If units is not specified, the default is assumed to be "m" indicating meters. - /// - /// - Range: `xsd:float` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(f64), functional)] - pub altitude: Option, + altitude { + /// Indicates the altitude of a place. The measurement units is indicated using the units + /// property. + /// + /// If units is not specified, the default is assumed to be "m" indicating meters. + /// + /// - Range: `xsd:float` + /// - Functional: true + types [ + XsdFloat, + ], + functional, + }, - /// The latitude of a place. - /// - /// - Range: `xsd:float` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(f64), functional)] - pub latitude: Option, + latitude { + /// The latitude of a place. + /// + /// - Range: `xsd:float` + /// - Functional: true + types [ + XsdFloat, + ], + functional, + } - /// The longitude of a place. - /// - /// - Range: `xsd:float` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(f64), functional)] - pub longitude: Option, + logitude { + /// The longitude of a place. + /// + /// - Range: `xsd:float` + /// - Functional: true + types [ + XsdFloat, + ], + functional, + }, - /// The radius from the given latitude and longitude for a Place. - /// - /// The units is expressed by the units property. If units is not specified, the default is - /// assumed to be "m" indicating "meters". - /// - /// - Range: `xsd:float` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(f64), functional)] - pub radius: Option, + radius { + /// The radius from the given latitude and longitude for a Place. + /// + /// The units is expressed by the units property. If units is not specified, the default is + /// assumed to be "m" indicating "meters". + /// + /// - Range: `xsd:float` + /// - Functional: true + types [ + XsdFloat, + ], + functional, + }, - /// Specifies the measurement units for the radius and altitude properties on a `Place` object. - /// - /// If not specified, the default is assumed to be "m" for "meters". - /// - /// - Range: `xsd:float` - /// - Functional: true - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(concrete(String), functional)] - pub units: Option, + units { + /// Specifies the measurement units for the radius and altitude properties on a `Place` object. + /// + /// If not specified, the default is assumed to be "m" for "meters". + /// + /// TODO: encode rage as any of `"cm"` | `"feet"` | `"inches"` | `"km"` | `"m"` | `xsd:anyUri` + /// - Range: `xsd:anyUri` | `xsd:string` + /// - Functional: true + types [ + XsdAnyUri, + XsdString, + ], + functional, + }, + } } /// Define all the properties of the Profile type as described by the Activity Streams vocabulary. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct ProfileProperties { - /// On a `Profile` object, the describes property identifies the object described by the - /// `Profile`. - /// - /// - Range: `Object` - /// - Functional: true - #[activitystreams(ab(Object), concrete(String), functional)] - pub describes: serde_json::Value, +properties! { + Profile { + describes { + /// On a `Profile` object, the describes property identifies the object described by the + /// `Profile`. + /// + /// - Range: `Object` + /// - Functional: true + types [ + XsdAnyUri, + ObjectBox, + ], + functional, + }, + } } /// Define all the properties of the Relationship type as described by the Activity Streams /// vocabulary. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct RelationshipProperties { - /// On a `Relationship` object, the subject property identifies one of the connected - /// individuals. - /// - /// For instance, for a `Relationship` object describing "John is related to Sally", subject - /// would refer to John. - /// - /// - Range: `Object` | `Link` - /// - Functional: true - #[activitystreams(ab(Object, Link), concrete(String), functional)] - subject: serde_json::Value, +properties! { + Relationship { + subject { + /// On a `Relationship` object, the subject property identifies one of the connected + /// individuals. + /// + /// For instance, for a `Relationship` object describing "John is related to Sally", subject + /// would refer to John. + /// + /// - Range: `Object` | `Link` + /// - Functional: true + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + functional, + }, - /// When used within a `Relationship` describes the entity to which the subject is related. - /// - /// - Range: `Object` | `Link` - /// - Functional: false - #[activitystreams(ab(Object, Link), concrete(String))] - object: serde_json::Value, + object { + /// When used within a `Relationship` describes the entity to which the subject is related. + /// + /// - Range: `Object` | `Link` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + LinkBox, + ], + }, - /// On a `Relationship` object, the relationship property identifies the kind of relationship - /// that exists between subject and object. - /// - /// - Range: `Object` - /// - Functional: false - #[activitystreams(ab(Object), concrete(String))] - relationship: serde_json::Value, + relationship { + /// On a `Relationship` object, the relationship property identifies the kind of relationship + /// that exists between subject and object. + /// + /// - Range: `Object` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + ], + }, + } } /// Define all the properties of the Tombstone type as described by the Activity Streams vocabulary. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] -#[serde(rename_all = "camelCase")] -pub struct TombstoneProperties { - /// On a `Tombstone` object, the formerType property identifies the type of the object that was - /// deleted. - /// - /// - Range: `Object` - /// - Functional: false - #[serde(skip_serializing_if = "Option::is_none")] - #[activitystreams(ab(Object), concrete(String))] - pub former_type: Option, +properties! { + Tombstone { + former_type { + /// On a `Tombstone` object, the formerType property identifies the type of the object that was + /// deleted. + /// + /// - Range: `Object` + /// - Functional: false + types [ + XsdAnyUri, + ObjectBox, + ], + }, - /// 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")] - #[activitystreams(concrete(String, UtcTime), functional)] - pub deleted: Option, + deleted { + /// On a `Tombstone` object, the deleted property is a timestamp for when the object was + /// deleted. + /// + /// - Range: `xsd:dateTime` + /// - Functional: true + types [ + XsdDateTime, + ], + functional, + }, + } }