Compare commits

..

2 Commits

Author SHA1 Message Date
Natty e21c5a8132
Merge branch 'activity-pub' into development
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/main.rs
2024-04-17 13:52:14 +02:00
Natty 59f1d834d4
Initial AP commit 2023-09-30 16:28:33 +02:00
15 changed files with 1120 additions and 0 deletions

14
Cargo.lock generated
View File

@ -1640,6 +1640,20 @@ dependencies = [
"url",
]
[[package]]
name = "magnetar_activity_streams"
version = "0.3.0-alpha"
dependencies = [
"async-trait",
"chrono",
"either",
"serde",
"serde_json",
"thiserror",
"tokio",
"url",
]
[[package]]
name = "magnetar_calckey_fe"
version = "0.3.0-alpha"

View File

@ -8,6 +8,7 @@ license = "AGPL-3.0-only"
[workspace]
members = [
".",
"ext_activity_streams",
"ext_federation",
"ext_nodeinfo",
"ext_webfinger",

View File

@ -13,6 +13,7 @@ async-trait = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
either = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }

View File

@ -1 +1,88 @@
use std::borrow::Cow;
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::link::Link;
pub mod link;
pub mod object;
pub mod recipe;
pub trait ObjectTyped: Clone + Debug + Sized + 'static {
fn get_type() -> &'static str;
}
#[macro_export]
macro_rules! def_ld {
($obj_type: expr, $x: ty) => {
impl $crate::ObjectTyped for $x {
fn get_type() -> &'static str {
$obj_type
}
}
};
}
#[macro_export]
macro_rules! ld_union {
($z: ident, $($item_name: ident as $item_type: ty),+) => {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct $z {
$(
#[serde(flatten)]
$item_name: Box<$item_type>,
)+
}
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMore<T> {
One(T),
Many(Vec<T>),
}
impl<T> OneOrMore<T> {
pub fn into_vec(self) -> Vec<T> {
match self {
OneOrMore::One(x) => vec![x],
OneOrMore::Many(x) => x,
}
}
pub fn iter(&self) -> std::slice::Iter<'_, T> {
match self {
OneOrMore::One(x) => std::slice::from_ref(x).iter(),
OneOrMore::Many(x) => x.iter(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Base {
#[serde(rename = "@type")]
as_type: Option<OneOrMore<String>>,
#[serde(rename = "@id")]
as_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LinkOrUrl<'a> {
Url(Url),
Link(Box<Link<'a>>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Id(pub String);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ObjectSlot<'a, T: ObjectTyped> {
Id(Id),
Value(Cow<'a, T>),
}

View File

@ -0,0 +1,7 @@
use crate::def_ld;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct LinkMention {}
def_ld!("https://www.w3.org/ns/activitystreams#Mention", LinkMention);

View File

@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
use crate::{def_ld, ObjectSlot};
use crate::object::ObjectLinkUnion;
use crate::OneOrMore;
pub mod link_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Link<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#href")]
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#hreflang")]
#[serde(skip_serializing_if = "Option::is_none")]
pub href_lang: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#mediaType")]
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#rel")]
#[serde(skip_serializing_if = "Option::is_none")]
pub rel: Option<OneOrMore<String>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#name")]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#width")]
#[serde(skip_serializing_if = "Option::is_none")]
pub width: Option<u32>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#height")]
#[serde(skip_serializing_if = "Option::is_none")]
pub height: Option<u32>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preview")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Link", Link<'_>);

View File

@ -0,0 +1,211 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{def_ld, ObjectSlot, ObjectTyped, OneOrMore};
use crate::object::ObjectLinkUnion;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Activity<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#actor")]
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#target")]
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#result")]
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#origin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#instrument")]
#[serde(skip_serializing_if = "Option::is_none")]
pub instrument: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Activity", Activity<'_>, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct IntransitiveActivity {}
def_ld!(
"https://www.w3.org/ns/activitystreams#IntransitiveActivity",
IntransitiveActivity,
base as Activity
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAccept {}
def_ld!("https://www.w3.org/ns/activitystreams#Accept", ActivityAccept, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeAccept {}
def_ld!(
"https://www.w3.org/ns/activitystreams#TentativeAccept",
ActivityTentativeAccept,
base as ActivityAccept
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAdd;
def_ld!("https://www.w3.org/ns/activitystreams#Add", ActivityAdd, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityArrive;
def_ld!("https://www.w3.org/ns/activitystreams#Arrive", ActivityArrive, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityCreate;
def_ld!("https://www.w3.org/ns/activitystreams#Create", ActivityCreate, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDelete {}
def_ld!("https://www.w3.org/ns/activitystreams#Delete", ActivityDelete, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFollow {}
def_ld!("https://www.w3.org/ns/activitystreams#Follow", ActivityFollow, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityIgnore {}
def_ld!("https://www.w3.org/ns/activitystreams#Ignore", ActivityIgnore, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityJoin {}
def_ld!("https://www.w3.org/ns/activitystreams#Join", ActivityJoin, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLeave {}
def_ld!("https://www.w3.org/ns/activitystreams#Leave", ActivityLeave, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLike {}
def_ld!("https://www.w3.org/ns/activitystreams#Like", ActivityLike, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityOffer {}
def_ld!("https://www.w3.org/ns/activitystreams#Offer", ActivityOffer, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityInvite {}
def_ld!("https://www.w3.org/ns/activitystreams#Invite", ActivityInvite, base as ActivityOffer);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityReject {}
def_ld!("https://www.w3.org/ns/activitystreams#Reject", ActivityReject, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeReject {}
def_ld!(
"https://www.w3.org/ns/activitystreams#TentativeReject",
ActivityTentativeReject,
base as ActivityReject
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRemove {}
def_ld!("https://www.w3.org/ns/activitystreams#Remove", ActivityRemove, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUndo {}
def_ld!("https://www.w3.org/ns/activitystreams#Undo", ActivityUndo, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUpdate {}
def_ld!("https://www.w3.org/ns/activitystreams#Update", ActivityUpdate, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityView {}
def_ld!("https://www.w3.org/ns/activitystreams#View", ActivityView, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityListen {}
def_ld!("https://www.w3.org/ns/activitystreams#Listen", ActivityListen, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRead {}
def_ld!("https://www.w3.org/ns/activitystreams#Read", ActivityRead, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityMove {}
def_ld!("https://www.w3.org/ns/activitystreams#Move", ActivityMove, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTravel {}
def_ld!("https://www.w3.org/ns/activitystreams#Travel", ActivityTravel, base as IntransitiveActivity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAnnounce {}
def_ld!("https://www.w3.org/ns/activitystreams#Announce", ActivityAnnounce, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityBlock {}
def_ld!("https://www.w3.org/ns/activitystreams#Block", ActivityBlock, base as ActivityIgnore);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFlag {}
def_ld!("https://www.w3.org/ns/activitystreams#Flag", ActivityFlag, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDislike {}
def_ld!("https://www.w3.org/ns/activitystreams#Dislike", ActivityDislike, base as Activity);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ActivityClosedStatus<'a> {
Date(DateTime<Utc>),
Bool(bool),
Other(ObjectSlot<'a, ObjectLinkUnion>),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityQuestion<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#oneOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#anyOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#closed")]
#[serde(skip_serializing_if = "Option::is_none")]
pub closed: Option<OneOrMore<ActivityClosedStatus<'a>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Question", ActivityQuestion<'_>, base as IntransitiveActivity);

View File

@ -0,0 +1,65 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{def_ld, ObjectSlot};
use crate::object::{Collection, ObjectLinkUnion, OrderedCollection};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorActivityPubProps<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#inbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub inbox: Option<ObjectSlot<'a, OrderedCollection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#outbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub outbox: Option<ObjectSlot<'a, OrderedCollection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#following")]
#[serde(skip_serializing_if = "Option::is_none")]
pub following: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#followers")]
#[serde(skip_serializing_if = "Option::is_none")]
pub followers: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#liked")]
#[serde(skip_serializing_if = "Option::is_none")]
pub liked: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#shares")]
#[serde(skip_serializing_if = "Option::is_none")]
pub shares: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preferredUsername")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preferred_username: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#endpoints")]
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoints: Option<Value>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorApplication {}
def_ld!("https://www.w3.org/ns/activitystreams#Application", ActorApplication, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorGroup {}
def_ld!("https://www.w3.org/ns/activitystreams#Group", ActorGroup, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorOrganization {}
def_ld!("https://www.w3.org/ns/activitystreams#Organization", ActorOrganization, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorPerson {}
def_ld!("https://www.w3.org/ns/activitystreams#Person", ActorPerson, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorService {}
def_ld!("https://www.w3.org/ns/activitystreams#Service", ActorService, base as Object);

View File

@ -0,0 +1,226 @@
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{ld_union, LinkOrUrl, ObjectSlot, OneOrMore};
use crate::def_ld;
use crate::link::Link;
use crate::object::object_types::ObjectImageLinkUnion;
pub mod activity;
pub mod actor;
pub mod object_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Object<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#attachment")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#attributedTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attributed_to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#audience")]
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#content")]
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#contentMap")]
#[serde(skip_serializing_if = "Option::is_none")]
pub content_map: Option<HashMap<String, String>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#context")]
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#name")]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#nameMap")]
#[serde(skip_serializing_if = "Option::is_none")]
pub name_map: Option<HashMap<String, String>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#endTime")]
#[serde(skip_serializing_if = "Option::is_none")]
pub end_time: Option<DateTime<Utc>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#generator")]
#[serde(skip_serializing_if = "Option::is_none")]
pub generator: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#icon")]
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<OneOrMore<ObjectSlot<'a, ObjectImageLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#image")]
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<OneOrMore<ObjectSlot<'a, ObjectImageLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#inReplyTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#location")]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preview")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#published")]
#[serde(skip_serializing_if = "Option::is_none")]
pub published: Option<DateTime<Utc>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#replies")]
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<ObjectSlot<'a, CollectionLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#startTime")]
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<DateTime<Utc>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#summary")]
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#summaryMap")]
#[serde(skip_serializing_if = "Option::is_none")]
pub summary_map: Option<HashMap<String, String>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#tag")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#updated")]
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<DateTime<Utc>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#url")]
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<OneOrMore<LinkOrUrl<'a>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#to")]
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bto")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bto: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#cc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bcc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#mediaType")]
#[serde(skip_serializing_if = "Option::is_none")]
pub media_type: Option<String>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#duration")]
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
// ActivityPub
#[serde(rename = "https://www.w3.org/ns/activitystreams#source")]
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<ObjectSlot<'a, Object<'a>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Object", Object<'_>);
ld_union!(
ObjectLinkUnion,
object_props as Object,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Collection {
#[serde(rename = "https://www.w3.org/ns/activitystreams#totalItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub total_items: Option<u32>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#current")]
#[serde(skip_serializing_if = "Option::is_none")]
pub current: Option<RefCollectionPageLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#first")]
#[serde(skip_serializing_if = "Option::is_none")]
pub first: Option<RefCollectionPageLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#last")]
#[serde(skip_serializing_if = "Option::is_none")]
pub last: Option<RefCollectionPageLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#items")]
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<OneOrMore<RefObjectLinkUnion>>,
}
def_ld!(
"https://www.w3.org/ns/activitystreams#Collection",
Collection
);
ld_union!(
CollectionLinkUnion,
collection_props as Collection,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct OrderedCollection {
#[serde(rename = "https://www.w3.org/ns/activitystreams#orderedItems")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ordered_items: Option<OneOrMore<RefObjectLinkUnion>>,
}
def_ld!(
"https://www.w3.org/ns/activitystreams#OrderedCollection",
OrderedCollection
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct CollectionPage {
#[serde(rename = "https://www.w3.org/ns/activitystreams#next")]
#[serde(skip_serializing_if = "Option::is_none")]
pub next: Option<RefCollectionPageLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#prev")]
#[serde(skip_serializing_if = "Option::is_none")]
pub prev: Option<RefCollectionPageLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#partOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub part_of: Option<RefCollectionLinkUnion>,
}
def_ld!(
"https://www.w3.org/ns/activitystreams#CollectionPage",
CollectionPage
);
ld_union!(
CollectionPageLinkUnion,
page_props as CollectionPage,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct OrderedCollectionPage {
#[serde(rename = "https://www.w3.org/ns/activitystreams#startIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub start_index: Option<u32>,
}
def_ld!(
"https://www.w3.org/ns/activitystreams#OrderedCollectionPage",
OrderedCollectionPage
);

View File

@ -0,0 +1,119 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{def_ld, ld_union, ObjectSlot, ObjectTyped, OneOrMore};
use crate::link::Link;
use crate::object::{Object, ObjectLinkUnion};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectRelationship<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#subject")]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<ObjectSlot<'a, ObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#relationship")]
#[serde(skip_serializing_if = "Option::is_none")]
pub relationship: Option<OneOrMore<ObjectSlot<'a, Object<'a>>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Relationship", ObjectRelationship, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectArticle {}
def_ld!("https://www.w3.org/ns/activitystreams#Article", ObjectArticle, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectDocument {}
def_ld!("https://www.w3.org/ns/activitystreams#Document", ObjectDocument, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectAudio {}
def_ld!("https://www.w3.org/ns/activitystreams#Audio", ObjectAudio, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectImage {}
def_ld!("https://www.w3.org/ns/activitystreams#Image", ObjectImage, base as ObjectDocument);
ld_union!(
ObjectImageLinkUnion,
image_props as ObjectImage,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectVideo {}
def_ld!("https://www.w3.org/ns/activitystreams#Video", ObjectVideo, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectNote {}
def_ld!("https://www.w3.org/ns/activitystreams#Note", ObjectNote, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectPage {}
def_ld!("https://www.w3.org/ns/activitystreams#Page", ObjectPage, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectEvent {}
def_ld!("https://www.w3.org/ns/activitystreams#Event", ObjectEvent, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectPlace {
#[serde(rename = "https://www.w3.org/ns/activitystreams#accuracy")]
#[serde(skip_serializing_if = "Option::is_none")]
pub accuracy: Option<f64>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#altitude")]
#[serde(skip_serializing_if = "Option::is_none")]
pub altitude: Option<f64>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#latitude")]
#[serde(skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#longitude")]
#[serde(skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#radius")]
#[serde(skip_serializing_if = "Option::is_none")]
pub radius: Option<f64>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#units")]
#[serde(skip_serializing_if = "Option::is_none")]
pub units: Option<String>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Place", ObjectPlace, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectProfile<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#describes")]
#[serde(skip_serializing_if = "Option::is_none")]
pub describes: Option<ObjectSlot<'a, ObjectProfile<'a>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Profile", ObjectProfile, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectTombstone {
#[serde(rename = "https://www.w3.org/ns/activitystreams#formerType")]
#[serde(skip_serializing_if = "Option::is_none")]
pub former_type: Option<OneOrMore<String>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#deleted")]
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted: Option<DateTime<Utc>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Tombstone", ObjectTombstone, base as Object);

View File

@ -0,0 +1,87 @@
use crate::object::activity::ActivityBlock;
use crate::object::Object;
use crate::recipe::RecipeError;
use crate::{extract_field, Id, ObjectRaw};
use serde::Deserialize;
#[derive(Clone, Debug)]
pub struct Block {
pub id: Id,
pub actor: Id,
pub object: Id,
pub to: Id,
}
impl Block {
fn parse(object: &ObjectRaw<impl AsRef<Object>>) -> Result<Block, RecipeError> {
let json = object.as_json();
let data: &Object = (*object.data).as_ref();
let ap_type = extract_field!(data, as_type, unwrap);
if ap_type.iter().all(|t| t != "Block") {
return Err(RecipeError::UnexpectedType(format!("{:?}", ap_type)));
}
let block = ActivityBlock::deserialize(json)?;
Ok(Block {
id: Id(extract_field!(block, as_id, unwrap | owned)),
actor: Id(extract_field!(
block,
actor,
unwrap | single | as_id | owned
)),
object: Id(extract_field!(
block,
object,
unwrap | single | as_id | owned
)),
to: Id(extract_field!(block, to, unwrap | single | as_id | owned)),
})
}
}
#[cfg(test)]
mod tests {
use crate::object::activity::{Activity, ActivityBlock, ActivityIgnore};
use crate::object::Object;
use crate::ObjectRaw;
use serde::Deserialize;
use serde_json::json;
#[test]
fn parse_json() {
let json = json!({
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://a.example.com/blocks/1",
"type": "Block",
"actor": "https://a.example.com/users/1",
"object": "https://b.example.com/users/2",
"to": "https://b.example.com/users/2",
});
let object = ObjectRaw::<Activity>::deserialize(json).unwrap();
let block = super::Block::parse(&object).unwrap();
println!("{:#?}", block);
}
#[test]
fn parse_json_with_multiple_types() {
let json = json!({
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://a.example.com/blocks/1",
"type": ["Block", "https://example.com/SomeOtherType"],
"actor": "https://a.example.com/users/1",
"object": "https://b.example.com/users/2",
"to": "https://b.example.com/users/2",
});
let object = ObjectRaw::<Activity>::deserialize(json).unwrap();
let block = super::Block::parse(&object).unwrap();
println!("{:#?}", block);
}
}

View File

@ -0,0 +1,90 @@
use thiserror::Error;
use crate::OneOrMore;
#[macro_use]
pub mod block;
#[derive(Debug, Error)]
pub enum RecipeError {
#[error("Serde error")]
DeserializationError(#[from] serde_json::Error),
#[error("Unexpected type: {0}")]
UnexpectedType(String),
#[error("Missing field: {0}")]
MissingField(&'static str),
#[error("Single item expected: {0}")]
ExpectedSingleField(&'static str),
#[error("Missing ID")]
MissingId,
}
#[derive(Debug, Error)]
pub struct FieldPipeline<T> {
value: T,
field_name: &'static str,
}
#[macro_export]
macro_rules! extract_field {
($obj: expr, $field: ident, $($step:ident)|+ $(|)?) => {{
let pipeline = $crate::recipe::FieldPipeline {
value: $obj.$field.as_ref(),
field_name: stringify!($field),
};
$(
let pipeline = pipeline.$step()?;
)+
pipeline.into_inner()
}};
}
impl<T> FieldPipeline<Option<T>> {
fn unwrap(self) -> Result<FieldPipeline<T>, RecipeError> {
Ok(FieldPipeline {
value: self
.value
.ok_or(RecipeError::MissingField(self.field_name))?,
field_name: self.field_name,
})
}
}
impl<T: Resolvable> FieldPipeline<&T> {
fn as_id(&self) -> Result<FieldPipeline<&str>, RecipeError> {
Ok(FieldPipeline {
value: self.value.unresolve().ok_or(RecipeError::MissingId)?,
field_name: self.field_name,
})
}
}
impl<'a, T> FieldPipeline<&'a OneOrMore<T>> {
fn single(self) -> Result<FieldPipeline<&'a T>, RecipeError> {
Ok(FieldPipeline {
value: match self.value {
OneOrMore::One(x) => Ok(x),
OneOrMore::Many(vec) if vec.len() == 1 => Ok(vec.first().unwrap()),
OneOrMore::Many(_) => Err(RecipeError::ExpectedSingleField(self.field_name)),
}?,
field_name: self.field_name,
})
}
}
impl<T: ToOwned + ?Sized> FieldPipeline<&T> {
fn owned(&self) -> Result<FieldPipeline<T::Owned>, RecipeError> {
Ok(FieldPipeline {
value: self.value.to_owned(),
field_name: self.field_name,
})
}
}
impl<T> FieldPipeline<T> {
fn into_inner(self) -> T {
self.value
}
}

View File

@ -0,0 +1,94 @@
use futures_util::stream::StreamExt;
use magnetar_activity_pub::Resolver;
use magnetar_core::web_model::content_type::ContentActivityStreams;
use reqwest::Client;
use serde::Deserialize;
use serde_json::Value;
use thiserror::Error;
use url::Url;
#[derive(Debug, Clone)]
pub struct DereferencingClient {
pub client: Client,
pub body_limit: usize,
}
#[derive(Debug, Error)]
pub enum DereferencingClientBuilderError {
#[error("Reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
}
#[derive(Debug, Error)]
pub enum DereferencingClientError {
#[error("Reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Body limit exceeded error")]
BodyLimitExceededError,
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("Client error: {0}")]
Other(String),
}
impl DereferencingClient {
pub fn new(
force_https: bool,
body_limit: usize,
) -> Result<DereferencingClient, reqwest::Error> {
let client = Client::builder().https_only(force_https).build()?;
Ok(DereferencingClient { client, body_limit })
}
pub async fn dereference(&self, url: Url) -> Result<Value, DereferencingClientError> {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static(ContentActivityStreams.as_ref()),
);
let response = self
.client
.get(url)
.headers(headers)
.send()
.await
.map_err(DereferencingClientError::ReqwestError)?;
let mut body = response.bytes_stream();
let mut data = Vec::with_capacity(4096);
while let Some(buf) = body.next().await {
let chunk = buf.map_err(DereferencingClientError::ReqwestError)?;
if data.len() + chunk.len() > self.body_limit {
return Err(DereferencingClientError::BodyLimitExceededError);
}
data.extend_from_slice(&chunk);
}
let json =
serde_json::from_slice::<Value>(&data).map_err(DereferencingClientError::JsonError)?;
Ok(json)
}
}
#[async_trait::async_trait]
impl Resolver for DereferencingClient {
type Error = DereferencingClientError;
async fn resolve<T: for<'a> Deserialize<'a>>(&self, id: &str) -> Result<T, Self::Error> {
let url = id.parse().map_err(DereferencingClientError::InvalidUrl)?;
let json = self.dereference(url).await?;
serde_json::from_value(json).map_err(DereferencingClientError::JsonError)
}
}

View File

@ -0,0 +1,72 @@
use axum::http::{HeaderMap, Method};
use hyper::client::HttpConnector;
use hyper::http::uri;
use hyper::{header, Body, Client, Request, Response, Uri};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ProxyClientError {
#[error("Client error: {0}")]
ClientError(String),
#[error("URL error: {0}")]
UriError(#[from] uri::InvalidUri),
#[error("HTTP error: {0}")]
HttpError(#[from] axum::http::Error),
#[error("Hyper error: {0}")]
HyperError(#[from] hyper::Error),
}
pub struct ProxyClient {
pub client: Client<HttpConnector, Body>,
pub upstream: String,
}
impl ProxyClient {
pub fn new(upstream: String) -> ProxyClient {
let client = Client::builder().set_host(false).build_http();
ProxyClient { client, upstream }
}
pub async fn send(
&self,
host: &str,
path: &str,
method: Method,
body: Body,
headers_in: &HeaderMap,
) -> Result<Response<Body>, ProxyClientError> {
let mut builder = Request::builder();
let Some(headers) = builder.headers_mut() else {
return Err(ProxyClientError::ClientError("No headers".to_owned()));
};
*headers = headers_in.clone();
headers.insert(
header::HOST,
host.parse().map_err(|e| {
ProxyClientError::ClientError(format!("Invalid header value: {e:?}"))
})?,
);
let uri = format!("{}/{}", self.upstream, path)
.parse::<Uri>()
.map_err(ProxyClientError::UriError)?;
let request = builder
.method(method)
.uri(uri)
.body(body)
.map_err(ProxyClientError::HttpError)?;
let response = self
.client
.request(request)
.await
.map_err(ProxyClientError::HyperError)?;
Ok(response)
}
}

2
src/client/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod dereferencing_client;
pub mod forwarding_client;