1#![forbid(unsafe_code)]
101
102use darling::FromDeriveInput;
103use proc_macro::TokenStream;
104use proc_macro2::TokenStream as TokenStream2;
105use quote::quote;
106use syn::{parse_macro_input, Data, DeriveInput, Ident, Type};
107
108mod codegen;
109mod parse;
110
111use codegen::{
112 generate_clap_impl, generate_defaults_impl, generate_load_impl, generate_migration_impl,
113 generate_modules_impl, generate_schema_impl, generate_validate_impl,
114};
115use darling::FromField;
116use parse::{FieldAttrs, StructAttrs};
117
118#[proc_macro_derive(Config, attributes(config))]
166pub fn config_derive(input: TokenStream) -> TokenStream {
167 let input = parse_macro_input!(input as DeriveInput);
168
169 match impl_config_derive(&input) {
170 Ok(tokens) => tokens.into(),
171 Err(err) => err.to_compile_error().into(),
172 }
173}
174
175#[proc_macro_derive(ConfigSchema, attributes(config))]
177pub fn config_schema_derive(input: TokenStream) -> TokenStream {
178 let input = parse_macro_input!(input as DeriveInput);
179
180 match impl_config_schema_derive(&input) {
181 Ok(tokens) => tokens.into(),
182 Err(err) => err.to_compile_error().into(),
183 }
184}
185
186#[proc_macro_derive(ConfigMigration, attributes(config))]
188pub fn config_migration_derive(input: TokenStream) -> TokenStream {
189 let input = parse_macro_input!(input as DeriveInput);
190
191 match impl_config_migration_derive(&input) {
192 Ok(tokens) => tokens.into(),
193 Err(err) => err.to_compile_error().into(),
194 }
195}
196
197#[proc_macro_derive(ConfigModules, attributes(config))]
199pub fn config_modules_derive(input: TokenStream) -> TokenStream {
200 let input = parse_macro_input!(input as DeriveInput);
201
202 match impl_config_modules_derive(&input) {
203 Ok(tokens) => tokens.into(),
204 Err(err) => err.to_compile_error().into(),
205 }
206}
207
208#[proc_macro_derive(ConfigClap, attributes(config))]
210pub fn config_clap_derive(input: TokenStream) -> TokenStream {
211 let input = parse_macro_input!(input as DeriveInput);
212
213 match impl_config_clap_derive(&input) {
214 Ok(tokens) => tokens.into(),
215 Err(err) => err.to_compile_error().into(),
216 }
217}
218
219fn impl_config_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
220 let struct_attrs = StructAttrs::from_derive_input(input)
222 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
223
224 struct_attrs
226 .validate(input)
227 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
228
229 let struct_ident = &input.ident;
231
232 let fields = match &input.data {
234 Data::Struct(data) => &data.fields,
235 _ => {
236 return Err(syn::Error::new_spanned(
237 input,
238 "Config can only be derived for named structs",
239 ))
240 }
241 };
242
243 let field_info: Vec<(&syn::Ident, &syn::Type, FieldAttrs)> = fields
245 .iter()
246 .filter_map(|field| {
247 let ident = field.ident.as_ref()?;
248 let attrs = FieldAttrs::from_field(field).ok()?;
249 Some((ident, &field.ty, attrs))
250 })
251 .collect();
252
253 for (ident, _ty, attrs) in &field_info {
255 attrs
256 .validate(
257 fields
258 .iter()
259 .find(|f| f.ident.as_ref() == Some(ident))
260 .expect("field must exist in parsed fields"),
261 )
262 .map_err(|e| syn::Error::new_spanned(ident, e.to_string()))?;
263 }
264
265 let defaults_impl = generate_defaults_impl(struct_ident, &field_info);
267 let load_impl = generate_load_impl(struct_ident, &struct_attrs, fields);
268 let validate_impl = generate_validate_impl(&struct_attrs, &field_info);
269 let sensitive_paths = generate_sensitive_paths(struct_ident, &field_info);
271 Ok(quote! {
272 #defaults_impl
273 #load_impl
274 #validate_impl
275 #sensitive_paths
276 })
277}
278
279fn generate_sensitive_paths(
283 struct_ident: &Ident,
284 fields: &[(&Ident, &Type, FieldAttrs)],
285) -> TokenStream2 {
286 use syn::LitStr;
287
288 let sensitive_paths: Vec<LitStr> = fields
289 .iter()
290 .filter(|(_, _, attrs)| attrs.sensitive || attrs.encrypt.is_some())
291 .map(|(ident, _, attrs)| {
292 let name = attrs.name.clone().unwrap_or_else(|| ident.to_string());
293 LitStr::new(&name, ident.span())
294 })
295 .collect();
296
297 if sensitive_paths.is_empty() {
298 return TokenStream2::new();
299 }
300
301 quote! {
302 impl #struct_ident {
303 fn sensitive_paths() -> &'static [&'static str] {
304 &[#(#sensitive_paths),*]
305 }
306 }
307 }
308}
309
310fn impl_config_schema_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
311 let struct_attrs = StructAttrs::from_derive_input(input)
312 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
313
314 let struct_ident = &input.ident;
315
316 let fields = match &input.data {
317 Data::Struct(data) => &data.fields,
318 _ => {
319 return Err(syn::Error::new_spanned(
320 input,
321 "ConfigSchema can only be derived for named structs",
322 ))
323 }
324 };
325
326 let schema_impl = generate_schema_impl(struct_ident, &struct_attrs, fields);
327
328 Ok(quote! {
329 #schema_impl
330 })
331}
332
333fn impl_config_migration_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
334 let struct_attrs = StructAttrs::from_derive_input(input)
335 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
336
337 let struct_ident = &input.ident;
338
339 let fields = match &input.data {
340 Data::Struct(data) => &data.fields,
341 _ => {
342 return Err(syn::Error::new_spanned(
343 input,
344 "ConfigMigration can only be derived for named structs",
345 ))
346 }
347 };
348
349 let migration_impl = generate_migration_impl(struct_ident, &struct_attrs, fields);
350
351 Ok(quote! {
352 #migration_impl
353 })
354}
355
356fn impl_config_modules_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
357 let struct_attrs = StructAttrs::from_derive_input(input)
358 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
359
360 let struct_ident = &input.ident;
361
362 let fields = match &input.data {
363 Data::Struct(data) => &data.fields,
364 _ => {
365 return Err(syn::Error::new_spanned(
366 input,
367 "ConfigModules can only be derived for named structs",
368 ))
369 }
370 };
371
372 let modules_impl = generate_modules_impl(struct_ident, &struct_attrs, fields);
373
374 Ok(quote! {
375 #modules_impl
376 })
377}
378
379fn impl_config_clap_derive(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
380 let struct_attrs = StructAttrs::from_derive_input(input)
381 .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
382
383 let struct_ident = &input.ident;
384
385 let fields = match &input.data {
386 Data::Struct(data) => &data.fields,
387 _ => {
388 return Err(syn::Error::new_spanned(
389 input,
390 "ConfigClap can only be derived for named structs",
391 ))
392 }
393 };
394
395 let clap_impl = generate_clap_impl(struct_ident, &struct_attrs, fields);
396
397 Ok(quote! {
398 #clap_impl
399 })
400}