ez_jsonrpc_macros/
lib.rs

1use std::iter;
2
3use darling::{ast, util, FromDeriveInput, FromField, FromMeta};
4use proc_macro2::TokenStream;
5use quote::{quote, ToTokens};
6use syn::{
7    ext::IdentExt as _,
8    parse::{Parse, ParseStream},
9    parse_macro_input, parse_quote,
10    punctuated::Punctuated,
11    DeriveInput, Ident, Token,
12};
13
14#[proc_macro_derive(DeserializePositional, attributes(jsonrpc))]
15pub fn deserialize_positional(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
16    let item = parse_macro_input!(item as DeriveInput);
17    expand_deserialize_positional(item)
18        .unwrap_or_else(syn::Error::into_compile_error)
19        .into()
20}
21
22#[proc_macro_derive(SerializePositional, attributes(jsonrpc))]
23pub fn serialize_positional(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
24    let item = parse_macro_input!(item as DeriveInput);
25    expand_serialize_positional(item)
26        .unwrap_or_else(syn::Error::into_compile_error)
27        .into()
28}
29
30#[proc_macro_derive(DeserializeNamed, attributes(jsonrpc))]
31pub fn deserialize_named(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
32    let item = parse_macro_input!(item as DeriveInput);
33    expand_deserialize_named(item)
34        .unwrap_or_else(syn::Error::into_compile_error)
35        .into()
36}
37
38#[proc_macro_derive(SerializeNamed, attributes(jsonrpc))]
39pub fn serialize_named(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
40    let item = parse_macro_input!(item as DeriveInput);
41    expand_serialize_named(item)
42        .unwrap_or_else(syn::Error::into_compile_error)
43        .into()
44}
45
46fn expand_serialize_named(item: DeriveInput) -> syn::Result<TokenStream> {
47    let Strukt {
48        ident,
49        fields,
50        krate,
51        ..
52    } = Strukt::new(&item)?;
53    let Exports {
54        SerializeNamed,
55        SerializeMap,
56        Result,
57        ..
58    } = Exports::new(krate);
59    let ser_fields = fields.iter().map(|Field { ident, rename, .. }| {
60        let name = rename.clone().unwrap_or(ident.unraw().to_string());
61        quote! {serializer.serialize_entry(&#name, &self.#ident)?;}
62    });
63    Ok(quote! {
64        const _: () = {
65            impl #SerializeNamed for #ident {
66                fn ser_named<S: #SerializeMap>(&self, mut serializer: S) -> #Result<S::Ok, S::Error> {
67                    #(#ser_fields)*
68                    serializer.end()
69                }
70            }
71        };
72    })
73}
74
75fn expand_deserialize_named(item: DeriveInput) -> syn::Result<TokenStream> {
76    let Strukt {
77        ident,
78        fields,
79        deny_unknown_fields,
80        krate,
81    } = Strukt::new(&item)?;
82    let Exports {
83        DeserializeNamed,
84        MapAccess,
85        Deserialize,
86        MapAccessDeserializer,
87        Result,
88        serde,
89        ..
90    } = Exports::new(krate);
91    let shim = syn::Ident::new(&format!("_{}", ident.unraw()), ident.span());
92    let deny_unknown_fields = deny_unknown_fields.then_some(quote! {#[serde(deny_unknown_fields)]});
93    let fields = fields.iter().map(
94        |Field {
95             ident,
96             ty,
97             rename,
98             default,
99         }| {
100            let default = default.then_some(quote! {#[serde(default)]});
101            let rename = rename.as_ref().map(|it| quote! {#[serde(rename = #it)]});
102            quote! {
103                #default
104                #rename
105                #ident: #ty,
106            }
107        },
108    );
109    let remote = ident.to_string();
110    let serde = serde.to_string();
111    Ok(quote! {
112        const _: () = {
113            impl<'de> #DeserializeNamed<'de> for #ident {
114                fn de_named<D: #MapAccess<'de>>(deserializer: D) -> #Result<Self, D::Error> {
115                    #[derive(#Deserialize)]
116                    #[serde(remote = #remote, crate = #serde)]
117                    #deny_unknown_fields
118                    struct #shim {
119                        #(#fields)*
120                    }
121                    #shim::deserialize(#MapAccessDeserializer::new(deserializer))
122                }
123            }
124        };
125    })
126}
127
128fn expand_deserialize_positional(item: DeriveInput) -> syn::Result<TokenStream> {
129    let Strukt {
130        ident,
131        fields,
132        deny_unknown_fields,
133        krate,
134    } = Strukt::new(&item)?;
135    for ((ix, l), r) in iter::zip(fields.iter().enumerate(), fields.iter().skip(1)) {
136        if l.default && !r.default {
137            return Err(syn::Error::new(
138                fields[ix].ident.span(),
139                "#[default] fields must follow other fields",
140            ));
141        }
142    }
143    let Exports {
144        DeserializePositional,
145        de_Error,
146        IgnoredAny,
147        SeqAccess,
148        Result,
149        Err_,
150        None_,
151        Ok_,
152        ..
153    } = Exports::new(krate);
154    let field_ctors = fields.iter().enumerate().map(
155        |(
156            ix,
157            Field {
158                ident, ty, default, ..
159            },
160        )| {
161            let missing_required_parameter = format!("missing required parameter at index {ix}");
162            match default {
163                false => quote! {
164                    #ident: deserializer.next_element::<#ty>()?
165                            .ok_or_else(|| #de_Error::custom(#missing_required_parameter))?,
166                },
167                true => quote! {
168                    #ident: deserializer.next_element::<#ty>()?.unwrap_or_default(),
169                },
170            }
171        },
172    );
173    let deny_unknown_fields = deny_unknown_fields.then_some(quote! {
174        let #Ok_(#None_) = deserializer.next_element::<#IgnoredAny>() else {
175            return #Err_(
176                #de_Error::custom("unknown trailing arguments")
177            );
178        };
179    });
180    Ok(quote! {
181        const _: () = {
182            impl<'de> #DeserializePositional<'de> for #ident {
183                fn de_positional<D: #SeqAccess<'de>>(mut deserializer: D) -> #Result<Self, D::Error> {
184                    let this = Self {
185                        #(#field_ctors)*
186                    };
187                    #deny_unknown_fields;
188                    #Ok_(this)
189                }
190            }
191        };
192    })
193}
194
195fn expand_serialize_positional(item: DeriveInput) -> syn::Result<TokenStream> {
196    let Strukt {
197        ident,
198        krate,
199        fields,
200        ..
201    } = Strukt::new(&item)?;
202    let Exports {
203        SerializePositional,
204        SerializeSeq,
205        Result,
206        ..
207    } = Exports::new(krate);
208    let ser_fields = fields
209        .iter()
210        .map(|Field { ident, .. }| quote! { serializer.serialize_element(&self.#ident)?; });
211
212    Ok(quote! {
213        const _: () = {
214            impl #SerializePositional for #ident {
215                fn ser_positional<S: #SerializeSeq>(&self, mut serializer: S) -> #Result<S::Ok, S::Error> {
216                    #(#ser_fields)*
217                    serializer.end()
218                }
219            }
220        };
221    })
222}
223
224#[derive(Debug)]
225struct Strukt {
226    ident: Ident,
227    fields: Vec<Field>,
228    deny_unknown_fields: bool,
229    krate: Option<ModulePath>,
230}
231
232macro_rules! exports {
233    ($($path:path as $ident:ident);* $(;)?) => {
234        #[allow(non_snake_case)]
235        struct Exports {
236            $(
237                #[doc = stringify!($path)]
238                $ident: TokenStream,
239            )*
240        }
241        impl Exports {
242            fn new(path_to_ez_jsonrpc: Option<ModulePath>) -> Self {
243                let path_to_ez_jsonrpc = path_to_ez_jsonrpc.unwrap_or(parse_quote!(::ez_jsonrpc)).into_token_stream();
244                Self {
245                    $(
246                        $ident: quote! { #path_to_ez_jsonrpc::__private::exports::$ident }
247                    ),*
248                }
249            }
250        }
251    };
252}
253
254exports! { // sync with the main crate
255    crate::params::DeserializeNamed as DeserializeNamed;
256    crate::params::DeserializePositional as DeserializePositional;
257    crate::params::SerializeNamed as SerializeNamed;
258    crate::params::SerializePositional as SerializePositional;
259    Err as Err_;
260    None as None_;
261    Ok as Ok_;
262    Result as Result;
263    serde as serde;
264    serde::de::Error as de_Error;
265    serde::de::IgnoredAny as IgnoredAny;
266    serde::de::MapAccess as MapAccess;
267    serde::de::SeqAccess as SeqAccess;
268    serde::de::value::MapAccessDeserializer as MapAccessDeserializer;
269    serde::Deserialize as Deserialize;
270    serde::ser::SerializeMap as SerializeMap;
271    serde::ser::SerializeSeq as SerializeSeq;
272}
273
274impl Strukt {
275    fn new(input: &DeriveInput) -> darling::Result<Self> {
276        #[derive(FromDeriveInput)]
277        #[darling(attributes(jsonrpc))]
278        struct _Strukt {
279            ident: Ident,
280            data: ast::Data<util::Ignored, Field>,
281            #[darling(default)]
282            deny_unknown_fields: bool,
283            #[darling(rename = "crate")]
284            krate: Option<ModulePath>,
285        }
286        if !input.generics.params.is_empty()
287            || input
288                .generics
289                .where_clause
290                .as_ref()
291                .is_some_and(|it| !it.predicates.is_empty())
292        {
293            return Err(darling::Error::unsupported_shape("generics").with_span(&input.generics));
294        }
295        let _Strukt {
296            ident,
297            data,
298            deny_unknown_fields,
299            krate,
300        } = FromDeriveInput::from_derive_input(input)?;
301        Ok(Self {
302            ident,
303            fields: match data {
304                ast::Data::Enum(_) => return Err(darling::Error::unsupported_shape("enum")),
305                ast::Data::Struct(fields) => fields.fields,
306            },
307            deny_unknown_fields,
308            krate,
309        })
310    }
311}
312
313#[derive(Debug)]
314struct ModulePath {
315    leading_colon: Option<Token![::]>,
316    segments: Punctuated<Ident, Token![::]>,
317}
318
319impl Parse for ModulePath {
320    fn parse(input: ParseStream) -> syn::Result<Self> {
321        let syn::Path {
322            leading_colon,
323            segments,
324        } = input.call(syn::Path::parse_mod_style)?;
325        Ok(Self {
326            leading_colon,
327            segments: segments.into_iter().map(|it| it.ident).collect(),
328        })
329    }
330}
331
332impl ToTokens for ModulePath {
333    fn to_tokens(&self, tokens: &mut TokenStream) {
334        let Self {
335            leading_colon,
336            segments,
337        } = self;
338        leading_colon.to_tokens(tokens);
339        segments.to_tokens(tokens);
340    }
341}
342
343impl FromMeta for ModulePath {
344    fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
345        Ok(syn::parse2(expr.to_token_stream())?)
346    }
347}
348
349#[derive(Debug)]
350struct Field {
351    ident: Ident,
352    ty: syn::Type,
353    rename: Option<String>,
354    default: bool,
355}
356
357impl FromField for Field {
358    fn from_field(field: &syn::Field) -> darling::Result<Self> {
359        #[derive(FromField)]
360        #[darling(attributes(jsonrpc))]
361        struct _Field {
362            ident: Option<Ident>,
363            ty: syn::Type,
364            rename: Option<String>,
365            #[darling(default)]
366            default: bool,
367        }
368
369        let _Field {
370            ident,
371            ty,
372            rename,
373            default,
374        } = FromField::from_field(field)?;
375        Ok(Self {
376            ident: ident.ok_or_else(|| {
377                darling::Error::unsupported_shape_with_expected(
378                    "unnamed fields",
379                    &"a struct with named fields",
380                )
381            })?,
382            ty,
383            rename,
384            default,
385        })
386    }
387}