use proc_macro2::TokenStream; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput, Fields::Unnamed, Ident, Variant}; /// Generates implementation ActivityHandler for an enum, which looks like the following (handling /// all enum variants). /// /// Based on this code: /// ```ignore /// #[derive(serde::Deserialize, serde::Serialize)] /// #[serde(untagged)] /// #[activity_handler(LemmyContext, LemmyError)] /// pub enum PersonInboxActivities { /// CreateNote(CreateNote), /// UpdateNote(UpdateNote), /// } /// ``` /// It will generate this: /// ```ignore /// impl ActivityHandler for PersonInboxActivities { /// type DataType = LemmyContext; /// type Error = LemmyError; /// /// async fn verify( /// &self, /// data: &Self::DataType, /// request_counter: &mut i32, /// ) -> Result<(), Self::Error> { /// match self { /// PersonInboxActivities::CreateNote(a) => a.verify(data, request_counter).await, /// PersonInboxActivities::UpdateNote(a) => a.verify(context, request_counter).await, /// } /// } /// /// async fn receive( /// &self, /// data: &Self::DataType, /// request_counter: &mut i32, /// ) -> Result<(), Self::Error> { /// match self { /// PersonInboxActivities::CreateNote(a) => a.receive(data, request_counter).await, /// PersonInboxActivities::UpdateNote(a) => a.receive(data, request_counter).await, /// } /// } /// ``` #[proc_macro_attribute] pub fn activity_handler( attr: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let derive_input = parse_macro_input!(input as DeriveInput); let derive_input2 = derive_input.clone(); let attr = proc_macro2::TokenStream::from(attr); let mut attr = attr.into_iter(); let data_type = attr.next().expect("data type input"); let _delimiter = attr.next(); let error = attr.next().expect("error type input"); let enum_name = derive_input2.ident; let (impl_generics, ty_generics, where_clause) = derive_input2.generics.split_for_impl(); let enum_variants = if let Data::Enum(d) = derive_input2.data { d.variants } else { unimplemented!() }; let impl_id = enum_variants .iter() .map(|v| generate_match_arm(&enum_name, v, "e! {a.id()})); let impl_actor = enum_variants .iter() .map(|v| generate_match_arm(&enum_name, v, "e! {a.actor()})); let body_verify = quote! {a.verify(context, request_counter).await}; let impl_verify = enum_variants .iter() .map(|v| generate_match_arm(&enum_name, v, &body_verify)); let body_receive = quote! {a.receive(context, request_counter).await}; let impl_receive = enum_variants .iter() .map(|v| generate_match_arm(&enum_name, v, &body_receive)); let expanded = quote! { #derive_input #[async_trait::async_trait(?Send)] impl #impl_generics activitypub_federation::traits::ActivityHandler for #enum_name #ty_generics #where_clause { type DataType = #data_type; type Error = #error; fn id( &self, ) -> &Url { match self { #(#impl_id)* } } fn actor( &self, ) -> &Url { match self { #(#impl_actor)* } } async fn verify( &self, context: &activitypub_federation::data::Data, request_counter: &mut i32, ) -> Result<(), Self::Error> { match self { #(#impl_verify)* } } async fn receive( self, context: &activitypub_federation::data::Data, request_counter: &mut i32, ) -> Result<(), Self::Error> { match self { #(#impl_receive)* } } } }; expanded.into() } fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream) -> TokenStream { let id = &variant.ident; match &variant.fields { Unnamed(_) => { quote! { #enum_name::#id(a) => #body, } } _ => unimplemented!(), } }