settings_schema_derive/
lib.rs

1mod ty;
2
3use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant};
4use proc_macro::TokenStream;
5use proc_macro2::TokenStream as TokenStream2;
6use quote::{quote, ToTokens};
7use std::string::ToString;
8use syn::{DeriveInput, Error, Ident, Lit, Type, Visibility};
9use ty::{NumericGuiType, TypeSchemaData};
10
11type TResult<T = TokenStream2> = Result<T, TokenStream>;
12
13fn error<T, TT: ToTokens>(message: &str, tokens: TT) -> TResult<T> {
14    Err(
15        Error::new_spanned(tokens, format!("[SettingsSchema] {}", message))
16            .to_compile_error()
17            .into(),
18    )
19}
20
21fn suffix_ident(ty_ident: &Ident, suffix: &str) -> Ident {
22    Ident::new(&format!("{}{}", ty_ident, suffix), ty_ident.span())
23}
24
25#[derive(Default)]
26struct StringMap(Vec<(String, String)>);
27
28impl FromMeta for StringMap {
29    fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
30        if let syn::Meta::List(value) = item {
31            let mut strings = vec![];
32            for item in &value.nested {
33                if let syn::NestedMeta::Meta(syn::Meta::NameValue(key_value)) = item {
34                    let key_ident = key_value.path.get_ident().ok_or_else(|| {
35                        darling::Error::custom("Key must be an identifier")
36                            .with_span(&key_value.path)
37                    })?;
38
39                    let value = if let Lit::Str(string) = &key_value.lit {
40                        string.value()
41                    } else {
42                        return Err(darling::Error::custom("Value must be a string")
43                            .with_span(&key_value.lit));
44                    };
45
46                    strings.push((key_ident.to_string(), value));
47                } else {
48                    return Err(
49                        darling::Error::custom("Unexpected syntax. Use `key = \"value\"`")
50                            .with_span(item),
51                    );
52                }
53            }
54
55            Ok(StringMap(strings))
56        } else {
57            Err(
58                darling::Error::custom("Invalid format for \"strings\". Use `strings(...)`")
59                    .with_span(item),
60            )
61        }
62    }
63}
64
65#[derive(FromField)]
66#[darling(attributes(schema))]
67struct FieldMeta {
68    vis: Visibility,
69
70    ident: Option<Ident>,
71
72    ty: Type,
73
74    #[darling(default)]
75    strings: StringMap,
76
77    #[darling(multiple, rename = "flag")]
78    flags: Vec<String>,
79
80    #[darling(default)]
81    gui: Option<NumericGuiType>,
82
83    suffix: Option<String>,
84}
85
86#[derive(FromMeta)]
87enum ChoiceControlType {
88    Dropdown,
89    ButtonGroup,
90}
91
92#[derive(FromVariant)]
93#[darling(attributes(schema), supports(unit, newtype, named))]
94struct VariantMeta {
95    ident: Ident,
96
97    #[darling(default)]
98    strings: StringMap,
99
100    #[darling(multiple, rename = "flag")]
101    flags: Vec<String>,
102
103    fields: ast::Fields<FieldMeta>,
104}
105
106#[derive(FromDeriveInput)]
107#[darling(attributes(schema), supports(struct_named, enum_any))]
108struct DeriveInputMeta {
109    data: ast::Data<VariantMeta, FieldMeta>,
110
111    #[darling(default)]
112    gui: Option<ChoiceControlType>,
113}
114
115struct SchemaData {
116    // Fields for the schema representation struct. In case of struct, the fields have the same name
117    // of the original fields. Incase of enum, adds the field `variant` plus the name of the
118    // variants of the original enum
119    default_fields_ts: TokenStream2,
120
121    // Schema instatiation code, to be inserted into the schema() method
122    schema_code_ts: TokenStream2,
123
124    // Auxiliary objects for enums (default variant and default variants data)
125    aux_objects_ts: Option<TokenStream2>,
126}
127
128fn named_fields_schema(
129    meta: Vec<FieldMeta>,
130    vis_override: Option<Visibility>,
131) -> TResult<SchemaData> {
132    let mut default_entries_ts = vec![];
133    let mut schema_entries_ts = vec![];
134
135    for meta in meta {
136        let vis = if let Some(vis) = &vis_override {
137            vis
138        } else {
139            &meta.vis
140        };
141        let field_ident = meta.ident.as_ref().unwrap().clone();
142        let TypeSchemaData {
143            default_ty_ts,
144            schema_code_ts,
145        } = ty::schema(&meta.ty, &meta)?;
146        let field_string = field_ident.to_string();
147
148        let string_key_values_ts = meta
149            .strings
150            .0
151            .into_iter()
152            .map(|(key, value)| quote!((#key.into(), #value.into())));
153        let string_flags = meta.flags;
154
155        default_entries_ts.push(quote!(#vis #field_ident: #default_ty_ts));
156        schema_entries_ts.push(quote!(settings_schema::SchemaEntry {
157            name: #field_string.into(),
158            strings: [#(#string_key_values_ts),*].into(),
159            flags: [#(#string_flags.into()),*].into(),
160            content: {
161                let default = default.#field_ident;
162                #schema_code_ts
163            }
164        }));
165    }
166
167    Ok(SchemaData {
168        default_fields_ts: quote!(#(#default_entries_ts,)*),
169        schema_code_ts: quote!(settings_schema::SchemaNode::Section(
170            vec![#(#schema_entries_ts),*]
171        )),
172        aux_objects_ts: None,
173    })
174}
175
176fn variants_schema(
177    gui_type: Option<ChoiceControlType>,
178    vis: &Visibility,
179    ident: &Ident,
180    meta: Vec<VariantMeta>,
181) -> TResult<SchemaData> {
182    let mut default_variants_ts = vec![];
183    let mut variant_entries_ts = vec![];
184    let mut variants = vec![];
185    let mut aux_variants_structs_ts = vec![];
186
187    let gui_ts = match gui_type {
188        None => quote!(None),
189        Some(ChoiceControlType::Dropdown) => {
190            quote!(Some(settings_schema::ChoiceControlType::Dropdown))
191        }
192        Some(ChoiceControlType::ButtonGroup) => {
193            quote!(Some(settings_schema::ChoiceControlType::ButtonGroup))
194        }
195    };
196
197    for meta in meta {
198        let variant_ident = meta.ident;
199        let variant_string = variant_ident.to_string();
200
201        variants.push(variant_ident.clone());
202
203        let entry_content_ts = match meta.fields.style {
204            ast::Style::Tuple => {
205                // darling macro attribute makes sure there is one and only one field
206                let field_meta = &meta.fields.fields[0];
207                let TypeSchemaData {
208                    default_ty_ts,
209                    schema_code_ts,
210                } = ty::schema(&field_meta.ty, field_meta)?;
211
212                if !field_meta.strings.0.is_empty() {
213                    return error(
214                        "Can't use `strings` list in variant tuple field.",
215                        field_meta.ty.to_token_stream(),
216                    );
217                }
218
219                default_variants_ts.push(quote!(#vis #variant_ident: #default_ty_ts));
220
221                quote!(Some({
222                    let default = default.#variant_ident;
223                    #schema_code_ts
224                }))
225            }
226            ast::Style::Struct => {
227                let default_ty_ts =
228                    suffix_ident(&suffix_ident(ident, &variant_ident.to_string()), "Default")
229                        .to_token_stream();
230                let SchemaData {
231                    default_fields_ts,
232                    schema_code_ts,
233                    ..
234                } = named_fields_schema(meta.fields.fields, Some(vis.clone()))?;
235
236                default_variants_ts.push(quote!(#vis #variant_ident: #default_ty_ts));
237                aux_variants_structs_ts.push(quote! {
238                    #[derive(settings_schema::Serialize, settings_schema::Deserialize, Clone, Debug)]
239                    #vis struct #default_ty_ts {
240                        #default_fields_ts
241                    }
242                });
243
244                quote!(Some({
245                    let default = default.#variant_ident;
246                    #schema_code_ts
247                }))
248            }
249            ast::Style::Unit => quote!(None),
250        };
251
252        let string_key_values_ts = meta
253            .strings
254            .0
255            .into_iter()
256            .map(|(key, value)| quote!((#key.into(), #value.into())));
257        let string_flags = meta.flags;
258
259        variant_entries_ts.push(quote!(settings_schema::SchemaEntry {
260            name: #variant_string.into(),
261            strings: [#(#string_key_values_ts),*].into(),
262            flags: [#(#string_flags.into()),*].into(),
263            content: #entry_content_ts,
264        }));
265    }
266
267    let default_variant_ty = suffix_ident(ident, "DefaultVariant");
268
269    Ok(SchemaData {
270        default_fields_ts: quote! {
271            #(#default_variants_ts,)*
272            #vis variant: #default_variant_ty,
273        },
274        schema_code_ts: quote!(settings_schema::SchemaNode::Choice {
275            default: settings_schema::to_json_value(default.variant)
276                .unwrap()
277                .as_str()
278                .unwrap()
279                .into(),
280            variants: vec![#(#variant_entries_ts),*],
281            gui: #gui_ts
282        }),
283        aux_objects_ts: Some(quote! {
284            #(#aux_variants_structs_ts)*
285
286            #[derive(settings_schema::Serialize, settings_schema::Deserialize, Clone, Debug)]
287            #vis enum #default_variant_ty {
288                #(#variants,)*
289            }
290        }),
291    })
292}
293
294// Generate new code from the given struct or enum.
295//
296// In case of a struct two things are created:
297// * a default settings representation (struct <StructName>Default)
298// * a impl with a schema() method, that returns the schema associated to the current struct
299// The default representation is a struct that contains each of the original fields, where the types
300// are substituted with the matching default representation type.
301//
302// Like for structs, for enums the default settings representation and a schema method are generated.
303// Some auxiliary objects are also generated: the default variant (enum <EnumName>DefaultVariant)
304// and default variants stuctures (struct <EnumName><VariantName>Default). The default variant is a
305// plain old enum with the same variants as the original enum but no variant data. The default
306// variants stuctures contains the default representation of the variants content, both in case of
307// newtype and struct style content.
308// The default representation struct contains the `variant` field of type default variant; the rest
309// of the fields are the name of the original variants, without casing transformations. Only
310// variants which contains data are inserted as fields in the default representation struct.
311fn schema(derive_input: DeriveInput) -> TResult {
312    if !derive_input.generics.params.is_empty() {
313        return error("Generics not supported", &derive_input.generics);
314    }
315
316    let meta: DeriveInputMeta =
317        FromDeriveInput::from_derive_input(&derive_input).map_err(|e| e.write_errors())?;
318
319    let gui_type = meta.gui;
320    let vis = derive_input.vis;
321    let derive_input_ident = derive_input.ident;
322    let default_ty_ident = suffix_ident(&derive_input_ident, "Default");
323
324    let SchemaData {
325        default_fields_ts,
326        schema_code_ts,
327        aux_objects_ts,
328    } = match meta.data {
329        ast::Data::Enum(variants) => {
330            variants_schema(gui_type, &vis, &derive_input_ident, variants)?
331        }
332        ast::Data::Struct(ast::Fields { fields, .. }) => named_fields_schema(fields, None)?,
333    };
334
335    Ok(quote! {
336        #aux_objects_ts
337
338        #[allow(non_snake_case)]
339        #[derive(settings_schema::Serialize, settings_schema::Deserialize, Clone, Debug)]
340        #vis struct #default_ty_ident {
341            #default_fields_ts
342        }
343
344        impl #derive_input_ident {
345            #vis fn schema(default: #default_ty_ident) -> settings_schema::SchemaNode {
346                #schema_code_ts
347            }
348        }
349    })
350}
351
352// This is the entry point of the macro, that is `derive(SettingsSchema)`
353#[proc_macro_derive(SettingsSchema, attributes(schema))]
354pub fn create_settings_schema_fn_and_default_ty(input: TokenStream) -> TokenStream {
355    match schema(syn::parse_macro_input!(input as DeriveInput)) {
356        Ok(tokens) => tokens.into(),
357        Err(e) => e,
358    }
359}