feat: refactored and expanded activitypub types

This commit is contained in:
əlemi 2024-03-19 05:15:41 +01:00
parent 7d69f8148c
commit a73852b3b7
Signed by: alemi
GPG key ID: A4895B84D311642C
21 changed files with 599 additions and 330 deletions

View file

@ -9,6 +9,7 @@ edition = "2021"
axum = "0.7.3" axum = "0.7.3"
chrono = { version = "0.4.31", features = ["serde"] } chrono = { version = "0.4.31", features = ["serde"] }
clap = { version = "4.5.3", features = ["derive"] } clap = { version = "4.5.3", features = ["derive"] }
reqwest = { version = "0.11.26", features = ["json"] }
sea-orm = { version = "0.12.14", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] } sea-orm = { version = "0.12.14", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
sea-orm-migration = "0.12.15" sea-orm-migration = "0.12.15"
serde = { version = "1.0.193", features = ["derive"] } serde = { version = "1.0.193", features = ["derive"] }

View file

@ -1,56 +0,0 @@
pub trait Activity : super::Object {
fn activity_type(&self) -> Option<super::types::ActivityType> { None }
fn actor_id(&self) -> Option<&str> { None }
fn actor(&self) -> Option<super::LinkedObject<impl super::Object>> { None::<super::LinkedObject<()>> }
fn object_id(&self) -> Option<&str> { None }
fn object(&self) -> Option<super::LinkedObject<impl super::Object>> { None::<super::LinkedObject<()>> }
fn target(&self) -> Option<&str> { None }
}
impl Activity for serde_json::Value {
fn activity_type(&self) -> Option<super::types::ActivityType> {
let serde_json::Value::String(t) = self.get("type")? else { return None };
super::types::ActivityType::try_from(t.as_str()).ok()
}
fn object(&self) -> Option<super::LinkedObject<impl super::Object>> {
let obj = self.get("object")?;
match obj {
serde_json::Value::Object(_) => Some(obj.clone().into()),
_ => None,
}
}
fn object_id(&self) -> Option<&str> {
match self.get("object")? {
serde_json::Value::Object(map) => match map.get("id")? {
serde_json::Value::String(id) => Some(id),
_ => None,
},
serde_json::Value::String(id) => Some(id),
_ => None,
}
}
fn actor(&self) -> Option<super::LinkedObject<impl super::Object>> {
let obj = self.get("actor")?;
match obj {
serde_json::Value::Object(_) => Some(obj.clone().into()),
_ => None,
}
}
fn actor_id(&self) -> Option<&str> {
match self.get("actor")? {
serde_json::Value::Object(map) => match map.get("id")? {
serde_json::Value::String(id) => Some(id),
_ => None,
},
serde_json::Value::String(id) => Some(id),
_ => None,
}
}
}

View file

@ -1,3 +0,0 @@
pub trait Actor : super::Object {
fn actor_type(&self) -> Option<super::ActorType> { None }
}

View file

@ -1,4 +1,13 @@
pub trait Link { use crate::strenum;
strenum! {
pub enum LinkType {
Link,
Mention
}
}
pub trait Link : super::Base {
fn href(&self) -> &str; fn href(&self) -> &str;
fn rel(&self) -> Option<&str> { None } fn rel(&self) -> Option<&str> { None }
fn media_type(&self) -> Option<&str> { None } // also in obj fn media_type(&self) -> Option<&str> { None } // also in obj
@ -9,27 +18,6 @@ pub trait Link {
fn preview(&self) -> Option<&str> { None } // also in obj fn preview(&self) -> Option<&str> { None } // also in obj
} }
pub enum LinkedObject<T> {
Object(T),
Link(Box<dyn Link>),
}
impl<T> LinkedObject<T>
where
T : for<'de> serde::Deserialize<'de>,
{
pub async fn resolve(self) -> T {
match self {
LinkedObject::Object(o) => o,
LinkedObject::Link(l) =>
reqwest::get(l.href())
.await.unwrap()
.json::<T>()
.await.unwrap(),
}
}
}
impl Link for String { impl Link for String {
fn href(&self) -> &str { fn href(&self) -> &str {
self self
@ -53,14 +41,3 @@ impl Link for serde_json::Value {
// ... TODO! // ... TODO!
} }
impl From<serde_json::Value> for LinkedObject<serde_json::Value> {
fn from(value: serde_json::Value) -> Self {
if value.is_string() || value.get("href").is_some() {
Self::Link(Box::new(value))
} else {
Self::Object(value)
}
}
}

View file

@ -1,62 +1,48 @@
pub mod object;
pub use object::Object;
pub mod actor;
pub use actor::Actor;
pub mod activity;
pub use activity::Activity;
pub mod types; pub mod types;
pub use types::{BaseType, ObjectType, ActivityType, ActorType};
pub mod link; pub mod link;
pub use link::{Link, LinkedObject}; pub use link::{Link, LinkType};
pub trait ToJson : Object { pub mod object;
fn json(&self) -> serde_json::Value; pub use object::{Object, ObjectType};
}
impl<T> ToJson for T where T : Object { pub mod node;
fn json(&self) -> serde_json::Value { pub use node::Node;
let mut map = serde_json::Map::new();
let mp = &mut map;
put_str(mp, "id", self.id()); use crate::strenum;
put_str(mp, "attributedTo", self.attributed_to());
put_str(mp, "name", self.name());
put_str(mp, "summary", self.summary());
put_str(mp, "content", self.content());
if let Some(t) = self.full_type() { strenum! {
map.insert( pub enum BaseType {
"type".to_string(), Invalid
serde_json::Value::String(format!("{t:?}")),
);
}
if let Some(published) = self.published() { Object(ObjectType),
map.insert( Link(LinkType)
"published".to_string(),
serde_json::Value::String(published.to_rfc3339()),
);
}
// ... TODO!
serde_json::Value::Object(map)
} }
} }
fn put_str(map: &mut serde_json::Map<String, serde_json::Value>, k: &str, v: Option<&str>) { pub trait Base {
if let Some(v) = v { fn id(&self) -> Option<&str> { None }
map.insert( fn base_type(&self) -> Option<BaseType> { None }
k.to_string(), }
serde_json::Value::String(v.to_string()),
); impl Base for () {}
impl Base for String {
fn id(&self) -> Option<&str> {
Some(self)
}
fn base_type(&self) -> Option<BaseType> {
Some(BaseType::Link(LinkType::Link))
}
}
impl Base for serde_json::Value {
fn id(&self) -> Option<&str> {
self.get("id")?.as_str()
}
fn base_type(&self) -> Option<BaseType> {
self.get("type")?.as_str()?.try_into().ok()
} }
} }

166
src/activitystream/node.rs Normal file
View file

@ -0,0 +1,166 @@
#[derive(Debug, thiserror::Error)]
pub enum NodeResolutionError {
#[error("error fetching object: {0}")]
FetchError(#[from] reqwest::Error),
#[error("empty array")]
EmptyArray,
#[error("field not present")]
Empty,
}
pub enum Node<T> {
Array(Vec<T>),
Object(T),
Link(Box<dyn super::Link>),
Empty,
}
impl<T> From<Option<T>> for Node<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(x) => Node::Object(x),
None => Node::Empty,
}
}
}
impl<T> Node<T> {
pub fn first(&self) -> Option<&T> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(x),
Node::Array(v) => v.first(),
}
}
pub fn items(&self) -> Option<Vec<&T>> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(vec![x]),
Node::Array(v) =>
Some(v.iter().map(|x| &x).collect()),
}
}
}
impl<T> Node<T>
where
T : super::Base
{
pub fn id(&self) -> Option<&str> {
match self {
Node::Array(v) => v.first()?.id(),
Node::Link(x) => Some(x.href()),
Node::Object(x) => x.id(),
Node::Empty => None,
}
}
}
impl<T> Node<T>
where
T : Clone + for<'de> serde::Deserialize<'de>,
{
pub async fn resolve(self) -> Result<T, NodeResolutionError> {
match self {
Node::Empty => Err(NodeResolutionError::Empty),
Node::Object(object) => Ok(object),
Node::Array(array) => Ok(
array
.first()
.ok_or(NodeResolutionError::EmptyArray)?
.clone()
),
Node::Link(link) => Ok(
reqwest::get(link.href())
.await?
.json::<T>()
.await?
),
}
}
}
pub trait NodeExtractor {
fn node(&self, id: &str) -> Node<serde_json::Value>;
fn node_vec(&self, id: &str) -> Node<serde_json::Value>;
}
impl NodeExtractor for serde_json::Value {
fn node(&self, id: &str) -> Node<serde_json::Value> {
match self.get(id) {
None => Node::Empty,
Some(x) => match Node::new(x.clone()) {
Err(e) => Node::Empty,
Ok(x) => x,
}
}
}
fn node_vec(&self, id: &str) -> Node<serde_json::Value> {
match self.get(id) {
None => Node::Empty,
Some(x) => match Node::many(x.clone()) {
Err(e) => Node::Empty,
Ok(x) => x,
}
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("json object is wrongly structured")]
pub struct JsonStructureError;
impl Node<serde_json::Value> {
pub fn new(value: serde_json::Value) -> Result<Self, JsonStructureError> {
if !(value.is_string() || value.is_object()) {
return Err(JsonStructureError);
}
if value.is_string() || value.get("href").is_some() {
Ok(Self::Link(Box::new(value)))
} else {
Ok(Self::Object(value))
}
}
pub fn many(value: serde_json::Value) -> Result<Vec<Self>, JsonStructureError> {
if let serde_json::Value::Array(arr) = value {
Ok(
arr
.into_iter()
.filter_map(|x| Self::new(x.clone()).ok())
.collect()
)
} else {
Ok(vec![Self::new(value)?])
}
}
}
pub(crate) trait InsertStr {
fn insert_str(&mut self, k: &str, v: Option<&str>);
fn insert_timestr(&mut self, k: &str, t: Option<chrono::DateTime<chrono::Utc>>);
}
impl InsertStr for serde_json::Map<String, serde_json::Value> {
fn insert_str(&mut self, k: &str, v: Option<&str>) {
if let Some(v) = v {
self.insert(
k.to_string(),
serde_json::Value::String(v.to_string()),
);
}
}
fn insert_timestr(&mut self, k: &str, t: Option<chrono::DateTime<chrono::Utc>>) {
if let Some(published) = t {
self.insert(
"published".to_string(),
serde_json::Value::String(published.to_rfc3339()),
);
}
}
}

View file

@ -1,47 +0,0 @@
pub trait Object {
fn id(&self) -> Option<&str> { None }
fn full_type(&self) -> Option<super::BaseType> { None }
fn attachment (&self) -> Option<&str> { None }
fn attributed_to (&self) -> Option<&str> { None }
fn audience (&self) -> Option<&str> { None }
fn content (&self) -> Option<&str> { None }
fn context (&self) -> Option<&str> { None }
fn name (&self) -> Option<&str> { None }
fn end_time (&self) -> Option<&str> { None }
fn generator (&self) -> Option<&str> { None }
fn icon (&self) -> Option<&str> { None }
fn image (&self) -> Option<&str> { None }
fn in_reply_to (&self) -> Option<&str> { None }
fn location (&self) -> Option<&str> { None }
fn preview (&self) -> Option<&str> { None }
fn published (&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn replies (&self) -> Option<&str> { None }
fn start_time (&self) -> Option<&str> { None }
fn summary (&self) -> Option<&str> { None }
fn tag (&self) -> Option<&str> { None }
fn updated (&self) -> Option<&str> { None }
fn url (&self) -> Option<&str> { None }
fn to (&self) -> Option<&str> { None }
fn bto (&self) -> Option<&str> { None }
fn cc (&self) -> Option<&str> { None }
fn bcc (&self) -> Option<&str> { None }
fn media_type (&self) -> Option<&str> { None }
fn duration (&self) -> Option<&str> { None }
}
/// impl for empty object
impl Object for () {}
// TODO only Value::Object is a valid Object, but rn "asd" behaves like {} (both are valid...)
/// impl for any json value
impl Object for serde_json::Value {
fn id(&self) -> Option<&str> {
self.get("id")?.as_str()
}
fn full_type(&self) -> Option<super::BaseType> {
self.get("type")?.as_str()?.try_into().ok()
}
// ... TODO!
}

View file

@ -0,0 +1,12 @@
use crate::strenum;
strenum! {
pub enum AcceptType {
Accept,
TentativeAccept
}
}
pub trait Accept : super::Activity {
fn accept_type(&self) -> Option<AcceptType>;
}

View file

@ -0,0 +1,12 @@
use crate::strenum;
strenum! {
pub enum IgnoreType {
Ignore,
Block
}
}
pub trait Ignore : super::Activity {
fn ignore_type(&self) -> Option<IgnoreType>;
}

View file

@ -0,0 +1,14 @@
use crate::strenum;
strenum! {
pub enum IntransitiveActivityType {
IntransitiveActivity,
Arrive,
Question,
Travel
}
}
pub trait IntransitiveActivity : super::Activity {
fn intransitive_activity_type(&self) -> Option<IntransitiveActivityType>;
}

View file

@ -0,0 +1,72 @@
pub mod accept;
pub use accept::{Accept, AcceptType};
pub mod ignore;
pub use ignore::{Ignore, IgnoreType};
pub mod intransitive;
pub use intransitive::{IntransitiveActivity, IntransitiveActivityType};
pub mod offer;
pub use offer::{Offer, OfferType};
pub mod reject;
pub use reject::{Reject, RejectType};
use crate::activitystream::node::NodeExtractor;
use crate::activitystream::Node;
use crate::strenum;
strenum! {
pub enum ActivityType {
Activity,
Add,
Announce,
Create,
Delete,
Dislike,
Flag,
Follow,
Join,
Leave,
Like,
Listen,
Move,
Read,
Remove,
Undo,
Update,
View
IntransitiveActivity(IntransitiveActivityType),
Accept(AcceptType),
Ignore(IgnoreType),
Offer(OfferType),
Reject(RejectType)
}
}
pub trait Activity : super::Object {
fn activity_type(&self) -> Option<ActivityType> { None }
fn actor(&self) -> Node<impl super::Actor> { Node::Empty::<serde_json::Value> }
fn object(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn target(&self) -> Option<&str> { None }
fn result(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn origin(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn instrument(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
}
impl Activity for serde_json::Value {
fn activity_type(&self) -> Option<ActivityType> {
let serde_json::Value::String(t) = self.get("type")? else { return None };
ActivityType::try_from(t.as_str()).ok()
}
fn object(&self) -> Node<impl super::Object> {
self.node_vec("object")
}
fn actor(&self) -> Node<impl super::Actor> {
self.node_vec("actor")
}
}

View file

@ -0,0 +1,12 @@
use crate::strenum;
strenum! {
pub enum OfferType {
Offer,
Invite
}
}
pub trait Offer : super::Activity {
fn offer_type(&self) -> Option<OfferType>;
}

View file

@ -0,0 +1,12 @@
use crate::strenum;
strenum! {
pub enum RejectType {
Reject,
TentativeReject
}
}
pub trait Reject : super::Activity {
fn reject_type(&self) -> Option<RejectType>;
}

View file

@ -0,0 +1,28 @@
use crate::strenum;
strenum! {
pub enum ActorType {
Application,
Group,
Organization,
Person,
Object
}
}
pub trait Profile : super::Object {
// not a Node because it's always embedded and one
fn describes(&self) -> Option<impl super::Object> { None::<serde_json::Value> }
}
pub trait Actor : super::Object {
fn actor_type(&self) -> Option<ActorType> { None }
}
impl Actor for serde_json::Value {
}
impl Profile for serde_json::Value {
}

View file

@ -0,0 +1,32 @@
pub mod page;
pub use page::CollectionPage;
use crate::activitystream::Node;
use crate::strenum;
strenum! {
pub enum CollectionType {
Collection,
CollectionPage,
OrderedCollection,
OrderedCollectionPage
}
}
pub trait Collection : super::Object {
fn collection_type(&self) -> Option<CollectionType> { None }
fn total_items(&self) -> Option<u32> { None }
fn current(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
fn first(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
fn last(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
fn items(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
}
impl Collection for serde_json::Value {
}
impl CollectionPage for serde_json::Value {
}

View file

@ -0,0 +1,7 @@
use crate::activitystream::Node;
pub trait CollectionPage : super::Collection {
fn part_of(&self) -> Node<impl super::Collection> { Node::Empty::<serde_json::Value> }
fn next(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
fn prev(&self) -> Node<impl CollectionPage> { Node::Empty::<serde_json::Value> }
}

View file

@ -0,0 +1,34 @@
use crate::strenum;
strenum! {
pub enum DocumentType {
Document,
Audio,
Image,
Page,
Video
}
}
pub trait Document : super::Object {
fn document_type(&self) -> Option<DocumentType> { None }
}
pub trait Place : super::Object {
fn accuracy(&self) -> Option<f32> { None }
fn altitude(&self) -> Option<f32> { None }
fn latitude(&self) -> Option<f32> { None }
fn longitude(&self) -> Option<f32> { None }
fn radius(&self) -> Option<f32> { None }
fn units(&self) -> Option<&str> { None }
}
pub trait Image : Document {}
impl Document for serde_json::Value {
}
impl Image for serde_json::Value {}

View file

@ -0,0 +1,129 @@
pub mod actor;
pub use actor::{Actor, Profile, ActorType};
pub mod collection;
pub use collection::{Collection, CollectionPage, CollectionType};
pub mod document;
pub use document::{Document, Image, Place, DocumentType};
pub mod activity;
pub use activity::{Activity, ActivityType};
pub mod tombstone;
pub use tombstone::Tombstone;
pub mod relationship;
pub use relationship::Relationship;
use crate::strenum;
use super::{node::NodeExtractor, Node};
strenum! {
pub enum ObjectType {
Object,
Article,
Event,
Note,
Place,
Profile,
Relationship,
Tombstone
Activity(ActivityType),
Actor(ActorType),
Collection(CollectionType),
Document(DocumentType)
}
}
pub trait Object : super::Base {
fn object_type(&self) -> Option<ObjectType> { None }
fn attachment(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn attributed_to(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
fn audience(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
fn content(&self) -> Option<&str> { None } // TODO handle language maps
fn context(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn name(&self) -> Option<&str> { None } // also in link // TODO handle language maps
fn end_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn generator(&self) -> Node<impl Actor> { Node::Empty::<serde_json::Value> }
fn icon(&self) -> Node<impl Image> { Node::Empty::<serde_json::Value> }
fn image(&self) -> Node<impl Image> { Node::Empty::<serde_json::Value> }
fn in_reply_to(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn location(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn preview(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> } // also in link
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn replies(&self) -> Node<impl Collection> { Node::Empty::<serde_json::Value> }
fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn summary(&self) -> Option<&str> { None }
fn tag(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn updated(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn url(&self) -> Option<Vec<impl super::Link>> { None::<Vec<serde_json::Value>> }
fn to(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn bto(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn cc(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn bcc(&self) -> Node<impl Object> { Node::Empty::<serde_json::Value> }
fn media_type(&self) -> Option<&str> { None } // also in link
fn duration(&self) -> Option<&str> { None } // TODO how to parse xsd:duration ?
}
impl Object for serde_json::Value {
fn object_type(&self) -> Option<ObjectType> {
match self.base_type() {
Some(super::BaseType::Object(o)) => Some(o),
_ => None,
}
}
fn attachment(&self) -> Node<impl Object> {
self.node_vec("attachment")
}
fn attributed_to(&self) -> Node<impl Actor> {
self.node_vec("attributedTo")
}
fn audience(&self) -> Node<impl Actor> {
self.node_vec("audience")
}
fn content(&self) -> Option<&str> {
self.get("content")?.as_str()
}
fn name(&self) -> Option<&str> {
self.get("name")?.as_str()
}
fn end_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
Some(
chrono::DateTime::parse_from_rfc3339(
self
.get("endTime")?
.as_str()?
)
.ok()?
.with_timezone(&chrono::Utc))
}
fn generator(&self) -> Node<impl Actor> {
self.node_vec("generator")
}
fn icon(&self) -> Node<impl Image> {
self.node_vec("icon")
}
fn image(&self) -> Node<impl Image> {
self.node_vec("image")
}
fn in_reply_to(&self) -> Node<impl Object> {
self.node_vec("inReplyTo")
}
fn location(&self) -> Node<impl Object> {
self.node_vec("location")
}
}

View file

@ -0,0 +1,11 @@
use crate::activitystream::Node;
pub trait Relationship : super::Object {
fn subject(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn relationship(&self) -> Option<&str> { None } // TODO what does this mean???
fn object(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
}
impl Relationship for serde_json::Value {
// ... TODO
}

View file

@ -0,0 +1,8 @@
pub trait Tombstone : super::Object {
fn former_type(&self) -> Option<super::super::BaseType> { None }
fn deleted(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
}
impl Tombstone for serde_json::Value {
// ... TODO
}

View file

@ -14,8 +14,9 @@ impl From<TypeValueError> for sea_orm::TryGetError {
} }
} }
#[macro_export]
macro_rules! strenum { macro_rules! strenum {
( $(pub enum $enum_name:ident { $($flat:ident),+ $($deep:ident($inner:ident)),*};)+ ) => { ( $(pub enum $enum_name:ident { $($flat:ident),+ $($deep:ident($inner:ident)),*})+ ) => {
$( $(
#[derive(PartialEq, Eq, Debug, Clone, Copy)] #[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum $enum_name { pub enum $enum_name {
@ -33,7 +34,7 @@ macro_rules! strenum {
} }
impl TryFrom<&str> for $enum_name { impl TryFrom<&str> for $enum_name {
type Error = TypeValueError; type Error = $crate::activitystream::types::TypeValueError;
fn try_from(value:&str) -> Result<Self, Self::Error> { fn try_from(value:&str) -> Result<Self, Self::Error> {
match value { match value {
@ -44,7 +45,7 @@ macro_rules! strenum {
return Ok(Self::$deep(x)); return Ok(Self::$deep(x));
} }
)* )*
Err(TypeValueError) Err($crate::activitystream::types::TypeValueError)
}, },
} }
} }
@ -87,142 +88,3 @@ macro_rules! strenum {
)* )*
}; };
} }
strenum! {
pub enum BaseType {
Invalid
Object(ObjectType),
Link(LinkType)
};
pub enum LinkType {
Base,
Mention
};
pub enum ObjectType {
Object,
Relationship,
Tombstone
Activity(ActivityType),
Actor(ActorType),
Collection(CollectionType),
Status(StatusType)
};
pub enum ActorType {
Application,
Group,
Organization,
Person,
Object
};
pub enum StatusType {
Article,
Event,
Note,
Place,
Profile
Document(DocumentType)
};
pub enum CollectionType {
Collection,
CollectionPage,
OrderedCollection,
OrderedCollectionPage
};
pub enum AcceptType {
Accept,
TentativeAccept
};
pub enum DocumentType {
Document,
Audio,
Image,
Page,
Video
};
pub enum ActivityType {
Activity,
Add,
Announce,
Create,
Delete,
Dislike,
Flag,
Follow,
Join,
Leave,
Like,
Listen,
Move,
Read,
Remove,
Undo,
Update,
View
IntransitiveActivity(IntransitiveActivityType),
Accept(AcceptType),
Ignore(IgnoreType),
Offer(OfferType),
Reject(RejectType)
};
pub enum IntransitiveActivityType {
IntransitiveActivity,
Arrive,
Question,
Travel
};
pub enum IgnoreType {
Ignore,
Block
};
pub enum OfferType {
Offer,
Invite
};
pub enum RejectType {
Reject,
TentativeReject
};
}
#[cfg(test)]
mod test {
#[test]
fn assert_flat_types_serialize() {
let x = super::IgnoreType::Block;
assert_eq!("Block", <super::IgnoreType as AsRef<str>>::as_ref(&x));
}
#[test]
fn assert_deep_types_serialize() {
let x = super::StatusType::Document(super::DocumentType::Page);
assert_eq!("Page", <super::StatusType as AsRef<str>>::as_ref(&x));
}
#[test]
fn assert_flat_types_deserialize() {
let x = super::ActorType::try_from("Person").expect("could not deserialize");
assert_eq!(super::ActorType::Person, x);
}
#[test]
fn assert_deep_types_deserialize() {
let x = super::ActivityType::try_from("Invite").expect("could not deserialize");
assert_eq!(super::ActivityType::Offer(super::OfferType::Invite), x);
}
}