forked from alemi/upub
feat: refactored and expanded activitypub types
This commit is contained in:
parent
7d69f8148c
commit
a73852b3b7
21 changed files with 599 additions and 330 deletions
|
@ -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"] }
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub trait Actor : super::Object {
|
|
||||||
fn actor_type(&self) -> Option<super::ActorType> { None }
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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
166
src/activitystream/node.rs
Normal 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()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!
|
|
||||||
}
|
|
12
src/activitystream/object/activity/accept.rs
Normal file
12
src/activitystream/object/activity/accept.rs
Normal 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>;
|
||||||
|
}
|
12
src/activitystream/object/activity/ignore.rs
Normal file
12
src/activitystream/object/activity/ignore.rs
Normal 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>;
|
||||||
|
}
|
14
src/activitystream/object/activity/intransitive.rs
Normal file
14
src/activitystream/object/activity/intransitive.rs
Normal 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>;
|
||||||
|
}
|
72
src/activitystream/object/activity/mod.rs
Normal file
72
src/activitystream/object/activity/mod.rs
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
12
src/activitystream/object/activity/offer.rs
Normal file
12
src/activitystream/object/activity/offer.rs
Normal 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>;
|
||||||
|
}
|
12
src/activitystream/object/activity/reject.rs
Normal file
12
src/activitystream/object/activity/reject.rs
Normal 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>;
|
||||||
|
}
|
28
src/activitystream/object/actor.rs
Normal file
28
src/activitystream/object/actor.rs
Normal 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 {
|
||||||
|
|
||||||
|
}
|
32
src/activitystream/object/collection/mod.rs
Normal file
32
src/activitystream/object/collection/mod.rs
Normal 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 {
|
||||||
|
|
||||||
|
}
|
7
src/activitystream/object/collection/page.rs
Normal file
7
src/activitystream/object/collection/page.rs
Normal 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> }
|
||||||
|
}
|
34
src/activitystream/object/document.rs
Normal file
34
src/activitystream/object/document.rs
Normal 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 {}
|
||||||
|
|
129
src/activitystream/object/mod.rs
Normal file
129
src/activitystream/object/mod.rs
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
11
src/activitystream/object/relationship.rs
Normal file
11
src/activitystream/object/relationship.rs
Normal 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
|
||||||
|
}
|
8
src/activitystream/object/tombstone.rs
Normal file
8
src/activitystream/object/tombstone.rs
Normal 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
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue