diff --git a/Cargo.lock b/Cargo.lock index 23b86fab7..65c9a28ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,6 +1069,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dissimilar" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" + [[package]] name = "either" version = "1.6.1" @@ -1371,6 +1377,12 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "h2" version = "0.2.7" @@ -1887,12 +1899,23 @@ dependencies = [ "activitystreams", "activitystreams-ext", "async-trait", + "lemmy_apub_lib_derive", "lemmy_utils", "lemmy_websocket", "serde", "url", ] +[[package]] +name = "lemmy_apub_lib_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.60", + "trybuild", +] + [[package]] name = "lemmy_apub_receive" version = "0.1.0" @@ -3652,6 +3675,15 @@ dependencies = [ "tokio 0.2.25", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.1" @@ -3735,6 +3767,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "trybuild" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1768998d9a3b179411618e377dbb134c58a88cda284b0aa71c42c40660127d46" +dependencies = [ + "dissimilar", + "glob", + "lazy_static", + "serde", + "serde_json", + "termcolor", + "toml", +] + [[package]] name = "twoway" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index eec65009e..a2f5af2ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "crates/api_crud", "crates/api_common", "crates/apub_lib", + "crates/apub_lib_derive", "crates/apub", "crates/apub_receive", "crates/utils", diff --git a/crates/apub_lib/Cargo.toml b/crates/apub_lib/Cargo.toml index 4d85c73fa..327670b50 100644 --- a/crates/apub_lib/Cargo.toml +++ b/crates/apub_lib/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] lemmy_utils = { path = "../utils" } lemmy_websocket = { path = "../websocket" } +lemmy_apub_lib_derive = { path = "../apub_lib_derive" } activitystreams = "0.7.0-alpha.11" activitystreams-ext = "0.1.0-alpha.2" serde = { version = "1.0.123", features = ["derive"] } diff --git a/crates/apub_lib/src/lib.rs b/crates/apub_lib/src/lib.rs index 00cd5dc8b..9b45add11 100644 --- a/crates/apub_lib/src/lib.rs +++ b/crates/apub_lib/src/lib.rs @@ -4,6 +4,7 @@ use activitystreams::{ primitives::OneOrMany, unparsed::Unparsed, }; +pub use lemmy_apub_lib_derive::*; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use std::marker::PhantomData; diff --git a/crates/apub_lib_derive/Cargo.toml b/crates/apub_lib_derive/Cargo.toml new file mode 100644 index 000000000..293a15b8c --- /dev/null +++ b/crates/apub_lib_derive/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "lemmy_apub_lib_derive" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dev-dependencies] +trybuild = { version = "1.0", features = ["diff"] } + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +quote = "1.0" \ No newline at end of file diff --git a/crates/apub_lib_derive/src/lib.rs b/crates/apub_lib_derive/src/lib.rs new file mode 100644 index 000000000..6e809813d --- /dev/null +++ b/crates/apub_lib_derive/src/lib.rs @@ -0,0 +1,102 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput}; + +#[proc_macro_derive(ActivityHandlerNew)] +pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Parse the input tokens into a syntax tree. + let input = parse_macro_input!(input as DeriveInput); + + // Used in the quasi-quotation below as `#name`. + let name = input.ident; + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let input_enum = if let Data::Enum(d) = input.data { + d + } else { + unimplemented!() + }; + + let impl_verify = input_enum + .variants + .iter() + .map(|variant| variant_impl_verify(&name, variant)); + let impl_receive = input_enum + .variants + .iter() + .map(|variant| variant_impl_receive(&name, variant)); + let impl_common = input_enum + .variants + .iter() + .map(|variant| variant_impl_common(&name, variant)); + + // The generated impl. + let expanded = quote! { + #[async_trait::async_trait(?Send)] + impl #impl_generics lemmy_apub_lib::ActivityHandlerNew for #name #ty_generics #where_clause { + async fn verify( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + match self { + #(#impl_verify)* + } + } + async fn receive( + &self, + context: &LemmyContext, + request_counter: &mut i32, + ) -> Result<(), LemmyError> { + match self { + #(#impl_receive)* + } + } + fn common(&self) -> &ActivityCommonFields { + match self { + #(#impl_common)* + } + } + } + }; + + // Hand the output tokens back to the compiler. + proc_macro::TokenStream::from(expanded) +} + +fn variant_impl_common(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { + let id = &variant.ident; + match &variant.fields { + syn::Fields::Unnamed(_) => { + quote! { + #name::#id(a) => a.common(), + } + } + _ => unimplemented!(), + } +} + +fn variant_impl_verify(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { + let id = &variant.ident; + match &variant.fields { + syn::Fields::Unnamed(_) => { + quote! { + #name::#id(a) => a.verify(context, request_counter).await, + } + } + _ => unimplemented!(), + } +} + +fn variant_impl_receive(name: &syn::Ident, variant: &syn::Variant) -> TokenStream { + let id = &variant.ident; + match &variant.fields { + syn::Fields::Unnamed(_) => { + quote! { + #name::#id(a) => a.receive(context, request_counter).await, + } + } + _ => unimplemented!(), + } +} diff --git a/crates/apub_receive/src/http/mod.rs b/crates/apub_receive/src/http/mod.rs index fe79992da..edfc03cb0 100644 --- a/crates/apub_receive/src/http/mod.rs +++ b/crates/apub_receive/src/http/mod.rs @@ -30,7 +30,7 @@ pub mod inbox_enums; pub mod person; pub mod post; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ActivityHandlerNew)] #[serde(untagged)] enum Ac { CreatePost(CreatePost), @@ -38,42 +38,6 @@ enum Ac { AcceptFollowCommunity(AcceptFollowCommunity), } -// TODO: write a derive trait which creates this -#[async_trait::async_trait(?Send)] -impl ActivityHandlerNew for Ac { - async fn verify( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - match self { - Ac::CreatePost(a) => a.verify(context, request_counter).await, - Ac::LikePost(a) => a.verify(context, request_counter).await, - Ac::AcceptFollowCommunity(a) => a.verify(context, request_counter).await, - } - } - - async fn receive( - &self, - context: &LemmyContext, - request_counter: &mut i32, - ) -> Result<(), LemmyError> { - match self { - Ac::CreatePost(a) => a.receive(context, request_counter).await, - Ac::LikePost(a) => a.receive(context, request_counter).await, - Ac::AcceptFollowCommunity(a) => a.receive(context, request_counter).await, - } - } - - fn common(&self) -> &ActivityCommonFields { - match self { - Ac::CreatePost(a) => a.common(), - Ac::LikePost(a) => a.common(), - Ac::AcceptFollowCommunity(a) => a.common(), - } - } -} - pub async fn shared_inbox( request: HttpRequest, mut body: web::Payload,