Compare commits
2 Commits
8b6bdd6e02
...
e21c5a8132
Author | SHA1 | Date |
---|---|---|
Natty | e21c5a8132 | |
Natty | 59f1d834d4 |
|
@ -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"
|
||||
|
|
|
@ -8,6 +8,7 @@ license = "AGPL-3.0-only"
|
|||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"ext_activity_streams",
|
||||
"ext_federation",
|
||||
"ext_nodeinfo",
|
||||
"ext_webfinger",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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>),
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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<'_>);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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
|
||||
);
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod dereferencing_client;
|
||||
pub mod forwarding_client;
|
Loading…
Reference in New Issue