From 7b538c0a0c405a3f0867c5593a78d9d98df1072c Mon Sep 17 00:00:00 2001 From: emilis Date: Fri, 4 Nov 2022 16:01:33 +0000 Subject: [PATCH] Added basic expansion logic and ability to deserialize single objects into vectors of that type --- Cargo.lock | 345 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 12 +- src/astreams/context.rs | 264 +++++++++++++++++++++++++++++ src/astreams/mod.rs | 95 +++++++++++ src/astreams/note.rs | 64 +++++++ src/astreams/resolve.rs | 66 ++++++++ src/astreams/serde_ext.rs | 41 +++++ src/main.rs | 30 +--- 8 files changed, 887 insertions(+), 30 deletions(-) create mode 100644 src/astreams/context.rs create mode 100644 src/astreams/mod.rs create mode 100644 src/astreams/note.rs create mode 100644 src/astreams/resolve.rs create mode 100644 src/astreams/serde_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 990d3b6..65c7944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,9 +36,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -211,6 +211,22 @@ dependencies = [ "version_check", ] +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -250,6 +266,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + [[package]] name = "failure" version = "0.1.8" @@ -278,12 +303,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "flabk" version = "0.0.1" dependencies = [ "anyhow", "argon2", + "async-trait", "axum", "base-62", "handlebars", @@ -291,6 +326,7 @@ dependencies = [ "mime_guess", "rand", "rand_core", + "reqwest", "rust-embed", "serde", "serde_json", @@ -305,6 +341,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.1.0" @@ -395,6 +446,25 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlebars" version = "4.3.3" @@ -409,6 +479,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -477,6 +553,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -490,6 +567,54 @@ 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 = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itoa" version = "1.0.3" @@ -519,6 +644,12 @@ dependencies = [ "simple_asn1", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.132" @@ -602,6 +733,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 = "num-bigint" version = "0.2.6" @@ -683,6 +832,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -826,6 +1020,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "postgres-protocol" version = "0.6.4" @@ -920,6 +1120,52 @@ dependencies = [ "bitflags", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +dependencies = [ + "base64", + "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" @@ -990,12 +1236,45 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.144" @@ -1177,6 +1456,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.34" @@ -1262,6 +1555,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-postgres" version = "0.7.7" @@ -1445,6 +1748,23 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1503,6 +1823,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.82" @@ -1615,3 +1947,12 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index 90e0cfb..4818e56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow = "1.0.64" argon2 = "0.4.1" +async-trait = "0.1.58" axum = "0.5.16" base-62 = "0.1.1" handlebars = "4.3.3" @@ -15,8 +16,17 @@ jsonwebtoken = "8.1.1" mime_guess = "2.0.4" rand = "0.8.5" rand_core = { version = "0.6.3", features = ["std"] } +reqwest = { version = "0.11.12", features = [ + "__tls", + "default-tls", + "hyper-tls", + "native-tls-crate", + "tokio-native-tls", + "serde_json", + "json", +] } rust-embed = "6.4.0" -serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]} +serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"] } serde_json = "1.0.85" tokio = { version = "1", features = ["full"] } tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] } diff --git a/src/astreams/context.rs b/src/astreams/context.rs new file mode 100644 index 0000000..397c6b3 --- /dev/null +++ b/src/astreams/context.rs @@ -0,0 +1,264 @@ +use serde::Deserialize; + +pub const CONTEXT_ID: &str = "https://www.w3.org/ns/activitystreams"; + +#[derive(Default, Debug, Clone, Deserialize)] +pub struct Context { + #[serde(rename = "@context")] + pub ctx: ContextMap, +} + +#[derive(Default, Debug, Clone, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ContextMap { + #[serde(rename = "@vocab")] + pub vocab: String, + #[serde(rename = "xsd")] + pub xsd: String, + #[serde(rename = "as")] + pub as_field: String, + #[serde(rename = "ldp")] + pub ldp: String, + #[serde(rename = "vcard")] + pub vcard: String, + #[serde(rename = "id")] + pub id: String, + #[serde(rename = "type")] + pub type_field: String, + pub accept: String, + pub activity: String, + pub intransitive_activity: String, + pub add: String, + pub announce: String, + pub application: String, + pub arrive: String, + pub article: String, + pub audio: String, + pub block: String, + pub collection: String, + pub collection_page: String, + pub relationship: String, + pub create: String, + pub delete: String, + pub dislike: String, + pub document: String, + pub event: String, + pub follow: String, + pub flag: String, + pub group: String, + pub ignore: String, + pub image: String, + pub invite: String, + pub join: String, + pub leave: String, + pub like: String, + pub link: String, + pub mention: String, + pub note: String, + pub object: String, + pub offer: String, + pub ordered_collection: String, + pub ordered_collection_page: String, + pub organization: String, + pub page: String, + pub person: String, + pub place: String, + pub profile: String, + pub question: String, + pub reject: String, + pub remove: String, + pub service: String, + pub tentative_accept: String, + pub tentative_reject: String, + pub tombstone: String, + pub undo: String, + pub update: String, + pub video: String, + pub view: String, + pub listen: String, + pub read: String, + #[serde(rename = "Move")] + pub move_field: String, + pub travel: String, + pub is_following: String, + pub is_followed_by: String, + pub is_contact: String, + pub is_member: String, + #[serde(rename = "subject")] + pub subject: Option, + #[serde(rename = "relationship")] + pub relationship2: Option, + #[serde(rename = "actor")] + pub actor: Option, + #[serde(rename = "attributedTo")] + pub attributed_to: Option, + #[serde(rename = "attachment")] + pub attachment: Option, + #[serde(rename = "bcc")] + pub bcc: Option, + #[serde(rename = "bto")] + pub bto: Option, + #[serde(rename = "cc")] + pub cc: Option, + #[serde(rename = "context")] + pub context: Option, + #[serde(rename = "current")] + pub current: Option, + #[serde(rename = "first")] + pub first: Option, + #[serde(rename = "generator")] + pub generator: Option, + #[serde(rename = "icon")] + pub icon: Option, + #[serde(rename = "image")] + pub image2: Option, + #[serde(rename = "inReplyTo")] + pub in_reply_to: Option, + #[serde(rename = "items")] + pub items: Option, + #[serde(rename = "instrument")] + pub instrument: Option, + #[serde(rename = "orderedItems")] + pub ordered_items: Option, + #[serde(rename = "last")] + pub last: Option, + #[serde(rename = "location")] + pub location: Option, + #[serde(rename = "next")] + pub next: Option, + #[serde(rename = "object")] + pub object2: Option, + #[serde(rename = "oneOf")] + pub one_of: Option, + #[serde(rename = "anyOf")] + pub any_of: Option, + #[serde(rename = "closed")] + pub closed: Option, + #[serde(rename = "origin")] + pub origin: Option, + #[serde(rename = "accuracy")] + pub accuracy: Option, + #[serde(rename = "prev")] + pub prev: Option, + #[serde(rename = "preview")] + pub preview: Option, + #[serde(rename = "replies")] + pub replies: Option, + #[serde(rename = "result")] + pub result: Option, + #[serde(rename = "audience")] + pub audience: Option, + #[serde(rename = "partOf")] + pub part_of: Option, + #[serde(rename = "tag")] + pub tag: Option, + #[serde(rename = "target")] + pub target: Option, + #[serde(rename = "to")] + pub to: Option, + #[serde(rename = "url")] + pub url: Option, + #[serde(rename = "altitude")] + pub altitude: Option, + #[serde(rename = "content")] + pub content: String, + #[serde(rename = "contentMap")] + pub content_map: Option, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "nameMap")] + pub name_map: Option, + #[serde(rename = "duration")] + pub duration: Option, + #[serde(rename = "endTime")] + pub end_time: Option, + #[serde(rename = "height")] + pub height: Option, + #[serde(rename = "href")] + pub href: Option, + #[serde(rename = "hreflang")] + pub hreflang: String, + #[serde(rename = "latitude")] + pub latitude: Option, + #[serde(rename = "longitude")] + pub longitude: Option, + #[serde(rename = "mediaType")] + pub media_type: String, + #[serde(rename = "published")] + pub published: Option, + #[serde(rename = "radius")] + pub radius: Option, + #[serde(rename = "rel")] + pub rel: String, + #[serde(rename = "startIndex")] + pub start_index: Option, + #[serde(rename = "startTime")] + pub start_time: Option, + #[serde(rename = "summary")] + pub summary: String, + #[serde(rename = "summaryMap")] + pub summary_map: Option, + #[serde(rename = "totalItems")] + pub total_items: Option, + #[serde(rename = "units")] + pub units: String, + #[serde(rename = "updated")] + pub updated: Option, + #[serde(rename = "width")] + pub width: Option, + #[serde(rename = "describes")] + pub describes: Option, + #[serde(rename = "formerType")] + pub former_type: Option, + #[serde(rename = "deleted")] + pub deleted: Option, + #[serde(rename = "inbox")] + pub inbox: Option, + #[serde(rename = "outbox")] + pub outbox: Option, + #[serde(rename = "following")] + pub following: Option, + #[serde(rename = "followers")] + pub followers: Option, + #[serde(rename = "streams")] + pub streams: Option, + #[serde(rename = "preferredUsername")] + pub preferred_username: String, + #[serde(rename = "endpoints")] + pub endpoints: Option, + #[serde(rename = "uploadMedia")] + pub upload_media: Option, + #[serde(rename = "proxyUrl")] + pub proxy_url: Option, + #[serde(rename = "liked")] + pub liked: Option, + #[serde(rename = "oauthAuthorizationEndpoint")] + pub oauth_authorization_endpoint: Option, + #[serde(rename = "oauthTokenEndpoint")] + pub oauth_token_endpoint: Option, + #[serde(rename = "provideClientKey")] + pub provide_client_key: Option, + #[serde(rename = "signClientKey")] + pub sign_client_key: Option, + #[serde(rename = "sharedInbox")] + pub shared_inbox: Option, + pub public: Option, + #[serde(rename = "source")] + pub source: String, + #[serde(rename = "likes")] + pub likes: Option, + #[serde(rename = "shares")] + pub shares: Option, + #[serde(rename = "alsoKnownAs")] + pub also_known_as: Option, +} + +#[derive(Default, Debug, Clone, Deserialize)] +pub struct TypedField { + #[serde(rename = "@id")] + pub id: String, + #[serde(rename = "@type")] + pub kind: Option, + #[serde(rename = "@container")] + pub container: Option, +} diff --git a/src/astreams/mod.rs b/src/astreams/mod.rs new file mode 100644 index 0000000..3e2ef10 --- /dev/null +++ b/src/astreams/mod.rs @@ -0,0 +1,95 @@ +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +use self::{context::Context, note::Note, resolve::ResolveError, serde_ext::pull_single_into_vec}; + +pub mod context; +pub mod note; +pub mod resolve; +mod serde_ext; + +#[derive(Clone, Copy, Serialize, Deserialize, Debug)] +pub enum ActivityKind { + Create, + Like, + Note, +} + +#[derive(Debug, Clone)] +pub enum ExpandError { + InvalidKind(Option), + NoAttribution, + ResolveIRI(String), + Other(String), +} + +impl From for ExpandError { + fn from(err: ResolveError) -> Self { + Self::ResolveIRI(err.0) + } +} + +#[async_trait] +pub trait ExpandLD { + async fn expand(obj: &ObjectLD) -> Result; +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct ActivityLD { + #[serde(rename = "@context")] + pub context_uri: String, + pub kind: ActivityKind, + #[serde(rename = "actor")] + pub actor_uri: String, + #[serde(rename = "to")] + pub to_uris: Vec, + pub object: Option, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct ObjectLD { + #[serde(rename = "@context")] + pub context_uri: Option, + pub id: String, + #[serde(rename = "type")] + pub kind: Option, + #[serde(rename = "attributedTo")] + pub attributed_to: Option, + pub published: Option, + pub content: Option, + pub context: Option, + pub conversation: Option, + pub url: Option, + #[serde(rename = "to")] + #[serde(deserialize_with = "pull_single_into_vec")] + pub to_uris: Vec, +} + +pub enum ActorType {} + +#[derive(Debug, Clone)] +pub struct Actor { + id: String, +} + +pub async fn test() { + let obj = r#"{ + "@context": "https://www.w3.org/ns/activitystreams", + "attributedTo": "https://localhost/u/test", + "content": "honk donk", + "context": "data:,electrichonkytonk-2jqQ42HyJXctnBKTy1", + "conversation": "data:,electrichonkytonk-2jqQ42HyJXctnBKTy1", + "id": "https://localhost/u/test/h/6Q8BFF8W6PZT2ddngZ", + "published": "2022-09-30T19:04:45Z", + "summary": "", + "to": "https://www.w3.org/ns/activitystreams#Public", + "type": "Note", + "url": "https://localhost/u/test/h/6Q8BFF8W6PZT2ddngZ" + }"#; + + let obj = serde_json::from_str::(obj).unwrap(); + let unwrapped = Note::expand(&obj).await.unwrap(); + println!("{:#?}", &unwrapped); + println!("to: {:#?}", obj.to_uris); + println!("{}", serde_json::to_string_pretty(&obj).unwrap()); +} diff --git a/src/astreams/note.rs b/src/astreams/note.rs new file mode 100644 index 0000000..e502919 --- /dev/null +++ b/src/astreams/note.rs @@ -0,0 +1,64 @@ +use async_trait::async_trait; + +use super::{ + context::{self, Context}, + resolve, ActivityKind, Actor, ExpandError, ExpandLD, ObjectLD, +}; + +#[derive(Debug, Clone)] +pub struct Note { + pub id: String, + pub content: Option, + pub context: Option, + pub published_at: Option, + pub author: Actor, + pub to: Vec, + pub url: Option, + pub conversation: Option, +} + +#[async_trait] +impl ExpandLD for Note { + async fn expand(obj: &ObjectLD) -> Result { + let obj = obj.clone(); + let resolver = resolve::Resolver::new(); + if let Some(kind) = obj.kind { + if let ActivityKind::Note = kind { + Ok(Note { + id: obj.id, + content: obj.content, + published_at: obj.published, + author: obj + .attributed_to + .map(|id| Actor { id }) + .ok_or(ExpandError::NoAttribution)?, + to: obj.to_uris.into_iter().map(|id| Actor { id }).collect(), + url: obj.url, + context: obj.context, + conversation: obj.conversation, + }) + } else { + Err(ExpandError::InvalidKind(Some(kind))) + } + } else { + Err(ExpandError::InvalidKind(None)) + } + } +} + +impl Into for Note { + fn into(self) -> ObjectLD { + ObjectLD { + context_uri: Some(context::CONTEXT_ID.to_string()), + id: self.id.clone(), + kind: Some(ActivityKind::Note), + attributed_to: Some(self.author.id), + published: self.published_at, + content: self.content, + context: self.context, + conversation: self.conversation, + url: Some(self.id), + to_uris: self.to.into_iter().map(|a| a.id).collect(), + } + } +} diff --git a/src/astreams/resolve.rs b/src/astreams/resolve.rs new file mode 100644 index 0000000..78d835f --- /dev/null +++ b/src/astreams/resolve.rs @@ -0,0 +1,66 @@ +use std::{future::Future, string::FromUtf8Error}; + +use reqwest::{Method, StatusCode}; + +const LD_CONTENT_TYPE: &str = "application/ld+json"; + +pub(crate) struct ResolveError(pub String); + +impl From for ResolveError { + fn from(err: reqwest::Error) -> Self { + Self(err.to_string()) + } +} +impl From for ResolveError { + fn from(e: FromUtf8Error) -> Self { + Self(format!( + "invalid schema format (tried utf8): {}", + e.to_string() + )) + } +} + +pub(super) struct Resolver { + client: reqwest::Client, +} + +impl Resolver { + pub fn new() -> Self { + Self { + client: reqwest::ClientBuilder::new().build().unwrap(), + } + } + + async fn get(&self, iri: String, ok: F) -> Result + where + F: FnOnce(reqwest::Response) -> Fut, + Fut: Future>, + { + let resp = self + .client + .request(Method::GET, iri) + .header("Accept", LD_CONTENT_TYPE) + .send() + .await?; + + match resp.status() { + StatusCode::OK => Ok(ok(resp).await?), + status => Err(ResolveError(format!("non-ok status: {}", status))), + } + } + + pub async fn resolve_into<'a, T: for<'de> serde::Deserialize<'de>>( + &self, + iri: String, + ) -> Result { + Ok(self + .get(iri, |resp| async { Ok(resp.json().await?) }) + .await?) + } + + pub async fn resolve(&self, iri: String) -> Result, ResolveError> { + Ok(self + .get(iri, |resp| async { Ok(resp.bytes().await?.to_vec()) }) + .await?) + } +} diff --git a/src/astreams/serde_ext.rs b/src/astreams/serde_ext.rs new file mode 100644 index 0000000..9d49ea0 --- /dev/null +++ b/src/astreams/serde_ext.rs @@ -0,0 +1,41 @@ +use std::{fmt, marker::PhantomData}; + +use serde::{de, Deserialize, Deserializer}; + +// Allows deserialization of a single item into a vector of that item +// As long as they implement the From trait +pub(super) fn pull_single_into_vec<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: From + serde::Deserialize<'de>, +{ + struct VecComposer(PhantomData>); + + impl<'de, T> de::Visitor<'de> for VecComposer + where + T: From + serde::Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let name = std::any::type_name::(); + formatter.write_str(format!("expected {} or Vec<{}>", name, name).as_str()) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![value.to_string().into()]) + } + + fn visit_seq(self, visitor: S) -> Result + where + S: de::SeqAccess<'de>, + { + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(visitor)) + } + } + + deserializer.deserialize_any(VecComposer(PhantomData)) +} diff --git a/src/main.rs b/src/main.rs index dc5f79c..0cd4a7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,17 @@ +#![feature(let_chains)] +mod astreams; mod database; mod sec; mod servek; mod svc; use database::db::DB; -use serde::{Deserialize, Serialize}; use servek::servek::Server; use svc::{auth::Auth, profiles::Profiler}; -#[derive(Clone, Copy, Serialize, Deserialize)] -pub enum ActivityKind { - Create, - Like, - Note, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct ActivityLD { - pub context_uri: String, - pub kind: ActivityKind, - pub actor_uri: String, - pub to_uris: Vec, - pub object: Option, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct ObjectLD { - pub context_uri: String, - pub id: Option, - pub kind: Option, - pub attributed_to: Option, - pub published: Option, - pub content: Option, -} - #[tokio::main] async fn main() -> Result<(), anyhow::Error> { + astreams::test().await; let db = DB::new( "localhost".to_owned(), "flabk".to_owned(),