# ActivityStreams _A set of Traits and Types that make up the ActivityStreams and ActivityPub specifications_ - Find the code on [git.asonix.dog](https://git.asonix.dog/asonix/activitystreams) - Read the docs on [docs.rs](https://docs.rs/activitystreams) - Join the matrix channel at [#activitystreams:asonix.dog](https://matrix.to/#/#activitystreams:matrix.asonix.dog?via=matrix.asonix.dog) - Hit me up on [mastodon](https://masto.asonix.dog/@asonix) ## Usage First, add ActivityStreams to your dependencies ```toml [dependencies] activitystreams = "0.7.0-alpha.19" ``` ### Types The project is laid out by Kind => Type So to use an ActivityStreams Video, you'd write ```rust use activitystreams::object::Video; let video = Video::new(); ``` And to use an ActivityPub profile, you'd write ```rust use activitystreams::object::{ApObject, Profile}; let inner = Profile::new(); let profile = ApObject::new(inner); ``` There's only one kind of Link ```rust use activitystreams::link::Mention; let mention = Mention::new(); ``` ### Fields Many fields on the provided types are wrapped in `OneOrMany<>` or have a type of `AnyBase`. This is because the activitystreams spec is very open as to what is considered a valid structure. 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. This library represents this as `Option>`. 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 string 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, there are some custom methods implemented on the `OneOrMany` type depending on what's inside of it. ```rust,ignore fn from_xsd_string(&mut self, T) -> Self; fn from_rdf_lang_string(&mut self, T) -> Self; fn as_single_xsd_string(&self) -> Option<&str>; fn as_single_rdf_langstring(&self) -> Option<&RdfLangString>; fn single_xsd_string(self) -> Option; fn single_rdf_lang_string(self) -> Option; fn add_xsd_string(&mut self, T) -> &mut Self; fn add_rdf_lang_string(&mut self, T) -> &mut Self; ``` 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 `Into` or `Into`. 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 `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`). ### Traits Since ActivityStreams is a heirarchical structure of data, it's represented as structs containing other structs. This means that the `context` field, which can be present on any ActivityStreams type, will be located in the innermost struct. In order to avoid writing code like `ap_object.collection.object.base.context = Some(context())`, this library provides traits that are automatically implmeneted for provided types. For example, the `BaseExt` trait provides the following methods for `context`, ```rust,ignore fn context(&self) -> Option<&OneOrMany>; fn set_context(&mut self, context: T) -> &mut Self where T: Into; fn set_many_contexts(&mut self, items: I) -> &mut Self where I: IntoIterator, T: Into; fn add_context(&mut self, context: T) -> &mut Self where T: Into; fn take_context(&mut self) -> Option>; fn delete_context(&mut self) -> &mut Self; ``` For fields with more specific bounds, like `id`, ```rust,ignore fn id(&self) -> Option<&XsdAnyUri>; fn set_id(&mut self, XsdAnyUri) -> &mut Self; fn take_id(&self) -> Option; fn delete_id(&mut self) -> &mut Self; ``` The full list of extension traits that implement methods like these on types can be found in the prelude module. By using `use activitystreams::prelude::*;` all of the methods will be implemented for types containing their fields. ### Markers 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: ```rust use activitystreams::{base::BaseExt, context, markers::Activity, iri}; fn manipulator(mut activity: T) -> Result<(), anyhow::Error> where T: Activity + BaseExt, { activity .set_id(iri!("https://example.com")) .set_context(context()); Ok(()) } ``` ### 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 ```rust 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"` ## Examples ### Create ```rust use activitystreams::{ context, object::{ApObject, Video}, prelude::*, iri, }; fn main() -> Result<(), anyhow::Error> { let mut video = ApObject::new(Video::new()); video .set_context(context()) .set_id(iri!("https://example.com/@example/lions")) .set_media_type("video/webm".parse()?) .set_url(iri!("https://example.com/@example/lions/video.webm")) .set_summary("A cool video") .set_duration("PT4M20S".parse()?) .set_shares(iri!("https://example.com/@example/lions/video.webm#shares")); println!("Video, {:#?}", video); let s = serde_json::to_string(&video)?; println!("json, {}", s); let v: ApObject