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);
+ }
+}