apollo_configuration_macros/
lib.rs1use crate::parse::ItemArgs;
2
3extern crate proc_macro;
4
5mod codegen;
6mod ir;
7mod parse;
8
9#[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 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 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 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 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;