activitystreams/README.md

294 lines
9.2 KiB
Markdown
Raw Normal View History

2018-05-14 06:18:12 +00:00
# ActivityStreams
2020-07-25 22:06:27 +00:00
_A set of Traits and Types that make up the ActivityStreams and ActivityPub specifications_
2018-05-14 06:18:12 +00:00
2021-02-11 01:45:35 +00:00
- Find the code on [git.asonix.dog](https://git.asonix.dog/asonix/activitystreams)
2020-07-25 22:06:27 +00:00
- Read the docs on [docs.rs](https://docs.rs/activitystreams)
2021-02-11 01:45:35 +00:00
- 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)
2019-07-30 22:02:05 +00:00
2018-05-14 06:18:12 +00:00
## Usage
2020-03-12 02:45:53 +00:00
First, add ActivityStreams to your dependencies
2018-05-14 06:18:12 +00:00
```toml
2020-07-25 22:06:27 +00:00
[dependencies]
2022-02-13 20:24:07 +00:00
activitystreams = "0.7.0-alpha.19"
2018-05-14 06:18:12 +00:00
```
2020-03-12 02:45:53 +00:00
### Types
2020-07-25 22:06:27 +00:00
The project is laid out by Kind => Type
2020-03-12 02:45:53 +00:00
So to use an ActivityStreams Video, you'd write
2018-05-14 06:18:12 +00:00
```rust
2020-07-25 22:06:27 +00:00
use activitystreams::object::Video;
let video = Video::new();
2020-03-12 02:45:53 +00:00
```
2020-03-10 20:26:08 +00:00
2020-03-12 02:45:53 +00:00
And to use an ActivityPub profile, you'd write
```rust
2020-07-25 22:06:27 +00:00
use activitystreams::object::{ApObject, Profile};
let inner = Profile::new();
let profile = ApObject::new(inner);
2020-03-12 02:45:53 +00:00
```
2020-07-25 22:06:27 +00:00
There's only one kind of Link
2020-03-12 02:45:53 +00:00
```rust
use activitystreams::link::Mention;
2020-07-25 22:06:27 +00:00
let mention = Mention::new();
2020-03-12 02:45:53 +00:00
```
2020-07-25 22:06:27 +00:00
### Fields
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
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.
2020-03-12 02:45:53 +00:00
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
2020-07-25 22:06:27 +00:00
combination thereof, can be present. This library represents this as `Option<OneOrMany<AnyString>>`.
2020-03-12 02:45:53 +00:00
This resulting type is exactly specific enough to match the following valid ActivityStreams
json, without matching any invalid json.
With no summary:
```json
{}
```
2020-07-25 22:06:27 +00:00
With a string summary:
2020-03-12 02:45:53 +00:00
```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"
]
}
```
2020-07-25 22:06:27 +00:00
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.
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
```rust,ignore
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<&str>;
fn as_single_rdf_langstring(&self) -> Option<&RdfLangString>;
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
fn single_xsd_string(self) -> Option<String>;
fn single_rdf_lang_string(self) -> Option<RdfLangString>;
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
fn add_xsd_string<T>(&mut self, T) -> &mut Self;
fn add_rdf_lang_string<T>(&mut self, T) -> &mut Self;
2020-03-12 02:45:53 +00:00
```
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
2020-07-25 22:06:27 +00:00
`Into<String>` or `Into<RdfLangString>`. This allows passing values to the method that
2020-03-12 02:45:53 +00:00
can be converted into the types, rather than requiring the caller to perform the conversion.
2020-07-25 22:06:27 +00:00
Types like `RdfLangString` can be found in the `primitives` module. Unless
2020-03-12 02:45:53 +00:00
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>`).
2020-07-25 22:06:27 +00:00
### Traits
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
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<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,ignore
fn id(&self) -> Option<&XsdAnyUri>;
fn set_id(&mut self, XsdAnyUri) -> &mut Self;
fn take_id(&self) -> Option<XsdAnyUri>;
2020-03-12 02:45:53 +00:00
fn delete_id(&mut self) -> &mut Self;
```
2020-07-25 22:06:27 +00:00
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
2020-03-12 02:45:53 +00:00
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:
2020-07-25 22:06:27 +00:00
2020-03-13 21:22:08 +00:00
```rust
2022-02-13 20:24:07 +00:00
use activitystreams::{base::BaseExt, context, markers::Activity, iri};
2020-07-25 22:06:27 +00:00
fn manipulator<T, Kind>(mut activity: T) -> Result<(), anyhow::Error>
2020-03-12 02:45:53 +00:00
where
2020-07-25 22:06:27 +00:00
T: Activity + BaseExt<Kind>,
2020-03-12 02:45:53 +00:00
{
2020-07-25 22:06:27 +00:00
activity
2022-02-13 20:24:07 +00:00
.set_id(iri!("https://example.com"))
2020-07-25 22:06:27 +00:00
.set_context(context());
Ok(())
2020-03-12 02:45:53 +00:00
}
```
### 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
2020-03-13 21:22:08 +00:00
```rust
2020-03-12 02:45:53 +00:00
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
2020-07-25 22:06:27 +00:00
### Create
2020-03-12 02:45:53 +00:00
```rust
2020-07-25 22:06:27 +00:00
use activitystreams::{
context,
object::{ApObject, Video},
prelude::*,
2022-02-13 20:24:07 +00:00
iri,
2020-07-25 22:06:27 +00:00
};
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
fn main() -> Result<(), anyhow::Error> {
let mut video = ApObject::new(Video::new());
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
video
.set_context(context())
2022-02-13 20:24:07 +00:00
.set_id(iri!("https://example.com/@example/lions"))
2020-07-25 22:06:27 +00:00
.set_media_type("video/webm".parse()?)
2022-02-13 20:24:07 +00:00
.set_url(iri!("https://example.com/@example/lions/video.webm"))
2020-07-25 22:06:27 +00:00
.set_summary("A cool video")
.set_duration("PT4M20S".parse()?)
2022-02-13 20:24:07 +00:00
.set_shares(iri!("https://example.com/@example/lions/video.webm#shares"));
2020-03-12 02:45:53 +00:00
2020-07-25 22:06:27 +00:00
println!("Video, {:#?}", video);
2020-03-10 20:26:08 +00:00
2020-07-25 22:06:27 +00:00
let s = serde_json::to_string(&video)?;
2020-03-10 20:26:08 +00:00
println!("json, {}", s);
2020-07-25 22:06:27 +00:00
let v: ApObject<Video> = serde_json::from_str(&s)?;
2020-03-10 20:26:08 +00:00
println!("Video again, {:#?}", v);
Ok(())
}
```
2020-07-25 22:06:27 +00:00
### Parse
2020-03-10 20:26:08 +00:00
```rust
2020-07-25 22:06:27 +00:00
use activitystreams::{activity::ActorAndObject, prelude::*};
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub enum AcceptedTypes {
Accept,
Announce,
Create,
Delete,
Follow,
Reject,
Update,
Undo,
2018-05-14 06:18:12 +00:00
}
2020-07-25 22:06:27 +00:00
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")),
}
2018-05-14 06:18:12 +00:00
Ok(())
}
2020-03-10 20:26:08 +00:00
2020-07-25 22:06:27 +00:00
static EXAMPLE_JSON: &str = r#"{"actor":"https://asonix.dog/users/asonix","object":"https://asonix.dog/users/asonix/posts/1","type":"Announce"}"#;
2018-05-14 06:18:12 +00:00
2020-03-12 02:45:53 +00:00
fn main() -> Result<(), anyhow::Error> {
2020-07-25 22:06:27 +00:00
handle_activity(serde_json::from_str(EXAMPLE_JSON)?)
2018-05-14 06:18:12 +00:00
}
```
## 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
2020-07-25 22:06:27 +00:00
Copyright © 2020 Riley Trautman
2018-05-14 06:18:12 +00:00
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/).