seraphic_derive/
lib.rs

1use core::panic;
2use darling::FromDeriveInput;
3use proc_macro::{self, TokenStream};
4use quote::{format_ident, quote};
5use std::hash::Hash;
6use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, TypePath};
7
8// https://github.com/imbolc/rust-derive-macro-guide
9#[derive(FromDeriveInput, Default)]
10#[darling(default, attributes(rpc_request))]
11struct Opts {
12    // formatted "type:variant"
13    namespace: String,
14    response: Option<String>,
15}
16
17#[proc_macro_derive(RpcRequest, attributes(rpc_request))]
18pub fn derive_rpc_req(input: TokenStream) -> TokenStream {
19    let input = parse_macro_input!(input);
20    let opts = Opts::from_derive_input(&input).expect("Wrong options");
21    let DeriveInput { ident, data, .. } = input;
22    match data {
23        syn::Data::Struct(DataStruct { fields, .. }) => {
24            let name = format!("{ident}");
25            let name_no_suffix = name
26                .strip_suffix("Request")
27                .expect("make sure to put 'Request' at the end of your struct name");
28            // let struct_name = format_ident!("{}", name_no_suffix);
29            let first_char = name_no_suffix
30                .chars()
31                .next()
32                .unwrap()
33                .to_owned()
34                .to_lowercase();
35            let method = format!("{first_char}{}", &name_no_suffix[1..]);
36
37            let mut from_json_body = quote! {};
38            let mut create_self_body = quote! {};
39
40            for f in fields {
41                let id = f.ident.unwrap();
42                let json_name = format_ident!("{}_json", id);
43                let id_string = format!("{id}");
44                let not_exist = format!("field '{id_string}' does not exist");
45                let not_deserialize = format!("field '{id_string}' does not implement deserialize");
46                from_json_body = quote! {
47                    #from_json_body
48                    let #json_name = json.get(#id_string).ok_or(#not_exist)?.to_owned();
49                    let #id = serde_json::from_value(#json_name).map_err(|_|#not_deserialize)?;
50                };
51
52                create_self_body = quote! {
53                    #create_self_body
54                    #id,
55                }
56            }
57
58            let create_self = quote! {
59                Ok(Self {
60                    #create_self_body
61                })
62            };
63
64            let from_json = quote! {
65              fn try_from_json(json: &serde_json::Value) -> std::result::Result<Self,Box<dyn std::error::Error + Send + Sync + 'static>> {
66                    #from_json_body
67                    #create_self
68              }
69            };
70
71            let method_name = quote! {
72                fn method()-> &'static str {
73                    #method
74                }
75            };
76
77            let ns = opts.namespace;
78            let (ns_type, ns_var) = ns
79                .split_once(':')
80                .expect("expected namespace attribute to have a ':'");
81
82            let ns_type_id = format_ident!("{ns_type}");
83            let namespace = quote! {
84                fn namespace() -> Self::Namespace {
85                     Self::Namespace::try_from_str(#ns_var).unwrap()
86
87                }
88            };
89
90            let (response_struct_name, should_impl) = match opts.response {
91                //if a response struct is passed in opt, it is assumed it alrady implements needed
92                //trait
93                Some(res) => (format_ident!("{}", res), false),
94                None => (format_ident!("{}Response", name_no_suffix), true),
95            };
96
97            let mut output = quote! {};
98            let response_struct_id = format!("{response_struct_name}").to_lowercase();
99            if should_impl {
100                output = quote! {
101                    impl RpcResponse for #response_struct_name {
102                        const IDENTITY: &str = #response_struct_id;
103                    }
104                }
105            }
106            output = quote! {
107                #output
108                impl RpcRequest for #ident {
109                    type Response = #response_struct_name;
110                    type Namespace = #ns_type_id;
111                    #from_json
112                    #method_name
113                    #namespace
114                }
115            };
116
117            output.into()
118        }
119        _ => {
120            panic!("cannot derive this on anything but a struct")
121        }
122    }
123}
124
125#[proc_macro_derive(RequestWrapper)]
126pub fn derive_req_wrapper(input: TokenStream) -> TokenStream {
127    let input = parse_macro_input!(input);
128    let DeriveInput { ident, data, .. } = input;
129    match data {
130        Data::Enum(DataEnum { variants, .. }) => {
131            let mut from_impls = quote! {};
132            let mut into_req_body = quote! {};
133            let mut from_req_body = quote! {
134                let e:Box<dyn std::error::Error + Send + Sync + 'static> = std::io::Error::other("Could not get Request object").into();
135                let mut ret = Err(e);
136            };
137            for v in variants {
138                let id = v.ident;
139                let enum_typ = match v.fields {
140                    syn::Fields::Unnamed(t) => match t.unnamed.iter().next().cloned().unwrap().ty {
141                        syn::Type::Path(TypePath { path, .. }) => {
142                            path.segments.iter().next().unwrap().ident.clone()
143                        }
144                        other => panic!("Expected type path as unnamed variant, got: {other:#?}"),
145                    },
146                    _ => panic!("only unnamed struct variants supported"),
147                };
148                let not_request = format!("variant {id} does not implement RpcRequest");
149
150                into_req_body = quote! {
151                    #into_req_body
152                    Self::#id(r) => r.into_request(id).expect(#not_request),
153                };
154
155                from_req_body = quote! {
156                    #from_req_body
157                    if ret.is_err() {
158                        match #enum_typ::try_from_request(&req) {
159                            Ok(v) => return Ok(Self::#id(v)),
160                            Err(e) => ret = Err(e),
161                        }
162                    }
163                };
164
165                from_impls = quote! {
166                    #from_impls
167                    impl From<#enum_typ> for #ident {
168                        fn from(v: #enum_typ) -> Self {
169                            Self::#id(v)
170                        }
171                    }
172                };
173            }
174
175            let into_req = quote! {
176                fn into_req(&self, id: impl ToString) -> seraphic::Request {
177                    match self {
178                        #into_req_body
179                    }
180                }
181            };
182
183            let from_req = quote! {
184                fn try_from_req(req: seraphic::Request) -> std::result::Result<Self,Box<dyn std::error::Error + Send + Sync + 'static>> {
185                    #from_req_body
186                    return ret;
187                }
188            };
189
190            let output = quote! {
191                #from_impls
192                impl seraphic::RequestWrapper for #ident {
193                    #into_req
194                    #from_req
195
196                }
197            };
198            output.into()
199        }
200        _ => {
201            panic!("cannot derive this on anything but an enum")
202        }
203    }
204}
205
206#[proc_macro_derive(ResponseWrapper)]
207pub fn derive_res_wrapper(input: TokenStream) -> TokenStream {
208    let input = parse_macro_input!(input);
209    let DeriveInput { ident, data, .. } = input;
210    match data {
211        Data::Enum(DataEnum { variants, .. }) => {
212            let mut from_impls = quote! {};
213            let mut into_res_body = quote! {};
214            let mut from_res_body = quote! {
215                let e:Box<dyn std::error::Error + Send + Sync + 'static> = std::io::Error::other("Could not get Response object").into();
216                let mut ret = Err(e);
217            };
218            for v in variants {
219                let id = v.ident;
220                let enum_typ = match v.fields {
221                    syn::Fields::Unnamed(t) => match t.unnamed.iter().next().cloned().unwrap().ty {
222                        syn::Type::Path(TypePath { path, .. }) => {
223                            path.segments.iter().next().unwrap().ident.clone()
224                        }
225                        other => panic!("Expected type path as unnamed variant, got: {other:#?}"),
226                    },
227                    _ => panic!("only unnamed struct variants supported"),
228                };
229                let not_res = format!("variant {id} does not implement RpcResponse");
230
231                into_res_body = quote! {
232                    #into_res_body
233                    Self::#id(r) => r.into_response(id).expect(#not_res),
234                };
235
236                from_res_body = quote! {
237                    #from_res_body
238                    if ret.is_err() {
239                        ret = #enum_typ::try_from_response(&res).map(|maybe_ok|  maybe_ok.map(|ok| Self::#id(ok)));
240                    }
241                };
242
243                from_impls = quote! {
244                    #from_impls
245                    impl From<#enum_typ> for #ident {
246                        fn from(v: #enum_typ) -> Self {
247                            Self::#id(v)
248                        }
249                    }
250                };
251            }
252
253            let into_res = quote! {
254                fn into_res(&self, id: impl ToString) -> seraphic::IdentifiedResponse {
255                    match self {
256                        #into_res_body
257                    }
258                }
259            };
260
261            let from_res = quote! {
262                fn try_from_res(res: seraphic::IdentifiedResponse) -> std::result::Result<std::result::Result<Self, seraphic::error::Error>, Box<dyn std::error::Error + Send + Sync + 'static>> {
263                    #from_res_body
264                    return ret;
265                }
266            };
267
268            let output = quote! {
269                #from_impls
270                impl ResponseWrapper for #ident {
271                    #into_res
272                    #from_res
273
274                }
275            };
276            output.into()
277        }
278        _ => {
279            panic!("cannot derive this on anything but an enum")
280        }
281    }
282}
283
284#[derive(FromDeriveInput, Default)]
285#[darling(default, attributes(namespace))]
286struct NamespaceOpts {
287    separator: Option<String>,
288}
289
290#[proc_macro_derive(RpcNamespace, attributes(namespace))]
291pub fn derive_namespace(input: TokenStream) -> TokenStream {
292    let input = parse_macro_input!(input);
293    let opts = NamespaceOpts::from_derive_input(&input).expect("Wrong options");
294    let separator = opts.separator.unwrap_or("_".to_string());
295    let separator = quote! {const SEPARATOR: &str = #separator;};
296
297    let DeriveInput { ident, data, .. } = input;
298    match data {
299        Data::Enum(DataEnum { variants, .. }) => {
300            let mut from_str_body = quote! {};
301            let mut as_ref_body = quote! {};
302            let mut my_str_consts = quote! {};
303            for v in variants {
304                let id = v.ident;
305                let id_str = format!("{id}");
306                let const_id = format_ident!("{}", id_str.to_uppercase());
307                let const_val = id_str.to_lowercase();
308                my_str_consts = quote! {
309                    #my_str_consts
310                    const #const_id: &str = #const_val;
311                };
312                from_str_body = quote! {
313                    #from_str_body
314                    Self::#const_id => Some(Self::#id),
315                };
316                as_ref_body = quote! {
317                    #as_ref_body
318                    Self::#id => Self::#const_id,
319                };
320            }
321
322            let as_str = quote! {
323                fn as_str(&self)-> &str {
324                    match self {
325                        #as_ref_body
326                    }
327                }
328            };
329
330            let try_from = quote! {
331                fn try_from_str(str: &str) -> Option<Self> {
332                    match str {
333                        #from_str_body
334                        o => None,
335                    }
336                }
337            };
338
339            let output = quote! {
340                impl #ident {
341                    #my_str_consts
342                }
343                impl RpcNamespace for #ident {
344                 #separator
345                    #as_str
346                    #try_from
347                }
348            };
349
350            output.into()
351        }
352        _ => {
353            panic!("cannot derive this on anything but an enum")
354        }
355    }
356}