activitystreams-new/README.md

291 lines
9.1 KiB
Markdown

# ActivityStreams New
__A set of Traits and Types that make up the ActivityStreams and ActivityPub specifications__
## Usage
First, add ActivityStreams to your dependencies
```toml
[dependencies]
activitystreams-new = { git = "https://git.asonix.dog/asonix/activitystreams-sketch" }
[patch.crates-io]
typed-builder = { git = "https://git.asonix.dog/asonix/typed-builder" }
```
### Types
The project is laid out by Kind => Type
So to use an ActivityStreams Video, you'd write
```rust
use activitystreams_new::object::Video;
let video = Video::new();
```
And to use an ActivityPub profile, you'd write
```rust
use activitystreams_new::object::{ApObject, Profile};
let inner = Profile::new();
let profile = ApObject::new(inner);
```
There's only one kind of Link
```rust
use activitystreams_new::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<OneOrMany<AnyString>>`.
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, there are some custom methods
implemented on the `OneOrMany` type depending on what's inside of it.
```rust
fn from_xsd_string<T>(&mut self, T) -> Self;
fn from_rdf_lang_string<T>(&mut self, T) -> Self;
fn as_single_xsd_string(&self) -> Option<&XsdString>;
fn as_single_rdf_langstring(&self) -> Option<&RdfLangString>;
fn single_xsd_string(self) -> Option<XsdString>;
fn single_rdf_lang_string(self) -> Option<RdfLangString>;
fn add_xsd_string<T>(&mut self, T) -> &mut Self;
fn add_rdf_lang_string<T>(&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<XsdString>` or `Into<RdfLangString>`. 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<u64>` and `Into<u64>`).
### 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
fn context(&self) -> Option<&OneOrMany<AnyBase>>;
fn set_context<T>(&mut self, context: T) -> &mut Self
where
T: Into<AnyBase>;
fn set_many_contexts<I, T>(&mut self, items: I) -> &mut Self
where
I: IntoIterator<Item = T>,
T: Into<AnyBase>;
fn add_context<T>(&mut self, context: T) -> &mut Self
where
T: Into<AnyBase>;
fn take_context(&mut self) -> Option<OneOrMany<AnyBase>>;
fn delete_context(&mut self) -> &mut Self;
```
For fields with more specific bounds, like `id`,
```rust
fn id(&self) -> Option<&XsdAnyUri>;
fn set_id(&mut self, XsdAnyUri) -> &mut Self;
fn take_id(&self) -> Option<XsdAnyUri>;
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_new::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_new::{base::BaseExt, context, markers::Activity};
fn manipulator<T>(mut activity: T) -> Result<(), SomeErrorType>
where
T: Activity + BaseExt<Kind>,
{
activity
.set_id("https://example.com".parse()?)
.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_new::objects::kind`, and similar paths.
To build your own Person struct, for example, you could write
```rust
use activitystreams_new::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_new::{
context,
object::{ApObject, Video},
prelude::*,
primitives::{XsdAnyUri, XsdString},
};
fn main() -> Result<(), anyhow::Error> {
let mut video = ApObject::new(Video::new());
video
.set_context(context())
.set_id("https://example.com/@example/lions".parse()?)
.set_media_type("video/webm".parse()?)
.set_url("https://example.com/@example/lions/video.webm".parse::<XsdAnyUri>()?)
.set_summary(XsdString::from("A cool video"))
.set_duration("PT4M20S".parse()?)
.set_shares("https://example.com/@example/lions/video.webm#shares".parse()?);
println!("Video, {:#?}", video);
let s = serde_json::to_string(&video)?;
println!("json, {}", s);
let v: ApObject<Video> = serde_json::from_str(&s)?;
println!("Video again, {:#?}", v);
Ok(())
}
```
### Parse
```rust
use activitystreams_new::{activity::ActorAndObject, prelude::*};
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub enum AcceptedTypes {
Accept,
Announce,
Create,
Delete,
Follow,
Reject,
Update,
Undo,
}
pub type AcceptedActivity = ActorAndObject<AcceptedTypes>;
pub fn handle_activity(activity: AcceptedActivity) -> Result<(), anyhow::Error> {
println!("Actor: {:?}", activity.actor());
println!("Object: {:?}", activity.object());
match activity.kind() {
Some(AcceptedTypes::Accept) => println!("Accept"),
Some(AcceptedTypes::Announce) => println!("Announce"),
Some(AcceptedTypes::Create) => println!("Create"),
Some(AcceptedTypes::Delete) => println!("Delete"),
Some(AcceptedTypes::Follow) => println!("Follow"),
Some(AcceptedTypes::Reject) => println!("Reject"),
Some(AcceptedTypes::Update) => println!("Update"),
Some(AcceptedTypes::Undo) => println!("Undo"),
None => return Err(anyhow::Error::msg("No activity type provided")),
}
Ok(())
}
static EXAMPLE_JSON: &str = r#"{"actor":"https://asonix.dog/users/asonix","object":"https://asonix.dog/users/asonix/posts/1","type":"Announce"}"#;
fn main() -> Result<(), anyhow::Error> {
handle_activity(serde_json::from_str(EXAMPLE_JSON)?)
}
```
## Contributing
Feel free to open issues for anything you find an issue with. Please note that any contributed code will be licensed under the GPLv3.
## License
Copyright © 2020 Riley Trautman
ActivityStreams is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
ActivityStreams is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This file is part of ActivityStreams.
You should have received a copy of the GNU General Public License along with ActivityStreams. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).