lbr_prelude_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{self, parse_macro_input, DeriveInput};
4
5/// Derive a `Json` trait implementation
6///
7/// Currently we only support a subset of types with an opinionated serialisation scheme. If you
8/// need anything else than what this library provides, it is advised to hand write the
9/// implementation.
10///
11/// Supported types:
12/// - **unit structs (newtypes)**: this will simply remove the wrapper, and serialize the wrapped value
13/// - **structs**: serialized into a Json Object
14/// - **enums with unnamed fields**: serialized into an object with the following schema: `{"name": string, "fields": any[]}`
15#[proc_macro_derive(Json)]
16pub fn derive_json_fn(input: TokenStream) -> TokenStream {
17    let ast = parse_macro_input!(input as DeriveInput);
18
19    let ident = &ast.ident;
20
21    let (to_json_impl, from_json_impl) = match &ast.data {
22        syn::Data::Struct(data_struct) => match &data_struct.fields {
23            syn::Fields::Named(fields_named) => impl_struct(ident, fields_named),
24            syn::Fields::Unnamed(fields_unnamed) => {
25                if fields_unnamed.unnamed.len() == 1 {
26                    impl_newtype()
27                } else {
28                    impl_tuple(ident, fields_unnamed)
29                }
30            }
31            syn::Fields::Unit => unimplemented!("Units are unsupported"),
32        },
33        syn::Data::Enum(data_enum) => impl_enum(ident, &data_enum.variants),
34        syn::Data::Union(_data_union) => unimplemented!("Unions are unsupported"),
35    };
36
37    let (impl_generics, ty_generics, where_clause) = &ast.generics.split_for_impl();
38
39    let expanded = quote! {
40        impl #impl_generics lbr_prelude::json::Json for #ident #ty_generics #where_clause {
41
42            #to_json_impl
43            #from_json_impl
44        }
45    };
46
47    TokenStream::from(expanded)
48}
49
50/// Derive `Json` implementations for a struct type
51/// All fields must implement the `Json` trait
52fn impl_struct(
53    ident: &syn::Ident,
54    fields_named: &syn::FieldsNamed,
55) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
56    let ident_str = ident.to_string();
57    let named = &fields_named.named;
58
59    // Insert keys and values of the JSON object into a dict
60    let dict_insert = named.iter().map(|field| {
61        let key = &field.ident;
62        let key_str = key.as_ref().unwrap().to_string();
63        quote! {
64            dict.insert(#key_str.to_owned(), self.#key.to_json());
65        }
66    });
67
68    let to_json_impl = quote! {
69        fn to_json(&self) -> serde_json::Value {
70            let mut dict = serde_json::Map::new();
71            #(#dict_insert)*
72
73            serde_json::Value::Object(dict)
74        }
75    };
76
77    // Get the values from the JSON object
78    let dict_get = named.iter().map(|field| {
79        let key = &field.ident;
80        let key_str = key.as_ref().unwrap().to_string();
81        quote! {
82            let #key = dict
83                .get(#key_str)
84                .ok_or(lbr_prelude::error::Error::UnexpectedFieldName {
85                    wanted: #key_str.to_owned(),
86                    got: dict.keys().cloned().collect(),
87                    parser: #ident_str.to_owned(),
88                })
89                .and_then(lbr_prelude::json::Json::from_json)?;
90        }
91    });
92
93    let keys = named.iter().map(|field| &field.ident);
94
95    let from_json_impl = quote! {
96        fn from_json(value: &serde_json::Value) -> std::result::Result<Self, lbr_prelude::error::Error> {
97            match value {
98                serde_json::Value::Object(dict) => {
99                    #(#dict_get)*
100
101                    Ok(Self {
102                        #(#keys,)*
103                    })
104                }
105                _ => Err(lbr_prelude::error::Error::UnexpectedJsonType {
106                    wanted: lbr_prelude::error::JsonType::Object,
107                    got: lbr_prelude::error::JsonType::from(value),
108                    parser: #ident_str.to_owned(),
109                }),
110            }
111        }
112    };
113
114    (to_json_impl, from_json_impl)
115}
116
117/// Derive `Json` implementations for a tuple struct type
118/// All fields must implement the `Json` trait
119fn impl_tuple(
120    ident: &syn::Ident,
121    fields_unnamed: &syn::FieldsUnnamed,
122) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
123    let ident_str = ident.to_string();
124
125    let arity = fields_unnamed.unnamed.len();
126    let to_json_indices = (0..arity).map(syn::Index::from);
127    let from_json_indices = to_json_indices.clone();
128
129    let to_json_impl = quote! {
130        fn to_json(&self) -> serde_json::Value {
131            serde_json::Value::Array(vec![
132                #(self.#to_json_indices.to_json(),)*
133            ])
134        }
135    };
136
137    let from_json_impl = quote! {
138        fn from_json(value: &serde_json::Value) -> std::result::Result<Self, lbr_prelude::error::Error> {
139            Vec::from_json(value).and_then(|vec: Vec<serde_json::Value>| {
140                if vec.len() == #arity {
141                    Ok(Self(
142                        #(Json::from_json(&vec[#from_json_indices])?,)*
143                    ))
144                } else {
145                    Err(lbr_prelude::error::Error::UnexpectedArrayLength {
146                        wanted: #arity,
147                        got: vec.len(),
148                    parser: #ident_str.to_owned(),
149                    })
150                }
151            })
152        }
153    };
154
155    (to_json_impl, from_json_impl)
156}
157
158/// Derive transparent `Json` implementations for a tuple type, the wrapper will not be present
159/// in the serialised format
160/// The enclosed field must implement the `Json` trait
161fn impl_newtype() -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
162    let to_json_impl = quote! {
163        fn to_json(&self) -> serde_json::Value {
164            self.0.to_json()
165        }
166    };
167
168    let from_json_impl = quote! {
169        fn from_json(value: &serde_json::Value) -> std::result::Result<Self, lbr_prelude::error::Error> {
170            Ok(Self(lbr_prelude::json::Json::from_json(value)?))
171        }
172    };
173
174    (to_json_impl, from_json_impl)
175}
176
177/// Derive `Json` implementation for an enum type
178/// All fields must implement the `Json` trait
179fn impl_enum(
180    ident: &syn::Ident,
181    variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
182) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
183    let ident_str = ident.to_string();
184    // Arms of the pattern match over the variants of the original data
185    let to_json_match_arms = variants.iter().map(|variant| {
186        let variant_ident = &variant.ident;
187        let variant_str = variant.ident.to_string();
188
189        match &variant.fields {
190            syn::Fields::Named(_fields_named) => {
191                unimplemented!("Enums with named fields are unsupported.")
192            }
193            syn::Fields::Unnamed(fields_unnamed) => {
194                let arity = fields_unnamed.unnamed.len();
195                let fields = (0..arity).map(|i| format_ident!("f{}", i));
196                let fields_2 = fields.clone();
197
198                quote! {
199                    #ident::#variant_ident( #(#fields),* ) =>
200                        lbr_prelude::json::json_constructor(#variant_str, &vec![
201                           #(#fields_2.to_json(),)*
202                        ]),
203                }
204            }
205            syn::Fields::Unit => quote! {
206                #ident::#variant_ident =>
207                    lbr_prelude::json::json_constructor(#variant_str, &Vec::with_capacity(0)),
208            },
209        }
210    });
211
212    let to_json_impl = quote! {
213        fn to_json(&self) -> serde_json::Value {
214            match self {
215                #(#to_json_match_arms)*
216            }
217        }
218    };
219
220    // Arms of the pattern match over the JSON name and fields
221    let from_json_match_arms = variants.iter().map(|variant| {
222        let variant_ident = &variant.ident;
223        let variant_str = variant.ident.to_string();
224
225        match &variant.fields {
226            syn::Fields::Named(_fields_named) => {
227                unimplemented!("Enums with named fields are unsupported.")
228            }
229
230            syn::Fields::Unnamed(fields_unnamed) => {
231                let arity = fields_unnamed.unnamed.len();
232                let fields = (0..arity).map(syn::Index::from);
233
234                quote! {
235                    (
236                        #variant_str,
237                        Box::new(|ctor_fields| {
238                            if ctor_fields.len() == #arity {
239                                Ok(#ident::#variant_ident(
240                                    #(Json::from_json(&ctor_fields[#fields])?),*
241                                ))
242                            } else {
243                                Err(lbr_prelude::error::Error::UnexpectedArrayLength {
244                                    wanted: #arity,
245                                    got: ctor_fields.len(),
246                                    parser: #ident_str.to_owned(),
247                                })
248                            }
249                        })
250                    )
251                }
252            }
253
254            syn::Fields::Unit => quote! {
255                (
256                    #variant_str,
257                    Box::new(|ctor_fields| match &ctor_fields[..] {
258                        [] => Ok(#ident::#variant),
259                        _ => Err(lbr_prelude::error::Error::UnexpectedArrayLength {
260                            wanted: 0,
261                            got: ctor_fields.len(),
262                            parser: #ident_str.to_owned(),
263                        })
264                    })
265                )
266
267            },
268        }
269    });
270
271    let from_json_impl = quote! {
272        fn from_json(value: &serde_json::Value) -> std::result::Result<Self, lbr_prelude::error::Error> {
273            lbr_prelude::json::case_json_constructor(#ident_str, vec![
274                #(#from_json_match_arms),*
275            ],
276            value)
277        }
278    };
279
280    (to_json_impl, from_json_impl)
281}