diff --git a/Cargo.toml b/Cargo.toml index 4af405c..96c7349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "activitystreams" description = "Activity Streams in Rust" -version = "0.4.0-alpha.2" +version = "0.4.0-alpha.3" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/activitystreams" @@ -17,7 +17,7 @@ primitives = ["chrono", "mime", "serde", "thiserror", "url"] types = ["derive", "kinds", "primitives", "serde"] [dependencies] -activitystreams-derive = { version = "0.4.0-alpha.1", path = "activitystreams-derive", optional = true} +activitystreams-derive = { version = "0.4.0-alpha.2", path = "activitystreams-derive", optional = true} typetag = "0.1.4" chrono = { version = "0.4", optional = true } mime = { version = "0.3", optional = true } diff --git a/README.md b/README.md index a1364a6..f2a3d70 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For basic use, add the following to your Cargo.toml ```toml # Cargo.toml -activitystreams = "0.4.0-alpha.2" +activitystreams = "0.4.0-alpha.3" ``` And then use it in your project diff --git a/activitystreams-derive/Cargo.toml b/activitystreams-derive/Cargo.toml index 42ff19a..03c27a4 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.4.0-alpha.1" +version = "0.4.0-alpha.2" license = "GPL-3.0" authors = ["asonix "] repository = "https://git.asonix.dog/Aardwolf/activitystreams" diff --git a/activitystreams-derive/README.md b/activitystreams-derive/README.md index a7e88c6..813c64b 100644 --- a/activitystreams-derive/README.md +++ b/activitystreams-derive/README.md @@ -10,20 +10,22 @@ Add the required crates to your `Cargo.toml` ```toml # Cargo.toml -activitystreams-derive = "0.4-alpha.0" -activitystreams-traits = "0.4-alpha.0" -activitystreams-types = "0.4-alpha.0" +activitystreams = "0.4.0-alpha.3" serde = { version = "1.0", features = ["derive"] } ``` And then in your project ```rust -use activitystreams_derive::{properties, PropRefs, UnitString}; -use activitystreams_traits::Object; -use activitystreams_types::object::{ - properties::ObjectProperties, - ObjectExt, +// derive macros +use activitystreams::{ + properties, + PropRefs, + UnitString }; +// traits +use activitystreams::Object; +// properties +use activitystreams::object::properties::ObjectProperties; /// Using the UnitString derive macro /// @@ -86,7 +88,7 @@ pub struct My { /// Derive AsRef and AsMut /// - /// as well as the Object and ObjectExt traits + /// as well as the Object trait #[serde(flatten)] #[activitystreams(Object)] properties: ObjectProperties, @@ -95,9 +97,11 @@ pub struct My { fn main() -> Result<(), Box> { let mut my = My::default(); - my.as_mut().set_required_key("Hello")?; + let mprops: &mut MyProperties = my.as_mut(); + mprops.set_required_key("Hello")?; - assert_eq!(my.as_ref().get_required_key(), "Hello"); + let mprops: &MyProperties = my.as_ref(); + assert_eq!(mprops.get_required_key(), "Hello"); Ok(()) } ``` diff --git a/activitystreams-derive/src/lib.rs b/activitystreams-derive/src/lib.rs index 0f6d678..7d27263 100644 --- a/activitystreams-derive/src/lib.rs +++ b/activitystreams-derive/src/lib.rs @@ -551,7 +551,7 @@ pub fn properties(tokens: TokenStream) -> TokenStream { String::new(), format!("`{}` isn't functional, meaning it can only be represented as a single `{}`", fname, ty), String::new(), - format!("This enum's variants representa ll valid types to construct a `{}`", fname), + format!("This enum's variants represent all valid types to construct a `{}`", fname), ]); quote! { #doc_lines @@ -944,15 +944,21 @@ pub fn properties(tokens: TokenStream) -> TokenStream { } }; - let doc_line = to_doc(&format!("Get `{}` as a slice of `{}`s", fname, term_ty.to_token_stream())); + let doc_line = to_doc(&format!("Get `{}` as a vec of `&{}`s", fname, v_ty.to_token_stream())); let get_many = quote! { #doc_line /// /// This returns `None` if /// - There is only one value present - pub fn #get_many_ident(&self) -> Option<&[#term_ty]> { + /// + /// The returned vec will be empty if no values match the requested + /// type, but values are present. + pub fn #get_many_ident(&self) -> Option> { match self.#fname { - #ty::Array(ref array) => Some(array), + #ty::Array(ref array) => Some(array.iter().filter_map(|i| match i { + #term_ty::#v_ty(item) => Some(item), + _ => None, + }).collect()), _ => None, } } @@ -1017,9 +1023,12 @@ pub fn properties(tokens: TokenStream) -> TokenStream { /// This returns `None` if /// - There is no value present /// - There is only one value present - pub fn #get_many_ident(&self) -> Option<&[#term_ty]> { + pub fn #get_many_ident(&self) -> Option> { match self.#fname { - Some(#ty::Array(ref array)) => Some(array), + Some(#ty::Array(ref array)) => Some(array.iter().filter_map(|i| match i { + #term_ty::#v_ty(item) => Some(item), + _ => None, + }).collect()), _ => None, } } diff --git a/src/lib.rs b/src/lib.rs index 65f960b..c706c5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,11 +21,234 @@ //! //! A set of Traits and Types that make up the Activity Streams specification //! +//! ## Usage +//! +//! First, add ActivityStreams to your dependencies +//! ```toml +//! activitystreams = "0.4.0-alpha.3" +//! ``` +//! +//! ### Types +//! +//! The project is laid out by Kind => vocabulary => Type +//! +//! So to use an ActivityStreams Video, you'd write +//! ```rust +//! use activitystreams::object::streams::Video; +//! ``` +//! +//! And to use an ActivityPub profile, you'd write +//! ```rust +//! use activitystreams::object::apub::Profile; +//! ``` +//! +//! Link is a little different, since there's only one defined link type, called Mention. +//! ```rust +//! use activitystreams::link::Mention; +//! ``` +//! +//! ### Properties +//! +//! Each concrete type implements `AsRef<>` for each of their properties fields. A basic +//! ActivityStreams object will implement `AsRef`, while an ActivityPub Actor +//! might implement `AsRef`, `AsRef`, and +//! `AsRef`. +//! +//! The Properties types can be found near the kind they're associated with. `ObjectProperties` and +//! `ApObjectProperties` are located in `activitystreams::object::properties`. +//! +//! The Properties types are generated by the `properties` macro, which attempts to create fields +//! that represent exactly the bounds of the ActivityStreams and ActivityPub specifications. +//! +//! For example, the Object type in ActivityStreams has a `summary` field, which can either be +//! represented as an `xsd:string` or an `rdf:langString`. It also states that the `summary` field +//! is not `functional`, meaning that any number of `xsd:string` or `rdf:langString`, or a +//! combination thereof, can be present. To represent this, the `properties` macro generates a +//! couple `enum` types. +//! +//! First, it generates `ObjectPropertiesSummaryTermEnum`, which is a "terminating" enum. +//! "terminating" in this context means it is the smallest unit of the type. This enum has two +//! variants, named after the types they contain, `XsdString(...)` and `RdfLangString(...)`. +//! +//! Next, it generates `ObjectPropertiesSummaryEnum`, which contains two variants, `Term(...)` and +//! `Array(...)`. The `Term` variant contains an `ObjectPropertiesSummaryTermEnum`, and the `Array` +//! variant contains a `Vec`. +//! +//! Finally, when declaring the field, it generates `summary: Option`, +//! since `summary` is not a required field. +//! +//! This resulting type is exactly specific enough to match the following valid ActivityStreams +//! json, without matching any invalid json. +//! +//! With no summary: +//! ```json +//! {} +//! ``` +//! +//! With a sring summary: +//! ```json +//! { +//! "summary": "A string" +//! } +//! ``` +//! +//! With an rdf langstring +//! ```json +//! { +//! "summary": { +//! "@value": "A string", +//! "@language": "en" +//! } +//! } +//! ``` +//! +//! With multiple values +//! ```json +//! { +//! "summary": [ +//! { +//! "@value": "A string", +//! "@language": "en" +//! }, +//! "An xsd:string this time", +//! ] +//! } +//! ``` +//! +//! It may seem like interacting with these types might get unweildy, so the `properties` macro +//! also generates methods for interacting with each field. +//! +//! ```ignore +//! fn set_summary_xsd_string(&mut self, T) -> Result<...>; +//! fn set_summary_rdf_lang_string(&mut self, T) -> Result<...>; +//! fn set_many_summary_xsd_strings(&mut self, Vec) -> Result<...>; +//! fn set_many_summary_rdf_lang_strings(&mut self, Vec) -> Result<...>; +//! +//! fn delete_summary(&mut self) -> &mut Self; +//! +//! fn get_summary_xsd_string(&self) -> Option; +//! fn get_summary_rdf_lang_string(&self) -> Option; +//! fn get_many_summary_xsd_strings(&self) -> Option>; +//! fn get_many_summary_rdf_lang_strings(&self) -> Option>; +//! ``` +//! These methods provide access to setting and fetching uniformly typed data, as well as deleting +//! the data. In the setter methods, the type parameter T is bound by +//! `TryInto` or `TryInto`. This allows passing values to the method that +//! can be converted into the types, rather than requiring the caller to perform the conversion. +//! +//! Types like `XsdString` and `RdfLangString` can be found in the `primitives` module. Unless +//! you're building your own custom types, you shouldn't need to import them yourself. They each +//! implement `FromStr` for parsing and `Display` to convert back to strings, as well as `From` and +//! `Into` or `TryFrom` and `TryInto` for types you might expect them to (e.g. +//! `XsdNonNegativeInteger` implements `From` and `Into`). +//! +//! For some fields, like `id`, there is only one valid type. methods generated for fields like +//! these will leave out the type name from the function name. +//! +//! ```ignore +//! fn set_id(&mut self, T) -> Result<...>; +//! fn delete_id(&mut self) -> &mut Self; +//! fn get_id(&self) -> Option; +//! ``` +//! +//! ### Traits +//! +//! This library provides a number of traits, such as `Object`, `Link`, `Actor`, `Activity`, +//! `Collection`, and `CollectionPage`. The majority of these traits exist solely to "mark" types, +//! meaning they don't provide value, at runtime, but exist to add constraints to generics at +//! compiletime. +//! +//! If you want to make a function that manipulates an Activity, but not a normal object, you could +//! bound the function like so: +//! ```ignore +//! fn my_manipulator(some_activity: T) -> Result<&mut ObjectProperties, SomeErrorType> +//! where +//! T: Activity + AsMut, +//! { +//! some_activity.as_mut().set_whatever_tbh() +//! } +//! ``` +//! +//! ### Kinds +//! +//! This library has a set of unit structs that serialize and deserialize to strings. This is to +//! enable different ActivityPub Object types to be deserialized into different Named structs. +//! These can be found in `activitystreams::objects::kind`, and similar paths. +//! +//! To build your own Person struct, for example, you could write +//! ```ignore +//! use activitystreams::actor::kind::PersonType; +//! +//! #[derive(serde::Deserialize, serde::Serialize)] +//! pub struct MyPerson { +//! // Do a rename since `type` is not a valid rust field name +//! #[serde(rename = "type")] +//! kind: PersonType, +//! } +//! ``` +//! And this type would only deserialize for JSON where `"type":"Person"` +//! +//! ### Features +//! There are a number of features that can be disabled in this crate. By default, everything is +//! enabled. +//! +//! ```toml +//! activitystreams = { version = "0.4.0", default-features = "false", features = ["derive"] } +//! ``` +//! +//! | feature | what you get | +//! | ---------- | --------------------------------------------------------- | +//! | none | Just the Marker Traits | +//! | derive | Marker Traits + derive macros from activitystreams-derive | +//! | kinds | Marker Traits + derive macros + Kind UnitStructs | +//! | primitives | Marker Traits + Primitive values | +//! | types | Everything, this is the default | +//! //! ## Examples //! //! ### Basic //! //! ```rust +//! use activitystreams::object::{streams::Video, properties::ObjectProperties}; +//! use anyhow::Error; +//! +//! // We perform configuration in a dedicated function to specify which Properties type we want to +//! // perform the operations on. +//! fn configure_video(mut v: impl AsMut) -> Result<(), Error> { +//! v.as_mut() +//! .set_context_xsd_any_uri("https://www.w3.org/ns/activitystreams")? +//! .set_id("https://example.com/@example/lions")? +//! .set_url_xsd_any_uri("https://example.com/@example/lions/video.webm")? +//! .set_name_xsd_string("My Cool Video")? +//! .set_summary_xsd_string("A video about some cool lions")? +//! .set_media_type("video/webm")? +//! .set_duration("PT4M20S")?; +//! +//! Ok(()) +//! } +//! +//! fn main() -> Result<(), Error> { +//! let mut v = Video::default(); +//! +//! configure_video(&mut v)?; +//! +//! println!("Video, {:#?}", v); +//! +//! let s = serde_json::to_string(&v)?; +//! +//! println!("json, {}", s); +//! +//! let v: Video = serde_json::from_str(&s)?; +//! +//! println!("Video again, {:#?}", v); +//! +//! Ok(()) +//! } +//! ``` +//! +//! ### Intermediate +//! +//! ```rust //! use activitystreams::{ //! context, //! object::{ @@ -33,7 +256,7 @@ //! ObjectProperties, //! ProfileProperties //! }, -//! streams::Profile, +//! apub::Profile, //! }, //! primitives::XsdAnyUri, //! Actor, @@ -145,7 +368,7 @@ //! #[activitystreams(None)] //! pub my_properties: MyProperties, //! -//! /// Derive AsRef/AsMut/Link for My -> MyProperties +//! /// Derive AsRef/AsMut/Link for My -> LinkProperties //! #[activitystreams(Link)] //! pub link_properties: LinkProperties, //! }