diff --git a/flabk-derive/src/lib.rs b/flabk-derive/src/lib.rs index 3b71849..a8d8e98 100644 --- a/flabk-derive/src/lib.rs +++ b/flabk-derive/src/lib.rs @@ -3,16 +3,18 @@ use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(IRI)] -pub fn derive_iri(input: TokenStream) -> TokenStream { +#[proc_macro_derive(LD)] +pub fn derive_ld(input: TokenStream) -> TokenStream { let DeriveInput { ident, .. } = parse_macro_input!(input); quote! { - impl std::str::FromStr for #ident { - type Err = (); - fn from_str(s: &str) -> Result { + impl crate::astreams::serde_ext::LDObject for #ident { + fn from_iri(s: &str) -> Self { let mut ident = #ident::default(); ident.id = s.into(); - Ok(ident) + ident + } + fn get_iri(&self) -> String { + self.id.clone() } } } diff --git a/flabk/src/astreams/context.rs b/flabk/src/astreams/context.rs index b820123..530824a 100644 --- a/flabk/src/astreams/context.rs +++ b/flabk/src/astreams/context.rs @@ -2,22 +2,26 @@ use std::str::FromStr; use serde::Deserialize; +use super::serde_ext::LDObject; + pub const CONTEXT_ID: &str = "https://www.w3.org/ns/activitystreams"; #[derive(Default, Debug, Clone, Deserialize)] -pub struct Context { +pub struct APContext { pub id: Option, #[serde(rename = "@context")] pub ctx: ContextMap, } -impl FromStr for Context { - type Err = (); - - fn from_str(s: &str) -> Result { +impl LDObject for APContext { + fn from_iri(iri: &str) -> Self { let mut ctx = Self::default(); - ctx.id = Some(s.into()); - Ok(ctx) + ctx.id = Some(iri.into()); + ctx + } + + fn get_iri(&self) -> String { + self.id.clone().unwrap_or_default() } } diff --git a/flabk/src/astreams/mod.rs b/flabk/src/astreams/mod.rs index 4177c9f..ec9d8fa 100644 --- a/flabk/src/astreams/mod.rs +++ b/flabk/src/astreams/mod.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; -use flabk_derive::{Resolvable, IRI}; +use flabk_derive::LD; use serde::{Deserialize, Serialize}; +use crate::astreams::resolve::Resolvable; + use self::{ - context::APContext, resolve::ResolveError, - serde_ext::{expand_partial, into_vec}, + serde_ext::{expand_partial, expand_partial_into_vec, into_vec}, }; pub mod context; @@ -155,14 +156,14 @@ pub struct APResult { #[derive(Debug, Clone, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct ObjectBase { - #[serde(rename = "@context")] - #[serde(deserialize_with = "expand_partial")] - pub ap_context: APContext, + // #[serde(rename = "@context")] + // #[serde(deserialize_with = "expand_partial")] + // pub ap_context: APContext, pub name: Option, pub name_map: Option>, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub attachment: Vec, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "expand_partial_into_vec")] pub attributed_to: Vec, pub audience: Option, pub media_type: Option, @@ -172,7 +173,7 @@ pub struct ObjectBase { pub start_time: Option, pub end_time: Option, pub generator: Option, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub icon: Vec, // omit in_reply_to pub location: Option, @@ -182,21 +183,21 @@ pub struct ObjectBase { pub replies: Option>, // TODO: type pub summary: Option, pub summary_map: Option>, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "expand_partial_into_vec", default = "Vec::new")] pub tag: Vec, // omit url for now: need to merge into_vec & expand_partial - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub to: Vec, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub bto: Vec, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub cc: Vec, - #[serde(deserialize_with = "into_vec")] + #[serde(deserialize_with = "into_vec", default = "Vec::new")] pub bcc: Vec, pub duration: Option, } -#[derive(Debug, Clone, Deserialize, Resolvable, IRI, Default)] +#[derive(Debug, Clone, Deserialize, LD, Default)] pub struct Object { #[serde(flatten)] pub base: ObjectBase, @@ -227,7 +228,7 @@ pub struct Link { /// for all types of activities. /// It is important to note that the Activity type itself /// does not carry any specific semantics about the kind of action being taken. -#[derive(Debug, Clone, Deserialize, Resolvable)] +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Activity { #[serde(flatten)] @@ -247,7 +248,7 @@ pub struct Activity { /// Instances of IntransitiveActivity are a subtype of Activity /// representing intransitive actions. -#[derive(Debug, Clone, Deserialize, Resolvable)] +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct IntransitiveActivity { #[serde(flatten)] @@ -277,7 +278,7 @@ pub struct Basic { pub name: Option, } -#[derive(Debug, Clone, Deserialize, IRI, Default, Resolvable)] +#[derive(Debug, Clone, Deserialize, LD, Default)] pub struct Actor { pub id: String, #[serde(rename = "type")] @@ -290,23 +291,10 @@ pub struct Actor { pub conversation: Option, pub url: Option, #[serde(rename = "to")] - #[serde(deserialize_with = "pull_single_into_vec")] + #[serde(deserialize_with = "into_vec")] pub to_uris: Vec, } -pub enum ActorType { - Application, - Group, - Organization, - Person, - Service, -} - -#[derive(Debug, Clone, Deserialize, IRI, Default)] -pub struct Actor { - id: String, -} - pub async fn test() { let obj = r#"{ "@context": "https://www.w3.org/ns/activitystreams", diff --git a/flabk/src/astreams/resolve.rs b/flabk/src/astreams/resolve.rs index 4b1c086..91ff0ca 100644 --- a/flabk/src/astreams/resolve.rs +++ b/flabk/src/astreams/resolve.rs @@ -1,9 +1,28 @@ use std::{future::Future, string::FromUtf8Error}; +use async_trait::async_trait; use reqwest::{Method, StatusCode}; +use serde::Deserialize; + +use super::serde_ext::LDObject; const LD_CONTENT_TYPE: &str = "application/ld+json"; +#[async_trait] +pub(super) trait Resolvable: Sized { + async fn resolve(&self, resolver: &Resolver) -> Result; +} + +#[async_trait] +impl Resolvable for T +where + T: LDObject + for<'de> Deserialize<'de> + Sync, +{ + async fn resolve(&self, resolver: &Resolver) -> Result { + resolver.resolve_into(self.get_iri()).await + } +} + #[derive(Debug)] pub(crate) struct ResolveError(pub String); diff --git a/flabk/src/astreams/serde_ext.rs b/flabk/src/astreams/serde_ext.rs index 39e6542..9e68392 100644 --- a/flabk/src/astreams/serde_ext.rs +++ b/flabk/src/astreams/serde_ext.rs @@ -1,22 +1,40 @@ -use std::{fmt, marker::PhantomData, str::FromStr}; +use std::{fmt, marker::PhantomData}; use serde::{ de::{self, MapAccess, Visitor}, Deserialize, Deserializer, }; +pub trait LDObject { + fn from_iri(iri: &str) -> Self; + fn get_iri(&self) -> String; +} + +impl LDObject for Option +where + T: LDObject, +{ + fn from_iri(iri: &str) -> Self { + todo!() + } + + fn get_iri(&self) -> String { + todo!() + } +} + // Allows a value that's a string to be expanded into an object (move string into id property via From) // AND the serialization of that object itself pub fn expand_partial<'de, T, D>(deserializer: D) -> Result where - T: Deserialize<'de> + FromStr, + T: Deserialize<'de> + LDObject, D: Deserializer<'de>, { struct BaseExpander(PhantomData T>); impl<'de, T> Visitor<'de> for BaseExpander where - T: Deserialize<'de> + FromStr, + T: Deserialize<'de> + LDObject, { type Value = T; @@ -28,7 +46,7 @@ where where E: de::Error, { - Ok(FromStr::from_str(value).unwrap()) + Ok(LDObject::from_iri(value)) } fn visit_map(self, map: M) -> Result @@ -42,18 +60,59 @@ where deserializer.deserialize_any(BaseExpander(PhantomData)) } +pub(super) fn expand_partial_into_vec<'de, T, D>(deserializer: D) -> Result, D::Error> +where + T: Deserialize<'de> + LDObject, + D: Deserializer<'de>, +{ + struct BaseExpander(PhantomData T>); + + impl<'de, T> Visitor<'de> for BaseExpander + where + T: Deserialize<'de> + LDObject, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or map") + } + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![LDObject::from_iri(value)]) + } + + fn visit_map(self, map: M) -> Result + where + M: MapAccess<'de>, + { + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)).map(|v| vec![v]) + } + + fn visit_seq(self, visitor: S) -> Result + where + S: serde::de::SeqAccess<'de>, + { + Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(visitor)) + } + } + + deserializer.deserialize_any(BaseExpander(PhantomData)) +} + // 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, Out>(deserializer: D) -> Result, D::Error> +pub(super) fn into_vec<'de, D, Out>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, - Out: From + serde::Deserialize<'de>, + Out: serde::Deserialize<'de>, { struct VecVisitor(PhantomData>); impl<'de, Out> Visitor<'de> for VecVisitor where - Out: From + serde::Deserialize<'de>, + Out: serde::Deserialize<'de>, { type Value = Vec; @@ -66,7 +125,14 @@ where where E: serde::de::Error, { - Ok(vec![value.to_string().into()]) + Deserialize::deserialize(de::value::StrDeserializer::new(value)).map(|v| vec![v]) + } + + fn visit_map(self, map: A) -> Result + where + A: MapAccess<'de>, + { + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)).map(|v| vec![v]) } fn visit_seq(self, visitor: S) -> Result @@ -82,14 +148,14 @@ where #[cfg(test)] mod tests { - use flabk_derive::IRI; + use flabk_derive::LD; use serde::Deserialize; use super::expand_partial; #[test] fn expand_partial_populates_iri_from_string() { - #[derive(Deserialize, IRI, Default)] + #[derive(Deserialize, LD, Default)] struct Context { pub id: String, pub context: bool, @@ -111,7 +177,7 @@ mod tests { #[test] fn expand_partial_expands_into_object_fully() { - #[derive(Deserialize, IRI, Default)] + #[derive(Deserialize, LD, Default)] struct Expanded { pub id: String, pub truth: bool,