Skip to main content

apollo_configuration_macros/
lib.rs

1use crate::parse::ItemArgs;
2
3extern crate proc_macro;
4
5mod codegen;
6mod ir;
7mod parse;
8
9/// Turn a Rust structure or enum into a configuration structure.
10///
11/// See the `apollo-configuration` crate-level documentation for more.
12#[proc_macro_attribute]
13pub fn configuration(
14    attr: proc_macro::TokenStream,
15    input: proc_macro::TokenStream,
16) -> proc_macro::TokenStream {
17    let args = match parse::parse_configuration_attribute(attr.into()) {
18        Ok(value) => value,
19        Err(err) => return syn::Error::into_compile_error(err).into(),
20    };
21
22    match syn::parse::<syn::ItemStruct>(input.clone()) {
23        Ok(input) => derive_configuration_struct(args, input)
24            .unwrap_or_else(syn::Error::into_compile_error)
25            .into(),
26        Err(struct_err) => match syn::parse::<syn::ItemEnum>(input) {
27            Ok(input) => derive_configuration_enum(args, input)
28                .unwrap_or_else(syn::Error::into_compile_error)
29                .into(),
30            // TODO(@goto-bus-stop): pick the error to report based on the keyword that was used
31            Err(_enum_err) => syn::Error::into_compile_error(struct_err).into(),
32        },
33    }
34}
35
36fn derive_configuration_struct(
37    args: ItemArgs,
38    input: syn::ItemStruct,
39) -> syn::Result<proc_macro2::TokenStream> {
40    let syn::ItemStruct {
41        attrs,
42        generics,
43        ident,
44        struct_token,
45        vis,
46        fields,
47        semi_token: _,
48    } = input;
49
50    let field_definitions = match fields {
51        syn::Fields::Unit => vec![],
52        syn::Fields::Named(fields) => fields
53            .named
54            .into_iter()
55            .map(parse::parse_named_field)
56            .collect::<Result<Vec<_>, _>>()?,
57        syn::Fields::Unnamed(fields) => {
58            return Err(syn::Error::new_spanned(
59                fields,
60                "tuple structs are not yet supported",
61            ));
62        }
63    };
64
65    let ir = ir::ConfigStructDefinition {
66        struct_token,
67        name: ident,
68        vis,
69        generics,
70        attrs,
71        fields: field_definitions,
72        validator: args.validator,
73    };
74
75    Ok(codegen::generate_configuration_struct(ir))
76}
77
78fn derive_configuration_enum(
79    args: ItemArgs,
80    input: syn::ItemEnum,
81) -> syn::Result<proc_macro2::TokenStream> {
82    let syn::ItemEnum {
83        attrs,
84        enum_token,
85        generics,
86        ident,
87        vis,
88        variants,
89        brace_token: _,
90    } = input;
91
92    // Separate serde/schemars helper attributes from other attributes.
93    // Helper attributes must come AFTER the derive that introduces them.
94    let (derive_helper_attrs, attrs): (Vec<_>, Vec<_>) = attrs
95        .into_iter()
96        .partition(|attr| attr.path().is_ident("serde") || attr.path().is_ident("schemars"));
97
98    let variant_definitions = variants
99        .into_iter()
100        .map(parse::parse_variant)
101        .collect::<Result<Vec<_>, _>>()?;
102
103    // Validate that at most one variant is marked as default
104    let default_variants: Vec<_> = variant_definitions
105        .iter()
106        .filter(|v| v.is_default)
107        .collect();
108    if default_variants.len() > 1 {
109        return Err(syn::Error::new(
110            default_variants[1].name.span(),
111            "only one variant can be marked as #[config(default)]",
112        ));
113    }
114
115    // Validate that default variants don't have required fields
116    if let Some(default_variant) = default_variants.first()
117        && let ir::VariantFields::Struct(fields) = &default_variant.fields
118    {
119        for field in fields {
120            if let Some(required_ident) = &field.required {
121                return Err(syn::Error::new_spanned(
122                    required_ident,
123                    "cannot mark field as required in a #[config(default)] variant",
124                ));
125            }
126        }
127    }
128
129    let ir = ir::ConfigEnumDefinition {
130        enum_token,
131        name: ident,
132        vis,
133        generics,
134        attrs,
135        derive_helper_attrs,
136        variants: variant_definitions,
137        validator: args.validator,
138    };
139
140    Ok(codegen::generate_configuration_enum(ir))
141}
142
143#[cfg(test)]
144mod tests;