]> Untitled Git - lemmy.git/blob - crates/apub_lib_derive/src/lib.rs
Rewrite remaining activities (#1712)
[lemmy.git] / crates / apub_lib_derive / src / lib.rs
1 use proc_macro2::TokenStream;
2 use quote::quote;
3 use syn::{parse_macro_input, Data, DeriveInput, Fields::Unnamed, Ident, Variant};
4
5 /// Generates implementation ActivityHandler for an enum, which looks like the following (handling
6 /// all enum variants).
7 ///
8 /// Based on this code:
9 /// ```ignore
10 /// #[derive(serde::Deserialize, serde::Serialize, ActivityHandler)]
11 /// #[serde(untagged)]
12 /// pub enum PersonInboxActivities {
13 ///  CreateNote(CreateNote),
14 ///  UpdateNote(UpdateNote),
15 /// ```
16 /// It will generate this:
17 /// ```ignore
18 /// impl ActivityHandler for PersonInboxActivities {
19 ///
20 ///     async fn verify(
21 ///     &self,
22 ///     context: &LemmyContext,
23 ///     request_counter: &mut i32,
24 ///   ) -> Result<(), LemmyError> {
25 ///     match self {
26 ///       PersonInboxActivities::CreateNote(a) => a.verify(context, request_counter).await,
27 ///       PersonInboxActivities::UpdateNote(a) => a.verify(context, request_counter).await,
28 ///     }
29 ///   }
30 ///
31 ///   async fn receive(
32 ///   &self,
33 ///   context: &LemmyContext,
34 ///   request_counter: &mut i32,
35 /// ) -> Result<(), LemmyError> {
36 ///     match self {
37 ///       PersonInboxActivities::CreateNote(a) => a.receive(context, request_counter).await,
38 ///       PersonInboxActivities::UpdateNote(a) => a.receive(context, request_counter).await,
39 ///     }
40 ///   }
41 /// fn common(&self) -> &ActivityCommonFields  {
42 ///     match self {
43 ///       PersonInboxActivities::CreateNote(a) => a.common(),
44 ///       PersonInboxActivities::UpdateNote(a) => a.common(),
45 ///     }
46 ///   }
47 ///
48 /// ```
49 #[proc_macro_derive(ActivityHandler)]
50 pub fn derive_activity_handler(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
51   let input = parse_macro_input!(input as DeriveInput);
52
53   let enum_name = input.ident;
54
55   let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
56
57   let enum_variants = if let Data::Enum(d) = input.data {
58     d.variants
59   } else {
60     unimplemented!()
61   };
62
63   let body_verify = quote! {a.verify(context, request_counter).await};
64   let impl_verify = enum_variants
65     .iter()
66     .map(|v| generate_match_arm(&enum_name, v, &body_verify));
67   let body_receive = quote! {a.receive(context, request_counter).await};
68   let impl_receive = enum_variants
69     .iter()
70     .map(|v| generate_match_arm(&enum_name, v, &body_receive));
71
72   let expanded = quote! {
73       #[async_trait::async_trait(?Send)]
74       impl #impl_generics lemmy_apub_lib::ActivityHandler for #enum_name #ty_generics #where_clause {
75           async fn verify(
76               &self,
77               context: &lemmy_websocket::LemmyContext,
78               request_counter: &mut i32,
79             ) -> Result<(), lemmy_utils::LemmyError> {
80             match self {
81               #(#impl_verify)*
82             }
83           }
84           async fn receive(
85             self,
86             context: &lemmy_websocket::LemmyContext,
87             request_counter: &mut i32,
88           ) -> Result<(), lemmy_utils::LemmyError> {
89             match self {
90               #(#impl_receive)*
91             }
92           }
93       }
94   };
95   expanded.into()
96 }
97
98 fn generate_match_arm(enum_name: &Ident, variant: &Variant, body: &TokenStream) -> TokenStream {
99   let id = &variant.ident;
100   match &variant.fields {
101     Unnamed(_) => {
102       quote! {
103         #enum_name::#id(a) => #body,
104       }
105     }
106     _ => unimplemented!(),
107   }
108 }
109
110 #[proc_macro_derive(ActivityFields)]
111 pub fn derive_activity_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
112   let input = parse_macro_input!(input as DeriveInput);
113
114   let name = input.ident;
115
116   let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
117
118   let expanded = match input.data {
119     Data::Enum(e) => {
120       let variants = e.variants;
121       let impl_id = variants
122         .iter()
123         .map(|v| generate_match_arm(&name, v, &quote! {a.id_unchecked()}));
124       let impl_actor = variants
125         .iter()
126         .map(|v| generate_match_arm(&name, v, &quote! {a.actor()}));
127       let impl_cc = variants
128         .iter()
129         .map(|v| generate_match_arm(&name, v, &quote! {a.cc()}));
130       quote! {
131           impl #impl_generics lemmy_apub_lib::ActivityFields for #name #ty_generics #where_clause {
132               fn id_unchecked(&self) -> &url::Url { match self { #(#impl_id)* } }
133               fn actor(&self) -> &url::Url { match self { #(#impl_actor)* } }
134               fn cc(&self) -> Vec<url::Url> { match self { #(#impl_cc)* } }
135           }
136       }
137     }
138     Data::Struct(s) => {
139       // check if the struct has a field "cc", and generate impl for cc() function depending on that
140       let has_cc = if let syn::Fields::Named(n) = s.fields {
141         n.named
142           .iter()
143           .any(|i| format!("{}", i.ident.as_ref().unwrap()) == "cc")
144       } else {
145         unimplemented!()
146       };
147       let cc_impl = if has_cc {
148         quote! {self.cc.clone().into()}
149       } else {
150         quote! {vec![]}
151       };
152       quote! {
153           impl #impl_generics lemmy_apub_lib::ActivityFields for #name #ty_generics #where_clause {
154               fn id_unchecked(&self) -> &url::Url { &self.id }
155               fn actor(&self) -> &url::Url { &self.actor }
156               fn cc(&self) -> Vec<url::Url> { #cc_impl }
157           }
158       }
159     }
160     _ => unimplemented!(),
161   };
162   expanded.into()
163 }