1mod registry;
2
3use darling::{FromAttributes, FromMeta};
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::parse_quote;
7use syn::spanned::Spanned;
8use syn::{parse_macro_input, Data, Ident, LitStr, Type};
9
10#[derive(Debug, FromAttributes)]
11#[darling(attributes(property))]
12struct PropertyArgs {
13 key: Option<LitStr>,
14 #[darling(default)]
15 optional: bool,
16 #[darling(default)]
17 flatten: bool,
18 indices: Option<Vec<LitStr>>,
19 array_ref: Option<ArrayRefArgs>,
20}
21
22#[derive(Debug, FromMeta)]
23struct ArrayRefArgs {
24 key: LitStr,
25 prefix: Option<LitStr>,
26}
27
28struct FieldDefinition {
29 pub ident: Ident,
30 pub ty: Type,
31 pub attrs: PropertyArgs,
32}
33
34#[proc_macro]
35pub fn expand_config_registry(input: TokenStream) -> TokenStream {
36 let input = parse_macro_input!(input as registry::RegistryDefinition);
37 registry::expand(input)
38 .into()
39}
40
41
42#[proc_macro_derive(Configuration, attributes(property))]
43pub fn derive_configuration(input: TokenStream) -> TokenStream {
44 let input = parse_macro_input!(input as syn::DeriveInput);
45 let ident = &input.ident;
46
47 let data = match input.data {
48 Data::Struct(d) => d,
49 _ => {
50 return syn::Error::new(input.span(), "Configuration can only be derived for structs")
51 .to_compile_error()
52 .into();
53 }
54 };
55
56 let field_defs: Vec<FieldDefinition> = data.fields.iter().map(|field| {
57 let attrs = &field.attrs;
58 let has_property_attr = attrs.iter().any(|attr| {
59 attr.path().is_ident("property")
60 });
61 assert!(has_property_attr, "Field {} does not have a #[property] attribute", field.ident.as_ref().unwrap());
62
63 let attrs: PropertyArgs = PropertyArgs::from_attributes(&attrs).unwrap();
64
65 FieldDefinition {
66 ident: field.ident.clone().expect("Fields must have a name"),
67 ty: field.ty.clone(),
68 attrs,
69 }
70 }).collect();
71
72 let bindings = field_defs.iter().map(|field_def| {
73 let field = field_def.ident.clone();
74 let key = field_def.attrs.key.clone().unwrap_or_else(|| LitStr::new(&field.to_string().as_str(), field.span()));
75
76 let handle_err = if field_def.attrs.optional {
77 quote!(
78 Ok(r) => {
79 result = result.or(Ok(r));
80 },
81 Err(e) => {
82 if e != figen::error::Error::NotFound {
83 return Err(e);
84 }
85 }
86 )
87 } else {
88 quote!(
89 Ok(r) => {
90 result = result.or(Ok(r));
91 },
92 Err(figen::error::Error::NotFound) => {
93 return Err(figen::error::Error::Required);
95 }
96 Err(e) => {
97 return Err(e);
98 }
99 )
100 };
101
102
103 let bind_call = if let Type::Array(_) = &field_def.ty {
104 let indices = field_def.attrs.indices.as_ref().map(|indices| {
105 quote!(
107 {
108 let _indices = &[#(#indices),*];
109 assert!(_indices.len() == self.#field.len(), "Array indices length does not match array size");
110 figen::binder::ArrayConfigIndicesMode::Custom(_indices)
111 }
112 )
113 }).or_else(|| {
114 Some(quote!(figen::binder::ArrayConfigIndicesMode::ZeroIndexed))
115 });
116
117 quote!(
118 {
119
120 let mut _binder = figen::binder::ArrayConfigBinder::new(#indices, &mut self.#field);
121 match _binder.bind(path, loader) {
122 Ok(r) => {
123 result = result.or(Ok(r));
124 },
125 Err(e) => {
126 if e != figen::error::Error::NotFound {
127 return Err(e);
128 }
129 }
130 };
131 }
132 )
133 } else {
134 if let Some(array_ref) = &field_def.attrs.array_ref {
135 let array_ref_key = &array_ref.key;
136 let array_ref_prefix = &array_ref.prefix;
137
138 let binder = if array_ref_prefix.is_some() {
139 quote!(
140 figen::binder::ArrayRefBinder::new(
141 #array_ref_key,
142 Some(#array_ref_prefix),
143 &mut self.#field
144 );
145 )
146 } else {
147 quote!(
148 figen::binder::ArrayRefBinder::new(
149 #array_ref_key,
150 None,
151 &mut self.#field
152 );
153 )
154 };
155
156 quote!(
157 {
158 let mut _binder = #binder
159 match _binder.bind(path, loader) {
160 Ok(r) => {
161 result = result.or(Ok(r));
162 },
163 Err(e) => {
164 if e != figen::error::Error::NotFound {
165 return Err(e);
166 }
167 }
168 };
169 }
170 )
171 } else {
172 quote!(
173 match self.#field.bind(path, loader) {
174 #handle_err
175 };
176 )
177 }
178 };
179
180 if field_def.attrs.flatten {
182 bind_call
183 } else {
184 quote!(
185 path.push(#key);
186 #bind_call
187 path.pop();
188 )
189 }
190 });
191
192 let initializers = field_defs.iter().map(|field_def| {
193 let field = &field_def.ident;
194 let ty = &field_def.ty;
195 let key = field_def
196 .attrs
197 .key
198 .clone()
199 .unwrap_or_else(|| LitStr::new(field.to_string().as_str(), field.span()));
200
201 let initialize_call = if let Type::Array(_) = ty {
202 let indices = if let Some(indices) = &field_def.attrs.indices {
203 quote!(figen::binder::ArrayConfigIndicesMode::Custom(&[#(#indices),*]))
204 } else {
205 quote!(figen::binder::ArrayConfigIndicesMode::ZeroIndexed)
206 };
207
208 quote!(
209 figen::binder::ArrayConfigInitializer::initialize(
210 #indices,
211 path,
212 loader,
213 )
214 )
215 } else if let Some(array_ref) = &field_def.attrs.array_ref {
216 let array_ref_key = &array_ref.key;
217 let prefix = if let Some(prefix) = &array_ref.prefix {
218 quote!(Some(#prefix))
219 } else {
220 quote!(None)
221 };
222
223 quote!(
224 figen::binder::ArrayRefInitializer::initialize(
225 #array_ref_key,
226 #prefix,
227 path,
228 loader,
229 )
230 )
231 } else {
232 quote!(
233 <#ty as figen::binder::ConfigInitializer<T, U>>::initialize(path, loader)
234 )
235 };
236
237 if field_def.attrs.flatten {
238 quote!(let #field: #ty = #initialize_call?;)
239 } else {
240 quote!(
241 path.push(#key);
242 let _result = #initialize_call;
243 path.pop();
244 let #field: #ty = _result?;
245 )
246 }
247 });
248
249 let initialized_fields = field_defs.iter().map(|field_def| &field_def.ident);
250
251 let result_expand = if field_defs.iter().all(|f| f.attrs.optional) {
252 quote!(let mut result = Err(figen::error::Error::NotFound);)
253 } else {
254 quote!(let mut result = Err(figen::error::Error::Required);)
255 };
256 quote!(
257 impl<T, U> figen::binder::ConfigBinder<T, U> for #ident where
258 T: figen::BindPath,
259 U: figen::loader::PropertyLoader,
260 {
261 #[cold]
262 #[inline(never)]
263 fn bind(&mut self, path: &mut T, loader: &U) -> figen::error::Result<()> {
264 #result_expand
265 #(#bindings);*
266 result
267 }
268 }
269
270 impl<T, U> figen::binder::ConfigInitializer<T, U> for #ident where
271 T: figen::BindPath,
272 U: figen::loader::PropertyLoader,
273 {
274 #[cold]
275 #[inline(never)]
276 fn initialize(path: &mut T, loader: &U) -> figen::error::Result<Self> {
277 #(#initializers)*
278 Ok(Self {
279 #(#initialized_fields),*
280 })
281 }
282 }
283 ).into()
284}
285
286#[proc_macro_derive(ConfigBinder)]
287pub fn derive_config_binder(input: TokenStream) -> TokenStream {
288 let input = parse_macro_input!(input as syn::DeriveInput);
289 let ident = &input.ident;
290
291 let mut generics = input.generics.clone();
292 generics.params.push(parse_quote!(T));
293 generics.params.push(parse_quote!(U));
294 let where_clause = generics.make_where_clause();
295 where_clause.predicates.push(parse_quote!(T: figen::BindPath));
296 where_clause
297 .predicates
298 .push(parse_quote!(U: figen::loader::PropertyLoader));
299
300 let (impl_generics, _, where_clause) = generics.split_for_impl();
301 let (_, ty_generics, _) = input.generics.split_for_impl();
302
303 quote!(
304 impl #impl_generics figen::binder::ConfigInitializer<T, U> for #ident #ty_generics
305 #where_clause
306 {
307 #[cold]
308 #[inline(never)]
309 fn initialize(path: &mut T, loader: &U) -> figen::Result<Self> {
310 let key = path.current_path();
311 let value: figen::str_ty!() = loader.load_str_value(key)?;
312 value
313 .as_str()
314 .try_into()
315 .map_err(|_| figen::error::Error::ParseError)
316 }
317 }
318
319 impl #impl_generics figen::binder::ConfigBinder<T, U> for #ident #ty_generics
320 #where_clause
321 {
322 #[cold]
323 #[inline(never)]
324 fn bind(&mut self, path: &mut T, loader: &U) -> figen::Result<()> {
325 *self = <Self as figen::binder::ConfigInitializer<T, U>>::initialize(path, loader)?;
326 Ok(())
327 }
328 }
329 )
330 .into()
331}