Initial AP commit

This commit is contained in:
Natty 2023-09-30 16:28:33 +02:00
parent 2ffb82895c
commit 59f1d834d4
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
20 changed files with 1575 additions and 0 deletions

235
Cargo.lock generated
View File

@ -584,6 +584,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
@ -742,6 +752,15 @@ dependencies = [
"serde",
]
[[package]]
name = "encoding_rs"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -818,6 +837,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
@ -1188,6 +1222,19 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
@ -1284,6 +1331,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-terminal"
version = "0.4.9"
@ -1377,13 +1430,16 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
name = "magnetar"
version = "0.2.0"
dependencies = [
"async-trait",
"axum",
"cached",
"cfg-if",
"chrono",
"dotenvy",
"futures-util",
"headers",
"hyper",
"magnetar_activity_pub",
"magnetar_calckey_model",
"magnetar_common",
"magnetar_core",
@ -1392,6 +1448,7 @@ dependencies = [
"magnetar_webfinger",
"miette",
"percent-encoding",
"reqwest",
"serde",
"serde_json",
"strum",
@ -1403,6 +1460,20 @@ dependencies = [
"tracing",
"tracing-subscriber",
"unicode-segmentation",
"url",
]
[[package]]
name = "magnetar_activity_pub"
version = "0.2.0"
dependencies = [
"async-trait",
"chrono",
"serde",
"serde_json",
"thiserror",
"tokio",
"url",
]
[[package]]
@ -1603,6 +1674,24 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1707,6 +1796,50 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "openssl"
version = "0.10.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
dependencies = [
"bitflags 2.3.3",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.28",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "ordered-float"
version = "3.7.0"
@ -2139,6 +2272,43 @@ dependencies = [
"bytecheck",
]
[[package]]
name = "reqwest"
version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
"base64 0.21.2",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
@ -2291,6 +2461,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -2472,6 +2651,29 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.180"
@ -3132,6 +3334,16 @@ dependencies = [
"syn 2.0.28",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.14"
@ -3480,6 +3692,7 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
@ -3571,6 +3784,18 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
@ -3740,6 +3965,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys",
]
[[package]]
name = "wyz"
version = "0.5.1"

View File

@ -8,6 +8,7 @@ license = "AGPL-3.0-only"
[workspace]
members = [
".",
"ext_activity_pub",
"ext_nodeinfo",
"ext_webfinger",
"ext_calckey_model",
@ -62,6 +63,7 @@ wasm-bindgen-futures = "0.4"
web-sys = "0.3"
[dependencies]
magnetar_activity_pub = { path = "./ext_activity_pub" }
magnetar_core = { path = "./core" }
magnetar_common = { path = "./magnetar_common" }
magnetar_webfinger = { path = "./ext_webfinger" }
@ -73,12 +75,17 @@ cached = { workspace = true }
chrono = { workspace = true }
dotenvy = { workspace = true }
async-trait = { workspace = true }
axum = { workspace = true, features = ["macros", "headers"] }
futures-util = { workspace = true }
headers = { workspace = true }
hyper = { workspace = true, features = ["full"] }
tokio = { workspace = true, features = ["full"] }
tower = { workspace = true }
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
url = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing = { workspace = true }

View File

@ -0,0 +1,19 @@
[package]
name = "magnetar_activity_pub"
version.workspace = true
edition.workspace = true
license = "MIT OR Apache-2.0"
[lib]
crate-type = ["rlib"]
[dependencies]
async-trait = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }

237
ext_activity_pub/src/lib.rs Normal file
View File

@ -0,0 +1,237 @@
pub mod link;
pub mod object;
pub mod recipe;
use crate::link::Link;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::borrow::Cow;
use std::fmt::Debug;
use std::ops::Deref;
use url::Url;
pub trait ObjectSingle: Clone + Debug + Sized + 'static {
fn get_type() -> &'static str;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ObjectRaw<T> {
#[serde(flatten)]
data: Box<T>,
#[serde(flatten)]
raw: Value,
}
impl<T> Deref for ObjectRaw<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> ObjectRaw<T> {
pub fn into_inner(self) -> Box<T> {
self.data
}
}
impl<T> AsRef<T> for ObjectRaw<T> {
fn as_ref(&self) -> &T {
self.as_data()
}
}
impl<T> ObjectRaw<T> {
pub fn as_data(&self) -> &T {
&self.data
}
pub fn as_json(&self) -> &Value {
&self.raw
}
}
#[async_trait::async_trait]
pub trait Resolver: Send + Sync {
type Error: Send + Sync;
async fn resolve<T: for<'a> Deserialize<'a>>(&self, id: &str) -> Result<T, Self::Error>;
}
#[async_trait::async_trait]
pub trait Resolvable {
type Output: Clone + Send + Sync;
async fn resolve<R: Resolver>(&self, resolver: &R) -> Result<Cow<Self::Output>, R::Error>;
fn unresolve(&self) -> Option<&str>;
}
#[macro_export]
macro_rules! def_ld {
($obj_type: expr, $x: ty) => {
impl $crate::ObjectSingle for $x {
fn get_type() -> &'static str {
$obj_type
}
}
};
}
#[macro_export]
macro_rules! ld_document {
($y: ident, $x: ty) => {
impl $y {
pub async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<ObjectRaw<$x>>, R::Error> {
match self {
$y::Object(obj) => Ok(std::borrow::Cow::Borrowed(obj)),
$y::Remote(Id(link)) => {
Ok(std::borrow::Cow::Owned(resolver.resolve(link).await?))
}
}
}
pub fn unresolve(&self) -> Option<&str> {
match self {
$y::Object(obj) => obj.as_data().as_id.as_deref(),
$y::Remote(Id(id)) => Some(&id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum $y {
Remote(Id),
Object(Box<ObjectRaw<$x>>),
}
#[async_trait::async_trait]
impl $crate::Resolvable for $y {
type Output = $crate::ObjectRaw<$x>;
async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<Self::Output>, R::Error> {
self.resolve(resolver).await
}
fn unresolve(&self) -> Option<&str> {
self.unresolve()
}
}
/*
impl ObjectRaw<$x> {
pub fn try_cast<T>() -> T {}
}
*/
};
}
#[macro_export]
macro_rules! ld_union {
($y: ident, $z: ident, $($item_name: ident as $item_type: ty),+) => {
impl $y {
pub async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<ObjectRaw<$z>>, R::Error> {
match self {
$y::Object(obj) => Ok(std::borrow::Cow::Borrowed(obj)),
$y::Remote(Id(link)) => {
Ok(std::borrow::Cow::Owned(resolver.resolve(link).await?))
}
}
}
pub fn unresolve(&self) -> Option<&str> {
match self {
$y::Object(obj) => obj.as_data().base.as_id.as_deref(),
$y::Remote(Id(id)) => Some(&id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct $z {
#[serde(flatten)]
pub base: $crate::Base,
$(
#[serde(flatten)]
$item_name: Box<$item_type>,
)+
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum $y {
Remote(Id),
Object(ObjectRaw<$z>),
}
#[async_trait::async_trait]
impl $crate::Resolvable for $y {
type Output = $crate::ObjectRaw<$z>;
async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<Self::Output>, R::Error> {
self.resolve(resolver).await
}
fn unresolve(&self) -> Option<&str> {
self.unresolve()
}
}
};
}
#[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 {
Url(Url),
Link(Box<Link>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Id(pub String);

View File

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

View File

@ -0,0 +1,44 @@
use crate::object::RefObjectLinkUnion;
use crate::OneOrMore;
use crate::{def_ld, ld_document, Id, ObjectRaw};
use serde::{Deserialize, Serialize};
pub mod link_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Link {
#[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<RefObjectLinkUnion>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Link", Link);
ld_document!(RefLink, Link);

View File

@ -0,0 +1,240 @@
use crate::object::{Object, RefObjectLinkUnion};
use crate::{def_ld, ld_document, Id, ObjectRaw, ObjectSingle, OneOrMore};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Activity {
#[serde(rename = "https://www.w3.org/ns/activitystreams#actor")]
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#target")]
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#result")]
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#origin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#instrument")]
#[serde(skip_serializing_if = "Option::is_none")]
pub instrument: Option<OneOrMore<RefObjectLinkUnion>>,
}
def_ld!("Activity", Activity, base as Object);
ld_document!(RefActivity, Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct IntransitiveActivity;
def_ld!(
"IntransitiveActivity",
IntransitiveActivity,
base as Activity
);
ld_document!(RefIntransitiveActivity, IntransitiveActivity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAccept;
def_ld!("Accept", ActivityAccept, base as Activity);
ld_document!(RefActivityAccept, ActivityAccept);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeAccept;
def_ld!(
"TentativeAccept",
ActivityTentativeAccept,
base as ActivityAccept
);
ld_document!(RefActivityTentativeAccept, ActivityTentativeAccept);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAdd;
def_ld!("Add", ActivityAdd, base as Activity);
ld_document!(RefActivityAdd, ActivityAdd);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityArrive;
def_ld!("Arrive", ActivityArrive, base as Activity);
ld_document!(RefActivityArrive, ActivityArrive);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityCreate;
def_ld!("Create", ActivityCreate, base as Activity);
ld_document!(RefActivityCreate, ActivityCreate);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDelete;
def_ld!("Delete", ActivityDelete, base as Activity);
ld_document!(RefActivityDelete, ActivityDelete);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFollow;
def_ld!("Follow", ActivityFollow, base as Activity);
ld_document!(RefActivityFollow, ActivityFollow);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityIgnore;
def_ld!("Ignore", ActivityIgnore, base as Activity);
ld_document!(RefActivityIgnore, ActivityIgnore);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityJoin;
def_ld!("Join", ActivityJoin, base as Activity);
ld_document!(RefActivityJoin, ActivityJoin);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLeave;
def_ld!("Leave", ActivityLeave, base as Activity);
ld_document!(RefActivityLeave, ActivityLeave);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLike;
def_ld!("Like", ActivityLike, base as Activity);
ld_document!(RefActivityLike, ActivityLike);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityOffer;
def_ld!("Offer", ActivityOffer, base as Activity);
ld_document!(RefActivityOffer, ActivityOffer);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityInvite;
def_ld!("Invite", ActivityInvite, base as ActivityOffer);
ld_document!(RefActivityInvite, ActivityInvite);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityReject;
def_ld!("Reject", ActivityReject, base as Activity);
ld_document!(RefActivityReject, ActivityReject);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeReject;
def_ld!(
"TentativeReject",
ActivityTentativeReject,
base as ActivityReject
);
ld_document!(RefActivityTentativeReject, ActivityTentativeReject);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRemove;
def_ld!("Remove", ActivityRemove, base as Activity);
ld_document!(RefActivityRemove, ActivityRemove);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUndo;
def_ld!("Undo", ActivityUndo, base as Activity);
ld_document!(RefActivityUndo, ActivityUndo);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUpdate;
def_ld!("Update", ActivityUpdate, base as Activity);
ld_document!(RefActivityUpdate, ActivityUpdate);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityView;
def_ld!("View", ActivityView, base as Activity);
ld_document!(RefActivityView, ActivityView);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityListen;
def_ld!("Listen", ActivityListen, base as Activity);
ld_document!(RefActivityListen, ActivityListen);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRead;
def_ld!("Read", ActivityRead, base as Activity);
ld_document!(RefActivityRead, ActivityRead);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityMove;
def_ld!("Move", ActivityMove, base as Activity);
ld_document!(RefActivityMove, ActivityMove);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTravel;
def_ld!("Travel", ActivityTravel, base as IntransitiveActivity);
ld_document!(RefActivityTravel, ActivityTravel);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAnnounce;
def_ld!("Announce", ActivityAnnounce, base as Activity);
ld_document!(RefActivityAnnounce, ActivityAnnounce);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityBlock;
def_ld!("Block", ActivityBlock, base as ActivityIgnore);
ld_document!(RefActivityBlock, ActivityBlock);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFlag;
def_ld!("Flag", ActivityFlag, base as Activity);
ld_document!(RefActivityFlag, ActivityFlag);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDislike;
def_ld!("Dislike", ActivityDislike, base as Activity);
ld_document!(RefActivityDislike, ActivityDislike);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ActivityClosedStatus {
Date(DateTime<Utc>),
Bool(bool),
Other(RefObjectLinkUnion),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityQuestion {
#[serde(rename = "https://www.w3.org/ns/activitystreams#oneOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#anyOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#closed")]
#[serde(skip_serializing_if = "Option::is_none")]
pub closed: Option<OneOrMore<ActivityClosedStatus>>,
}
def_ld!("Question", ActivityQuestion, base as IntransitiveActivity);
ld_document!(RefActivityQuestion, ActivityQuestion);

View File

@ -0,0 +1,68 @@
use crate::object::{RefCollection, RefOrderedCollection};
use crate::{def_ld, ld_document, Id, ObjectRaw};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorActivityPubProps {
#[serde(rename = "https://www.w3.org/ns/activitystreams#inbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub inbox: Option<RefOrderedCollection>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#outbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub outbox: Option<RefOrderedCollection>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#following")]
#[serde(skip_serializing_if = "Option::is_none")]
pub following: Option<RefCollection>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#followers")]
#[serde(skip_serializing_if = "Option::is_none")]
pub followers: Option<RefCollection>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#liked")]
#[serde(skip_serializing_if = "Option::is_none")]
pub liked: Option<RefCollection>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#shares")]
#[serde(skip_serializing_if = "Option::is_none")]
pub shares: Option<RefCollection>,
#[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<String>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorApplication;
def_ld!("Application", ActorApplication, base as Object);
ld_document!(RefActorApplication, ActorApplication);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorGroup;
def_ld!("Group", ActorGroup, base as Object);
ld_document!(RefActorGroup, ActorGroup);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorOrganization;
def_ld!("Organization", ActorOrganization, base as Object);
ld_document!(RefActorOrganization, ActorOrganization);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorPerson;
def_ld!("Person", ActorPerson, base as Object);
ld_document!(RefActorPerson, ActorPerson);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorService;
def_ld!("Service", ActorService, base as Object);
ld_document!(RefActorService, ActorService);

View File

@ -0,0 +1,233 @@
use crate::link::Link;
use crate::object::object_types::RefObjectImageLinkUnion;
use crate::{def_ld, Id};
use crate::{ld_document, ld_union, Base, LinkOrUrl, ObjectRaw, ObjectSingle, OneOrMore};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub mod activity;
pub mod actor;
pub mod object_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#attachment")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#attributedTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attributed_to: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#audience")]
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<OneOrMore<RefObjectLinkUnion>>,
#[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<RefObjectLinkUnion>>,
#[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<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#icon")]
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<OneOrMore<RefObjectImageLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#image")]
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<OneOrMore<RefObjectImageLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#inReplyTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#location")]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preview")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<OneOrMore<RefObjectLinkUnion>>,
#[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<RefCollectionLinkUnion>,
#[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<RefObjectLinkUnion>>,
#[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>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#to")]
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bto")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bto: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#cc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bcc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<OneOrMore<RefObjectLinkUnion>>,
#[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<RefObject>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Object", Object);
ld_document!(RefObject, Object);
ld_union!(
RefObjectLinkUnion,
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_document!(RefCollection, Collection);
ld_union!(
RefCollectionLinkUnion,
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
);
ld_document!(RefOrderedCollection, 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_document!(RefCollectionPage, CollectionPage);
ld_union!(
RefCollectionPageLinkUnion,
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
);
ld_document!(RefOrderedCollectionPage, OrderedCollectionPage);

View File

@ -0,0 +1,131 @@
use crate::link::Link;
use crate::object::{Object, RefObject, RefObjectLinkUnion};
use crate::{def_ld, ld_document, ld_union, Id, ObjectRaw, ObjectSingle, OneOrMore};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectRelationship {
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#subject")]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<RefObjectLinkUnion>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#relationship")]
#[serde(skip_serializing_if = "Option::is_none")]
pub relationship: Option<OneOrMore<RefObject>>,
}
def_ld!("Relationship", ObjectRelationship, base as Object);
ld_document!(RefObjectRelationship, ObjectRelationship);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectArticle;
def_ld!("Article", ObjectArticle, base as Object);
ld_document!(RefObjectArticle, ObjectArticle);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectDocument;
def_ld!("Document", ObjectDocument, base as Object);
ld_document!(RefObjectDocument, ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectAudio;
def_ld!("Audio", ObjectAudio, base as ObjectDocument);
ld_document!(RefObjectAudio, ObjectAudio);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectImage;
def_ld!("Image", ObjectImage, base as ObjectDocument);
ld_document!(RefObjectImage, ObjectImage);
ld_union!(
RefObjectImageLinkUnion,
ObjectImageLinkUnion,
image_props as ObjectImage,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectVideo;
def_ld!("Video", ObjectVideo, base as ObjectDocument);
ld_document!(RefObjectVideo, ObjectVideo);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectNote;
def_ld!("Note", ObjectNote, base as Object);
ld_document!(RefObjectNote, ObjectNote);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectPage;
def_ld!("Page", ObjectPage, base as ObjectDocument);
ld_document!(RefObjectPage, ObjectPage);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectEvent;
def_ld!("Event", ObjectEvent, base as Object);
ld_document!(RefObjectEvent, ObjectEvent);
#[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!("Place", ObjectPlace, base as Object);
ld_document!(RefObjectPlace, ObjectPlace);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectProfile {
#[serde(rename = "https://www.w3.org/ns/activitystreams#describes")]
#[serde(skip_serializing_if = "Option::is_none")]
pub describes: Option<RefObject>,
}
def_ld!("Profile", ObjectProfile, base as Object);
ld_document!(RefObjectProfile, ObjectProfile);
#[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!("Tombstone", ObjectTombstone, base as Object);
ld_document!(RefObjectTombstone, ObjectTombstone);

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

View File

@ -0,0 +1,97 @@
use crate::{OneOrMore, Resolvable};
use thiserror::Error;
#[macro_use]
pub mod account_move;
#[macro_use]
pub mod block;
#[macro_use]
pub mod flag;
#[macro_use]
pub mod poll;
#[macro_use]
pub mod tag;
#[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

View File

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;

View File

@ -1,4 +1,5 @@
mod api_v1;
pub mod client;
pub mod model;
pub mod nodeinfo;
pub mod service;