feat: separated apb types into crate, reworked

no more "impl ..." hell, each trait has associated types so that we know
it's a "Self::Link" or a "Self::Actor", but in practice they can both be
a "serde_json::Value" and thus we can change its type. also Node::Array
is now a Vec<T> rather than Vec<Node<T>> because it makes more sense.
Node is Iterable and will yield zero (Empty|Link), one (Object) or many
(Array) Ts
This commit is contained in:
əlemi 2024-04-06 16:56:13 +02:00
parent b5867b90ac
commit 520c8eff3a
Signed by: alemi
GPG key ID: A4895B84D311642C
51 changed files with 3593 additions and 829 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
/apb/target

View file

@ -1,3 +1,6 @@
[workspace]
members = ["apb"]
[package]
name = "upub"
version = "0.1.0"
@ -10,7 +13,7 @@ axum = "0.7.3"
chrono = { version = "0.4.31", features = ["serde"] }
clap = { version = "4.5.3", features = ["derive"] }
paste = "1.0.14"
reqwest = { version = "0.11.26", features = ["json"] }
reqwest = { version = "0.12", features = ["json"] }
sea-orm = { version = "0.12.14", features = ["macros", "sqlx-sqlite", "runtime-tokio-rustls"] }
sea-orm-migration = "0.12.15"
serde = { version = "1.0.193", features = ["derive"] }
@ -21,6 +24,7 @@ tracing = "0.1.40"
tracing-subscriber = "0.3.18"
uuid = { version = "1.8.0", features = ["v4"] }
jrd = "0.1"
apb = { path = "apb", features = ["dict", "fetch", "orm"] }
# nodeinfo = "0.0.2" # the version on crates.io doesn't re-export necessary types to build the struct!!!
nodeinfo = { git = "https://codeberg.org/thefederationinfo/nodeinfo-rs", rev = "e865094804" }
rand = "0.8.5"

2747
apb/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

23
apb/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "apb"
version = "0.1.0"
edition = "2021"
[lib]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
thiserror = "1.0"
serde_json = { version = "1.0", optional = true }
reqwest = { version = "0.12", features = ["json"], optional = true }
paste = "1.0.14"
sea-orm = { version = "0.12", optional = true }
tracing = "0.1.40"
[features]
default = ["fetch", "dict", "orm"]
fetch = ["dep:reqwest"]
dict = ["dep:serde_json"]
orm = ["dep:sea-orm"]

41
apb/src/base.rs Normal file
View file

@ -0,0 +1,41 @@
use crate::{getter, setter, strenum, LinkType, ObjectType};
strenum! {
pub enum BaseType {
;
Object(ObjectType),
Link(LinkType)
};
}
pub trait Base {
fn id(&self) -> Option<&str> { None }
fn base_type(&self) -> Option<BaseType> { None }
}
pub trait BaseMut {
fn set_id(self, val: Option<&str>) -> Self;
fn set_base_type(self, val: Option<BaseType>) -> Self;
}
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 {
getter! { id -> &str }
getter! { base_type -> type BaseType }
}
impl BaseMut for serde_json::Value {
setter! { id -> &str }
setter! { base_type -> type BaseType }
}

36
apb/src/lib.rs Normal file
View file

@ -0,0 +1,36 @@
mod macros;
mod node;
pub use node::Node;
mod link;
pub use link::{Link, LinkMut, LinkType};
pub mod key;
pub use key::{PublicKey, PublicKeyMut};
mod base;
pub use base::{Base, BaseMut, BaseType};
mod object;
pub use object::{
Object, ObjectMut, ObjectType,
activity::{
Activity, ActivityMut, ActivityType,
accept::{Accept, AcceptMut, AcceptType},
ignore::{Ignore, IgnoreMut, IgnoreType},
intransitive::{IntransitiveActivity, IntransitiveActivityMut, IntransitiveActivityType},
offer::{Offer, OfferMut, OfferType},
reject::{Reject, RejectMut, RejectType},
},
actor::{Actor, ActorMut, ActorType},
collection::{
Collection, CollectionMut, CollectionType,
page::{CollectionPage, CollectionPageMut}
},
document::{Document, DocumentMut, DocumentType},
place::{Place, PlaceMut},
// profile::Profile,
relationship::{Relationship, RelationshipMut},
tombstone::{Tombstone, TombstoneMut},
};

View file

@ -37,7 +37,7 @@ macro_rules! strenum {
}
impl TryFrom<&str> for $enum_name {
type Error = $crate::activitystream::macros::TypeValueError;
type Error = $crate::macros::TypeValueError;
fn try_from(value:&str) -> Result<Self, Self::Error> {
match value {
@ -48,7 +48,7 @@ macro_rules! strenum {
return Ok(Self::$deep(x));
}
)*
Err($crate::activitystream::macros::TypeValueError)
Err($crate::macros::TypeValueError)
},
}
}
@ -171,19 +171,19 @@ macro_rules! getter {
};
($name:ident -> node $t:ty) => {
fn $name(&self) -> $crate::activitystream::Node<$t> {
fn $name(&self) -> $crate::Node<$t> {
match self.get(stringify!($name)) {
Some(x) => $crate::activitystream::Node::from(x.clone()),
None => $crate::activitystream::Node::Empty,
Some(x) => $crate::Node::from(x.clone()),
None => $crate::Node::Empty,
}
}
};
($name:ident::$rename:ident -> node $t:ty) => {
fn $name(&self) -> $crate::activitystream::Node<$t> {
fn $name(&self) -> $crate::Node<$t> {
match self.get(stringify!($rename)) {
Some(x) => $crate::activitystream::Node::from(x.clone()),
None => $crate::activitystream::Node::Empty,
Some(x) => $crate::Node::from(x.clone()),
None => $crate::Node::Empty,
}
}
};
@ -194,7 +194,7 @@ macro_rules! setter {
($name:ident -> bool) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<bool>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($name), val.map(|x| serde_json::Value::Bool(x))
);
self
@ -205,7 +205,7 @@ macro_rules! setter {
($name:ident -> &str) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_string()))
);
self
@ -216,7 +216,7 @@ macro_rules! setter {
($name:ident::$rename:ident -> &str) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<&str>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_string()))
);
self
@ -227,7 +227,7 @@ macro_rules! setter {
($name:ident -> u64) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($name), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
);
self
@ -238,7 +238,7 @@ macro_rules! setter {
($name:ident::$rename:ident -> u64) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<u64>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($rename), val.map(|x| serde_json::Value::Number(serde_json::Number::from(x)))
);
self
@ -249,7 +249,7 @@ macro_rules! setter {
($name:ident -> chrono::DateTime<chrono::Utc>) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($name), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
);
self
@ -260,7 +260,7 @@ macro_rules! setter {
($name:ident::$rename:ident -> chrono::DateTime<chrono::Utc>) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, stringify!($rename), val.map(|x| serde_json::Value::String(x.to_rfc3339()))
);
self
@ -270,8 +270,8 @@ macro_rules! setter {
($name:ident -> node $t:ty ) => {
paste::item! {
fn [< set_$name >](mut self, val: $crate::activitystream::Node<$t>) -> Self {
$crate::activitystream::macros::set_maybe_node(
fn [< set_$name >](mut self, val: $crate::Node<$t>) -> Self {
$crate::macros::set_maybe_node(
&mut self, stringify!($name), val
);
self
@ -281,8 +281,8 @@ macro_rules! setter {
($name:ident::$rename:ident -> node $t:ty ) => {
paste::item! {
fn [< set_$name >](mut self, val: $crate::activitystream::Node<$t>) -> Self {
$crate::activitystream::macros::set_maybe_node(
fn [< set_$name >](mut self, val: $crate::Node<$t>) -> Self {
$crate::macros::set_maybe_node(
&mut self, stringify!($rename), val
);
self
@ -293,7 +293,7 @@ macro_rules! setter {
($name:ident -> type $t:ty ) => {
paste::item! {
fn [< set_$name >](mut self, val: Option<$t>) -> Self {
$crate::activitystream::macros::set_maybe_value(
$crate::macros::set_maybe_value(
&mut self, "type", val.map(|x| serde_json::Value::String(x.as_ref().to_string()))
);
self
@ -302,11 +302,11 @@ macro_rules! setter {
};
}
pub fn set_maybe_node<T : super::Base>(obj: &mut serde_json::Value, key: &str, node: super::Node<T>) {
pub fn set_maybe_node(obj: &mut serde_json::Value, key: &str, node: super::Node<serde_json::Value>) {
match node {
super::Node::Object(x) => {
set_maybe_value(
obj, key, Some(x.underlying_json_object()),
obj, key, Some(*x),
);
},
super::Node::Link(l) => {
@ -316,7 +316,7 @@ pub fn set_maybe_node<T : super::Base>(obj: &mut serde_json::Value, key: &str, n
},
super::Node::Array(_) => {
set_maybe_value(
obj, key, Some(serde_json::Value::Array(node.flat())),
obj, key, Some(serde_json::Value::Array(node.into_iter().collect())),
);
},
super::Node::Empty => {
@ -357,7 +357,7 @@ impl InsertValue for serde_json::Map<String, serde_json::Value> {
Node::Array(ref _arr) => {
self.insert(
k.to_string(),
serde_json::Value::Array(node.flat()),
serde_json::Value::Array(node.into_iter().collect()),
);
},
Node::Link(l) => {

167
apb/src/node.rs Normal file
View file

@ -0,0 +1,167 @@
pub enum Node<T : super::Base> {
Array(Vec<T>), // TODO would be cool to make it Box<[T]> so that Node is just a ptr
Object(Box<T>),
Link(Box<dyn super::Link>),
Empty,
}
impl<T : super::Base> From<Option<T>> for Node<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(x) => Node::Object(Box::new(x)),
None => Node::Empty,
}
}
}
impl<T : super::Base + Clone> Iterator for Node<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let x = match self {
Self::Empty => return None,
Self::Link(_) => return None,
Self::Array(arr) => return arr.pop(), // TODO weird that we iter in reverse
Self::Object(x) => *x.clone(), // TODO needed because next() on object can't get value without owning
};
*self = Self::Empty;
Some(x)
}
}
impl<T : super::Base> Node<T> {
pub fn get(&self) -> Option<&T> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(x),
Node::Array(v) => v.last(), // TODO so it's coherent with next(), still weird tho!
}
}
pub fn extract(self) -> Option<T> {
match self {
Node::Empty | Node::Link(_) => None,
Node::Object(x) => Some(*x),
Node::Array(mut v) => v.pop(), // TODO so it's coherent with next(), still weird tho!
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Node::Empty)
}
pub fn is_link(&self) -> bool {
matches!(self, Node::Link(_))
}
pub fn is_object(&self) -> bool {
matches!(self, Node::Object(_))
}
pub fn is_array(&self) -> bool {
matches!(self, Node::Array(_))
}
pub fn len(&self) -> usize {
match self {
Node::Empty => 0,
Node::Link(_) => 1,
Node::Object(_) => 1,
Node::Array(v) => v.len(),
}
}
pub fn id(&self) -> Option<String> {
match self {
Node::Empty | Node::Array(_) => None,
Node::Link(uri) => Some(uri.href().to_string()),
Node::Object(obj) => obj.id().map(|x| x.to_string()),
}
}
}
#[cfg(feature = "dict")]
impl Node<serde_json::Value> {
pub fn link(uri: String) -> Self {
Node::Link(Box::new(uri))
}
pub fn links(uris: Vec<String>) -> Self {
Node::Array(
uris
.into_iter()
.map(serde_json::Value::String)
.collect()
)
}
pub fn maybe_link(uri: Option<String>) -> Self {
match uri {
Some(uri) => Node::Link(Box::new(uri)),
None => Node::Empty,
}
}
pub fn object(x: serde_json::Value) -> Self {
Node::Object(Box::new(x))
}
pub fn maybe_object(x: Option<serde_json::Value>) -> Self {
match x {
Some(x) => Node::Object(Box::new(x)),
None => Node::Empty,
}
}
pub fn array(values: Vec<serde_json::Value>) -> Self {
Node::Array(values)
}
#[cfg(feature = "fetch")]
pub async fn fetch(&mut self) -> reqwest::Result<&mut Self> {
if let Node::Link(link) = self {
*self = reqwest::Client::new()
.get(link.href())
.header("Accept", "application/json")
.send()
.await?
.json::<serde_json::Value>()
.await?
.into();
}
Ok(self)
}
}
#[cfg(feature = "dict")]
impl From<Option<&str>> for Node<serde_json::Value> {
fn from(value: Option<&str>) -> Self {
match value {
Some(x) => Node::Link(Box::new(x.to_string())),
None => Node::Empty,
}
}
}
#[cfg(feature = "dict")]
impl From<&str> for Node<serde_json::Value> {
fn from(value: &str) -> Self {
Node::Link(Box::new(value.to_string()))
}
}
#[cfg(feature = "dict")]
impl From<serde_json::Value> for Node<serde_json::Value> {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::String(uri) => Node::Link(Box::new(uri)),
serde_json::Value::Array(arr) => Node::Array(arr),
serde_json::Value::Object(_) => match value.get("href") {
None => Node::Object(Box::new(value)),
Some(_) => Node::Link(Box::new(value)),
},
_ => Node::Empty,
}
}
}

View file

@ -0,0 +1,81 @@
pub mod accept;
pub mod ignore;
pub mod intransitive;
pub mod offer;
pub mod reject;
use crate::{Node, object::{Object, ObjectMut}, getter, setter, strenum};
use accept::AcceptType;
use reject::RejectType;
use offer::OfferType;
use intransitive::IntransitiveActivityType;
use ignore::IgnoreType;
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 : Object {
fn activity_type(&self) -> Option<ActivityType> { None }
fn actor(&self) -> Node<Self::Actor> { Node::Empty }
fn object(&self) -> Node<Self::Object> { Node::Empty }
fn target(&self) -> Node<Self::Object> { Node::Empty }
fn result(&self) -> Node<Self::Object> { Node::Empty }
fn origin(&self) -> Node<Self::Object> { Node::Empty }
fn instrument(&self) -> Node<Self::Object> { Node::Empty }
}
pub trait ActivityMut : ObjectMut {
fn set_activity_type(self, val: Option<ActivityType>) -> Self;
fn set_actor(self, val: Node<Self::Actor>) -> Self;
fn set_object(self, val: Node<Self::Object>) -> Self;
fn set_target(self, val: Node<Self::Object>) -> Self;
fn set_result(self, val: Node<Self::Object>) -> Self;
fn set_origin(self, val: Node<Self::Object>) -> Self;
fn set_instrument(self, val: Node<Self::Object>) -> Self;
}
impl Activity for serde_json::Value {
getter! { activity_type -> type ActivityType }
getter! { actor -> node Self::Actor }
getter! { object -> node <Self as Object>::Object }
getter! { target -> node <Self as Object>::Object }
getter! { result -> node <Self as Object>::Object }
getter! { origin -> node <Self as Object>::Object }
getter! { instrument -> node <Self as Object>::Object }
}
impl ActivityMut for serde_json::Value {
setter! { activity_type -> type ActivityType }
setter! { actor -> node Self::Actor }
setter! { object -> node <Self as Object>::Object }
setter! { target -> node <Self as Object>::Object }
setter! { result -> node <Self as Object>::Object }
setter! { origin -> node <Self as Object>::Object }
setter! { instrument -> node <Self as Object>::Object }
}

84
apb/src/object/actor.rs Normal file
View file

@ -0,0 +1,84 @@
use crate::{Node, getter, setter, strenum};
use super::{Object, ObjectMut, super::key::PublicKey};
strenum! {
pub enum ActorType {
Application,
Group,
Organization,
Person;
};
}
pub trait Actor : Object {
type PublicKey : PublicKey;
fn actor_type(&self) -> Option<ActorType> { None }
fn preferred_username(&self) -> Option<&str> { None }
fn inbox(&self) -> Node<Self::Collection>;
fn outbox(&self) -> Node<Self::Collection>;
fn following(&self) -> Node<Self::Collection> { todo!() }
fn followers(&self) -> Node<Self::Collection> { todo!() }
fn liked(&self) -> Node<Self::Collection> { todo!() }
fn streams(&self) -> Node<Self::Collection> { todo!() }
fn endpoints(&self) -> Option<serde_json::Map<String, String>> { None }
fn public_key(&self) -> Node<Self::PublicKey> { todo!() }
// idk about this? everyone has it but AP doesn't mention it
fn discoverable(&self) -> Option<bool> { None }
}
pub trait ActorMut : ObjectMut {
type PublicKey : PublicKey;
fn set_actor_type(self, val: Option<ActorType>) -> Self;
fn set_preferred_username(self, val: Option<&str>) -> Self;
fn set_inbox(self, val: Node<Self::Collection>) -> Self;
fn set_outbox(self, val: Node<Self::Collection>) -> Self;
fn set_following(self, val: Node<Self::Collection>) -> Self;
fn set_followers(self, val: Node<Self::Collection>) -> Self;
fn set_liked(self, val: Node<Self::Collection>) -> Self;
fn set_streams(self, val: Node<Self::Collection>) -> Self;
fn set_endpoints(self, val: Option<serde_json::Map<String, String>>) -> Self;
fn set_public_key(self, val: Node<Self::PublicKey>) -> Self;
fn set_discoverable(self, val: Option<bool>) -> Self;
}
impl Actor for serde_json::Value {
type PublicKey = serde_json::Value;
getter! { actor_type -> type ActorType }
getter! { preferred_username::preferredUsername -> &str }
getter! { inbox -> node Self::Collection }
getter! { outbox -> node Self::Collection }
getter! { following -> node Self::Collection }
getter! { followers -> node Self::Collection }
getter! { liked -> node Self::Collection }
getter! { streams -> node Self::Collection }
getter! { public_key::publicKey -> node Self::PublicKey }
getter! { discoverable -> bool }
fn endpoints(&self) -> Option<serde_json::Map<String, String>> {
todo!()
}
}
impl ActorMut for serde_json::Value {
type PublicKey = serde_json::Value;
setter! { actor_type -> type ActorType }
setter! { preferred_username::preferredUsername -> &str }
setter! { inbox -> node Self::Collection }
setter! { outbox -> node Self::Collection }
setter! { following -> node Self::Collection }
setter! { followers -> node Self::Collection }
setter! { liked -> node Self::Collection }
setter! { streams -> node Self::Collection }
setter! { public_key::publicKey -> node Self::PublicKey }
setter! { discoverable -> bool }
fn set_endpoints(mut self, _val: Option<serde_json::Map<String, String>>) -> Self {
self.as_object_mut().unwrap().insert("endpoints".to_string(), serde_json::Value::Object(serde_json::Map::default()));
self
}
}

View file

@ -0,0 +1,62 @@
pub mod page;
pub use page::CollectionPage;
use crate::{Node, Object, object::ObjectMut, getter, setter, strenum};
strenum! {
pub enum CollectionType {
Collection,
CollectionPage,
OrderedCollection,
OrderedCollectionPage;
};
}
pub trait Collection : Object {
type CollectionPage : CollectionPage;
fn collection_type(&self) -> Option<CollectionType> { None }
fn total_items(&self) -> Option<u64> { None }
fn current(&self) -> Node<Self::CollectionPage> { Node::Empty }
fn first(&self) -> Node<Self::CollectionPage> { Node::Empty }
fn last(&self) -> Node<Self::CollectionPage> { Node::Empty }
fn items(&self) -> Node<Self::Object> { Node::Empty }
fn ordered_items(&self) -> Node<Self::Object> { Node::Empty }
}
pub trait CollectionMut : ObjectMut {
type CollectionPage : CollectionPage;
fn set_collection_type(self, val: Option<CollectionType>) -> Self;
fn set_total_items(self, val: Option<u64>) -> Self;
fn set_current(self, val: Node<Self::CollectionPage>) -> Self;
fn set_first(self, val: Node<Self::CollectionPage>) -> Self;
fn set_last(self, val: Node<Self::CollectionPage>) -> Self;
fn set_items(self, val: Node<Self::Object>) -> Self;
fn set_ordered_items(self, val: Node<Self::Object>) -> Self;
}
impl Collection for serde_json::Value {
type CollectionPage = serde_json::Value;
getter! { collection_type -> type CollectionType }
getter! { total_items::totalItems -> u64 }
getter! { current -> node Self::CollectionPage }
getter! { first -> node Self::CollectionPage }
getter! { last -> node Self::CollectionPage }
getter! { items -> node <Self as Object>::Object }
getter! { ordered_items::orderedItems -> node <Self as Object>::Object }
}
impl CollectionMut for serde_json::Value {
type CollectionPage = serde_json::Value;
setter! { collection_type -> type CollectionType }
setter! { total_items::totalItems -> u64 }
setter! { current -> node Self::CollectionPage }
setter! { first -> node Self::CollectionPage }
setter! { last -> node Self::CollectionPage }
setter! { items -> node <Self as Object>::Object }
setter! { ordered_items::orderedItems -> node <Self as Object>::Object }
}

View file

@ -0,0 +1,25 @@
use crate::{Node, getter, setter};
pub trait CollectionPage : super::Collection {
fn part_of(&self) -> Node<Self::Collection> { Node::Empty }
fn next(&self) -> Node<Self::CollectionPage> { Node::Empty }
fn prev(&self) -> Node<Self::CollectionPage> { Node::Empty }
}
pub trait CollectionPageMut : super::CollectionMut {
fn set_part_of(self, val: Node<Self::Collection>) -> Self;
fn set_next(self, val: Node<Self::CollectionPage>) -> Self;
fn set_prev(self, val: Node<Self::CollectionPage>) -> Self;
}
impl CollectionPage for serde_json::Value {
getter! { part_of::partOf -> node Self::Collection }
getter! { next -> node Self::CollectionPage }
getter! { prev -> node Self::CollectionPage }
}
impl CollectionPageMut for serde_json::Value {
setter! { part_of::partOf -> node Self::Collection }
setter! { next -> node Self::CollectionPage }
setter! { prev -> node Self::CollectionPage }
}

View file

@ -26,8 +26,3 @@ impl Document for serde_json::Value {
impl DocumentMut for serde_json::Value {
setter! { document_type -> type DocumentType }
}
pub trait Image : Document {}
impl Image for serde_json::Value {}

202
apb/src/object/mod.rs Normal file
View file

@ -0,0 +1,202 @@
pub mod activity;
pub mod actor;
pub mod collection;
pub mod document;
pub mod tombstone;
pub mod place;
pub mod profile;
pub mod relationship;
use crate::{getter, setter, strenum};
use super::{Base, BaseMut, Link, Node};
use actor::{Actor, ActorType};
use document::{Document, DocumentType};
use activity::ActivityType;
use collection::{Collection, CollectionType};
strenum! {
pub enum ObjectType {
Object,
Article,
Event,
Note,
Place,
Profile,
Relationship,
Tombstone;
Activity(ActivityType),
Actor(ActorType),
Collection(CollectionType),
Document(DocumentType)
};
}
pub trait Object : Base {
type Link : Link;
type Actor : Actor;
type Object : Object;
type Collection : Collection;
type Document : Document;
fn object_type(&self) -> Option<ObjectType> { None }
fn attachment(&self) -> Node<Self::Object> { Node::Empty }
fn attributed_to(&self) -> Node<Self::Actor> { Node::Empty }
fn audience(&self) -> Node<Self::Actor> { Node::Empty }
fn content(&self) -> Option<&str> { None } // TODO handle language maps
fn context(&self) -> Node<Self::Object> { Node::Empty }
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<Self::Actor> { Node::Empty }
fn icon(&self) -> Node<Self::Document> { Node::Empty }
fn image(&self) -> Node<Self::Document> { Node::Empty }
fn in_reply_to(&self) -> Node<Self::Object> { Node::Empty }
fn location(&self) -> Node<Self::Object> { Node::Empty }
fn preview(&self) -> Node<Self::Object> { Node::Empty } // also in link
fn published(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn replies(&self) -> Node<Self::Collection> { Node::Empty }
fn start_time(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn summary(&self) -> Option<&str> { None }
fn tag(&self) -> Node<Self::Object> { Node::Empty }
fn updated(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
fn url(&self) -> Node<Self::Link> { Node::Empty }
fn to(&self) -> Node<Self::Link> { Node::Empty }
fn bto(&self) -> Node<Self::Link> { Node::Empty }
fn cc(&self) -> Node<Self::Link> { Node::Empty }
fn bcc(&self) -> Node<Self::Link> { Node::Empty }
fn media_type(&self) -> Option<&str> { None } // also in link
fn duration(&self) -> Option<&str> { None } // TODO how to parse xsd:duration ?
}
pub trait ObjectMut : BaseMut {
type Link : Link;
type Actor : Actor;
type Object : Object;
type Collection : Collection;
type Document : Document;
fn set_object_type(self, val: Option<ObjectType>) -> Self;
fn set_attachment(self, val: Node<Self::Object>) -> Self;
fn set_attributed_to(self, val: Node<Self::Actor>) -> Self;
fn set_audience(self, val: Node<Self::Actor>) -> Self;
fn set_content(self, val: Option<&str>) -> Self; // TODO handle language maps
fn set_context(self, val: Node<Self::Object>) -> Self;
fn set_name(self, val: Option<&str>) -> Self; // also in link // TODO handle language maps
fn set_end_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_generator(self, val: Node<Self::Actor>) -> Self;
fn set_icon(self, val: Node<Self::Document>) -> Self;
fn set_image(self, val: Node<Self::Document>) -> Self;
fn set_in_reply_to(self, val: Node<Self::Object>) -> Self;
fn set_location(self, val: Node<Self::Object>) -> Self;
fn set_preview(self, val: Node<Self::Object>) -> Self; // also in link
fn set_published(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_replies(self, val: Node<Self::Collection>) -> Self;
fn set_start_time(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_summary(self, val: Option<&str>) -> Self;
fn set_tag(self, val: Node<Self::Object>) -> Self;
fn set_updated(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
fn set_url(self, val: Node<Self::Link>) -> Self;
fn set_to(self, val: Node<Self::Link>) -> Self;
fn set_bto(self, val: Node<Self::Link>) -> Self;
fn set_cc(self, val: Node<Self::Link>) -> Self;
fn set_bcc(self, val: Node<Self::Link>) -> Self;
fn set_media_type(self, val: Option<&str>) -> Self; // also in link
fn set_duration(self, val: Option<&str>) -> Self; // TODO how to parse xsd:duration ?
}
impl Object for serde_json::Value {
type Link = serde_json::Value;
type Actor = serde_json::Value;
type Object = serde_json::Value;
type Document = serde_json::Value;
type Collection = serde_json::Value;
getter! { object_type -> type ObjectType }
getter! { attachment -> node <Self as Object>::Object }
getter! { attributed_to::attributedTo -> node Self::Actor }
getter! { audience -> node Self::Actor }
getter! { content -> &str }
getter! { name -> &str }
getter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
getter! { generator -> node Self::Actor }
getter! { icon -> node Self::Document }
getter! { image -> node Self::Document }
getter! { in_reply_to::inReplyTo -> node <Self as Object>::Object }
getter! { location -> node <Self as Object>::Object }
getter! { preview -> node <Self as Object>::Object }
getter! { published -> chrono::DateTime<chrono::Utc> }
getter! { replies -> node Self::Collection }
getter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
getter! { summary -> &str }
getter! { tag -> node <Self as Object>::Object }
getter! { updated -> chrono::DateTime<chrono::Utc> }
getter! { to -> node Self::Link }
getter! { bto -> node Self::Link }
getter! { cc -> node Self::Link }
getter! { bcc -> node Self::Link }
getter! { media_type -> &str }
getter! { duration -> &str }
getter! { url -> node Self::Link }
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
fn context(&self) -> Node<<Self as Object>::Object> {
match self.get("context") {
Some(x) => Node::from(x.clone()),
None => match self.get("conversation") {
Some(x) => Node::from(x.clone()),
None => Node::Empty,
}
}
}
}
impl ObjectMut for serde_json::Value {
type Link = serde_json::Value;
type Actor = serde_json::Value;
type Object = serde_json::Value;
type Document = serde_json::Value;
type Collection = serde_json::Value;
setter! { object_type -> type ObjectType }
setter! { attachment -> node <Self as Object>::Object }
setter! { attributed_to::attributedTo -> node Self::Actor }
setter! { audience -> node Self::Actor }
setter! { content -> &str }
setter! { name -> &str }
setter! { end_time::endTime -> chrono::DateTime<chrono::Utc> }
setter! { generator -> node Self::Actor }
setter! { icon -> node Self::Document }
setter! { image -> node Self::Document }
setter! { in_reply_to::inReplyTo -> node <Self as Object>::Object }
setter! { location -> node <Self as Object>::Object }
setter! { preview -> node <Self as Object>::Object }
setter! { published -> chrono::DateTime<chrono::Utc> }
setter! { replies -> node Self::Collection }
setter! { start_time::startTime -> chrono::DateTime<chrono::Utc> }
setter! { summary -> &str }
setter! { tag -> node <Self as Object>::Object }
setter! { updated -> chrono::DateTime<chrono::Utc> }
setter! { to -> node Self::Link }
setter! { bto -> node Self::Link}
setter! { cc -> node Self::Link }
setter! { bcc -> node Self::Link }
setter! { media_type -> &str }
setter! { duration -> &str }
setter! { url -> node Self::Link }
// TODO Mastodon doesn't use a "context" field on the object but makes up a new one!!
fn set_context(mut self, ctx: Node<<Self as Object>::Object>) -> Self {
if let Some(conversation) = ctx.id() {
crate::macros::set_maybe_value(
&mut self, "conversation", Some(serde_json::Value::String(conversation)),
);
}
crate::macros::set_maybe_node(
&mut self, "context", ctx
);
self
}
}

View file

@ -1,17 +1,17 @@
use crate::activitystream::Node;
use crate::Node;
pub trait Relationship : super::Object {
fn subject(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn subject(&self) -> Node<Self::Object> { Node::Empty }
fn relationship(&self) -> Option<&str> { None } // TODO what does this mean???
// TODO was just object but clashes with Activity
fn relationship_object(&self) -> Node<impl super::Object> { Node::Empty::<serde_json::Value> }
fn relationship_object(&self) -> Node<Self::Object> { Node::Empty }
}
pub trait RelationshipMut : super::ObjectMut {
fn set_subject(self, val: Node<impl super::Object>) -> Self;
fn set_subject(self, val: Node<Self::Object>) -> Self;
fn set_relationship(self, val: Option<&str>) -> Self; // TODO what does this mean???
// TODO was just object but clashes with Activity
fn set_relationship_object(self, val: Node<impl super::Object>) -> Self;
fn set_relationship_object(self, val: Node<Self::Object>) -> Self;
}
impl Relationship for serde_json::Value {

View file

@ -1,10 +1,10 @@
pub trait Tombstone : super::Object {
fn former_type(&self) -> Option<super::super::BaseType> { None }
fn former_type(&self) -> Option<crate::BaseType> { None }
fn deleted(&self) -> Option<chrono::DateTime<chrono::Utc>> { None }
}
pub trait TombstoneMut : super::ObjectMut {
fn set_former_type(self, val: Option<super::super::BaseType>) -> Self;
fn set_former_type(self, val: Option<crate::BaseType>) -> Self;
fn set_deleted(self, val: Option<chrono::DateTime<chrono::Utc>>) -> Self;
}

View file

@ -1,6 +1,7 @@
use axum::{extract::{Path, State}, http::StatusCode};
use sea_orm::EntityTrait;
use crate::{activitystream::{object::{activity::ActivityMut, ObjectMut}, BaseMut, Node}, model::{self, activity, object}, server::Context};
use crate::{model::{self, activity, object}, server::Context};
use apb::{ActivityMut, ObjectMut, BaseMut, Node};
use super::{jsonld::LD, JsonLD};
@ -13,9 +14,9 @@ pub fn ap_activity(activity: model::activity::Model) -> serde_json::Value {
.set_target(Node::maybe_link(activity.target))
.set_published(Some(activity.published))
.set_to(Node::links(activity.to.0.clone()))
.set_bto(Node::empty())
.set_bto(Node::Empty)
.set_cc(Node::links(activity.cc.0.clone()))
.set_bcc(Node::empty())
.set_bcc(Node::Empty)
}
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {

View file

@ -1,7 +1,7 @@
use axum::{extract::{Query, State}, http::StatusCode};
use sea_orm::{ColumnTrait, Condition, EntityTrait, Order, QueryFilter, QueryOrder, QuerySelect};
use crate::{activitystream::Node, auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
use crate::{auth::{AuthIdentity, Identity}, errors::UpubError, model, server::Context, url};
use super::{activity::ap_activity, jsonld::LD, JsonLD, Pagination, PUBLIC_TARGET};
@ -39,8 +39,8 @@ pub async fn page(
offset, limit,
activities
.into_iter()
.filter_map(|(_, a)| Some(Node::object(ap_activity(a?))))
.collect::<Vec<Node<serde_json::Value>>>()
.filter_map(|(_, a)| Some(ap_activity(a?)))
.collect::<Vec<serde_json::Value>>()
).ld_context()
))
}

View file

@ -12,10 +12,25 @@ use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use rand::Rng;
use sea_orm::{ColumnTrait, Condition, EntityTrait, QueryFilter};
use crate::{activitystream::{key::PublicKeyMut, object::{actor::{ActorMut, ActorType}, ObjectMut}, BaseMut, Node}, model, server::Context, url};
use apb::{PublicKeyMut, ActorMut, ActorType, Link, Object, ObjectMut, BaseMut, Node};
use crate::{model, server::Context, url};
use self::jsonld::LD;
pub trait Addressed : Object {
fn addressed(&self) -> Vec<String>;
}
impl Addressed for serde_json::Value {
fn addressed(&self) -> Vec<String> {
let mut to : Vec<String> = self.to().map(|x| x.href().to_string()).collect();
to.append(&mut self.bto().map(|x| x.href().to_string()).collect());
to.append(&mut self.cc().map(|x| x.href().to_string()).collect());
to.append(&mut self.bcc().map(|x| x.href().to_string()).collect());
to
}
}
pub const PUBLIC_TARGET : &str = "https://www.w3.org/ns/activitystreams#Public";
pub fn split_id(id: &str) -> (String, String) {

View file

@ -1,7 +1,8 @@
use axum::{extract::{Path, State}, http::StatusCode};
use sea_orm::EntityTrait;
use crate::{activitystream::{object::ObjectMut, BaseMut, Node}, model::{self, object}, server::Context};
use apb::{ObjectMut, BaseMut, Node};
use crate::{model::{self, object}, server::Context};
use super::{jsonld::LD, JsonLD};
@ -16,9 +17,9 @@ pub fn ap_object(object: model::object::Model) -> serde_json::Value {
.set_context(Node::maybe_link(object.context.clone()))
.set_published(Some(object.published))
.set_to(Node::links(object.to.0.clone()))
.set_bto(Node::empty())
.set_bto(Node::Empty)
.set_cc(Node::links(object.cc.0.clone()))
.set_bcc(Node::empty())
.set_bcc(Node::Empty)
}
pub async fn view(State(ctx) : State<Context>, Path(id): Path<String>) -> Result<JsonLD<serde_json::Value>, StatusCode> {

View file

@ -1,7 +1,7 @@
use axum::{extract::{Path, Query, State}, http::StatusCode};
use sea_orm::{ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
use crate::{activitypub::{jsonld::LD, JsonLD, Pagination}, activitystream::Node, model, server::Context, url};
use crate::{activitypub::{jsonld::LD, JsonLD, Pagination}, model, server::Context, url};
use model::relation::Column::{Following, Follower};
@ -51,7 +51,7 @@ pub async fn page<const OUTGOING: bool>(
&url!(ctx, "/users/{id}/{follow___}"),
offset,
limit,
following.into_iter().map(Node::link).collect()
following.into_iter().map(serde_json::Value::String).collect()
).ld_context()
))
},

View file

@ -1,7 +1,8 @@
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
use sea_orm::{sea_query::Expr, ColumnTrait, Condition, EntityTrait, IntoActiveModel, Order, QueryFilter, QueryOrder, QuerySelect, Set};
use crate::{activitypub::{activity::ap_activity, jsonld::LD, JsonLD, Pagination}, activitystream::{object::{activity::{Activity, ActivityType}, Addressed, Object, ObjectType}, Base, BaseType, Node}, auth::{AuthIdentity, Identity}, errors::{LoggableError, UpubError}, model, server::Context, url};
use apb::{Activity, ActivityType, Object, ObjectType, Base, BaseType};
use crate::{activitypub::{activity::ap_activity, jsonld::LD, Addressed, JsonLD, Pagination}, auth::{AuthIdentity, Identity}, errors::{LoggableError, UpubError}, model, server::Context, url};
pub async fn get(
State(ctx): State<Context>,
@ -48,8 +49,8 @@ pub async fn page(
offset, limit,
activities
.into_iter()
.filter_map(|(_, a)| Some(Node::object(ap_activity(a?))))
.collect::<Vec<Node<serde_json::Value>>>()
.filter_map(|(_, a)| Some(ap_activity(a?)))
.collect::<Vec<serde_json::Value>>()
).ld_context()
))
},
@ -84,7 +85,7 @@ pub async fn post(
Some(BaseType::Object(ObjectType::Activity(ActivityType::Delete))) => {
// TODO verify the signature before just deleting lmao
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?.to_string();
let oid = object.object().id().ok_or(StatusCode::BAD_REQUEST)?;
// TODO maybe we should keep the tombstone?
model::user::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from users");
model::activity::Entity::delete_by_id(&oid).exec(ctx.db()).await.info_failed("failed deleting from activities");
@ -152,8 +153,8 @@ pub async fn post(
},
Some(BaseType::Object(ObjectType::Activity(ActivityType::Like))) => {
let aid = object.actor().id().ok_or(StatusCode::BAD_REQUEST)?.to_string();
let oid