From c69027638f95a8fc1e5e8d7f8e9287be006260d9 Mon Sep 17 00:00:00 2001 From: alemi Date: Sat, 30 Dec 2023 05:08:05 +0100 Subject: [PATCH] feat: add simple models actor, webfinger, activity, object thanks gargron https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/ --- Cargo.toml | 11 ++++++++ src/main.rs | 5 ++++ src/model/activity.rs | 45 +++++++++++++++++++++++++++++++++ src/model/actor.rs | 57 ++++++++++++++++++++++++++++++++++++++++++ src/model/mod.rs | 4 +++ src/model/object.rs | 57 ++++++++++++++++++++++++++++++++++++++++++ src/model/webfinger.rs | 40 +++++++++++++++++++++++++++++ 7 files changed, 219 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 src/model/activity.rs create mode 100644 src/model/actor.rs create mode 100644 src/model/mod.rs create mode 100644 src/model/object.rs create mode 100644 src/model/webfinger.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6a0db62 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "anwt" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.31", features = ["serde"] } +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d94b81c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +pub mod model; + +fn main() { + println!("Hello, world!"); +} diff --git a/src/model/activity.rs b/src/model/activity.rs new file mode 100644 index 0000000..947d029 --- /dev/null +++ b/src/model/activity.rs @@ -0,0 +1,45 @@ +use serde::{Serialize, Deserialize}; + +use super::object::Object; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Activity { + #[serde(rename = "@context")] + context: String, + + id: String, + + #[serde(rename = "type")] + activity_type: ActivityType, + + actor: String, + + object: Object, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ActivityType { + Create, +} + +#[cfg(test)] +mod test { + use super::{Activity, ActivityType}; + use crate::model::object::Object; + + #[test] + fn activity_serializes_as_expected() { + let activity = Activity { + context: "https://www.w3.org/ns/activitystreams".into(), + id: "https://my-example.com/create-hello-world".into(), + activity_type: ActivityType::Create, + actor: "https://my-example.com/actor".into(), + object: Object::default(), + }; + + let serialized_activity = serde_json::to_string(&activity).unwrap(); + let expected_serialized_activity = "{\"@context\":\"https://www.w3.org/ns/activitystreams\",\"id\":\"https://my-example.com/create-hello-world\",\"type\":\"Create\",\"actor\":\"https://my-example.com/actor\",\"object\":{\"id\":\"https://my-example.com/hello-world\",\"type\":\"Note\",\"published\":\"2018-06-23T17:17:11Z\",\"attributedTo\":\"https://my-example.com/actor\",\"inReplyTo\":\"https://mastodon.social/@Gargron/100254678717223630\",\"content\":\"

Hello world

\",\"to\":\"https://www.w3.org/ns/activitystreams#Public\"}}"; + + assert_eq!(serialized_activity, expected_serialized_activity); + } +} diff --git a/src/model/actor.rs b/src/model/actor.rs new file mode 100644 index 0000000..87db2ca --- /dev/null +++ b/src/model/actor.rs @@ -0,0 +1,57 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Actor { + #[serde(rename = "@context")] + pub context: Vec, // note: must be @context + pub id: String, + #[serde(rename = "type")] + pub actor_type: ActorType, + #[serde(rename = "preferredUsername")] + pub preferred_username: String, + pub inbox: String, + #[serde(rename = "publicKey")] + pub public_key: PublicKey, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PublicKey { + pub id: String, + pub owner: String, + #[serde(rename = "publicKeyPem")] + pub public_key_pem: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ActorType { + Person, +} + +#[cfg(test)] +mod test { + use super::{Actor, ActorType, PublicKey}; + + #[test] + fn actor_serializes_as_expected() { + let actor = Actor { + context: vec![ + "https://www.w3.org/ns/activitystreams".into(), + "https://w3id.org/security/v1".into() + ], + id: "https://my-example.com/actor".into(), + actor_type: ActorType::Person, + preferred_username: "alice".into(), + inbox: "https://my-example.com/inbox".into(), + public_key: PublicKey { + id: "https://my-example.com/actor#main-key".into(), + owner: "https://my-example.com/actor".into(), + public_key_pem: "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----".into(), + }, + }; + + let serialized_actor = serde_json::to_string(&actor).unwrap(); + let expected_serialized_actor = "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\"id\":\"https://my-example.com/actor\",\"type\":\"Person\",\"preferredUsername\":\"alice\",\"inbox\":\"https://my-example.com/inbox\",\"publicKey\":{\"id\":\"https://my-example.com/actor#main-key\",\"owner\":\"https://my-example.com/actor\",\"publicKeyPem\":\"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----\"}}"; + + assert_eq!(expected_serialized_actor, serialized_actor); + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..7754fd8 --- /dev/null +++ b/src/model/mod.rs @@ -0,0 +1,4 @@ +pub mod activity; +pub mod actor; +pub mod webfinger; +pub mod object; diff --git a/src/model/object.rs b/src/model/object.rs new file mode 100644 index 0000000..882b51a --- /dev/null +++ b/src/model/object.rs @@ -0,0 +1,57 @@ +use chrono::{Utc, DateTime}; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Object { + id: String, + + #[serde(rename = "type")] + object_type: ObjectType, + + published: DateTime, + + #[serde(rename = "attributedTo")] + attributed_to: String, + + #[serde(rename = "inReplyTo")] + in_reply_to: String, + + content: String, + + to: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ObjectType { + Note, +} + +#[cfg(test)] +impl Default for Object { + fn default() -> Self { + Object { + id: "https://my-example.com/hello-world".into(), + object_type: ObjectType::Note, + published: DateTime::parse_from_rfc3339("2018-06-23T17:17:11Z").unwrap().into(), + attributed_to: "https://my-example.com/actor".into(), + in_reply_to: "https://mastodon.social/@Gargron/100254678717223630".into(), + content: "

Hello world

".into(), + to: "https://www.w3.org/ns/activitystreams#Public".into(), + } + } +} + +#[cfg(test)] +mod test { + use super::Object; + + #[test] + fn object_serializes_as_expected() { + let object = Object::default(); + + let serialized_object = serde_json::to_string(&object).unwrap(); + let expected_serialized_object = "{\"id\":\"https://my-example.com/hello-world\",\"type\":\"Note\",\"published\":\"2018-06-23T17:17:11Z\",\"attributedTo\":\"https://my-example.com/actor\",\"inReplyTo\":\"https://mastodon.social/@Gargron/100254678717223630\",\"content\":\"

Hello world

\",\"to\":\"https://www.w3.org/ns/activitystreams#Public\"}"; + + assert_eq!(serialized_object, expected_serialized_object); + } +} diff --git a/src/model/webfinger.rs b/src/model/webfinger.rs new file mode 100644 index 0000000..54c1cad --- /dev/null +++ b/src/model/webfinger.rs @@ -0,0 +1,40 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Webfinger { + subject: String, + links: Vec, +} + + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WebfingerLink { + rel: String, + #[serde(rename = "type")] + link_type: String, + href: String, +} + +#[cfg(test)] +mod test { + use super::{Webfinger, WebfingerLink}; + + #[test] + fn webfinger_serializes_as_expected() { + let webfinger = Webfinger { + subject: "acct:alice@my-example.com".into(), + links: vec![ + WebfingerLink { + rel: "self".into(), + link_type: "application/activity+json".into(), + href: "https://my-example.com/actor".into(), + }, + ], + }; + + let serialized_webfinger = serde_json::to_string(&webfinger).unwrap(); + let expected_serialized_webfinger = "{\"subject\":\"acct:alice@my-example.com\",\"links\":[{\"rel\":\"self\",\"type\":\"application/activity+json\",\"href\":\"https://my-example.com/actor\"}]}"; + + assert_eq!(serialized_webfinger, expected_serialized_webfinger); + } +}