1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use syn::Data;
5use syn::DeriveInput;
6use syn::Fields;
7use syn::Lit;
8use syn::Meta;
9use syn::Token;
10use syn::parse::Parse;
11use syn::parse::ParseStream;
12use syn::punctuated::Punctuated;
13use syn::spanned::Spanned;
14
15#[proc_macro_derive(Settings, attributes(settings, setting))]
16pub fn derive_settings(input: TokenStream) -> TokenStream {
17 match derive_settings_impl(input.into()) {
18 Ok(tokens) => tokens.into(),
19 Err(err) => err.to_compile_error().into(),
20 }
21}
22
23#[proc_macro_derive(Validate, attributes(settings, setting))]
24pub fn derive_validate(input: TokenStream) -> TokenStream {
25 match derive_validate_impl(input.into()) {
26 Ok(tokens) => tokens.into(),
27 Err(err) => err.to_compile_error().into(),
28 }
29}
30
31#[proc_macro_derive(ConfigDisplay, attributes(settings, setting))]
32pub fn derive_config_display(input: TokenStream) -> TokenStream {
33 match derive_config_display_impl(input.into()) {
34 Ok(tokens) => tokens.into(),
35 Err(err) => err.to_compile_error().into(),
36 }
37}
38
39struct StructAttrs {
40 prefix: Option<String>,
41 resolve_with: Option<syn::Path>,
42}
43
44struct FieldAttrs {
45 envs: Vec<String>,
46 envs_override: bool,
47 default: Option<Lit>,
48 default_str: Option<String>,
49 use_default: bool,
50 resolve_with: Option<syn::Path>,
51 nested: bool,
52 skip: bool,
53 sensitive: bool,
54 override_prefix: Option<Option<String>>,
55}
56
57struct BracketedStrings {
58 values: Vec<String>,
59}
60
61impl Parse for BracketedStrings {
62 fn parse(input: ParseStream) -> syn::Result<Self> {
63 let content;
64 syn::bracketed!(content in input);
65 let lits: Punctuated<syn::LitStr, Token![,]> = content.parse_terminated(|input| input.parse::<syn::LitStr>(), Token![,])?;
66 Ok(Self {
67 values: lits.iter().map(syn::LitStr::value).collect(),
68 })
69 }
70}
71
72fn parse_struct_attrs(input: &DeriveInput) -> syn::Result<StructAttrs> {
73 let mut prefix = None;
74 let mut resolve_with = None;
75 for attr in &input.attrs {
76 if !attr.path().is_ident("settings") {
77 continue;
78 }
79 attr.parse_nested_meta(|meta| {
80 if meta.path.is_ident("prefix") {
81 let value = meta.value()?;
82 let lit: syn::LitStr = value.parse()?;
83 prefix = Some(lit.value());
84 return Ok(());
85 }
86 if meta.path.is_ident("resolve_with") {
87 let value = meta.value()?;
88 let lit: syn::LitStr = value.parse()?;
89 let path: syn::Path = lit.parse()?;
90 resolve_with = Some(path);
91 return Ok(());
92 }
93 Err(meta.error("unknown settings attribute"))
94 })?;
95 }
96 Ok(StructAttrs { prefix, resolve_with })
97}
98
99fn parse_env_list(meta: &syn::meta::ParseNestedMeta<'_>) -> syn::Result<Vec<String>> {
100 let value = meta.value()?;
101 if value.peek(syn::token::Bracket) {
102 let parsed: BracketedStrings = value.parse()?;
103 Ok(parsed.values)
104 } else {
105 let lit: syn::LitStr = value.parse()?;
106 Ok(vec![lit.value()])
107 }
108}
109
110fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
111 let mut attrs = FieldAttrs {
112 envs: Vec::new(),
113 envs_override: false,
114 default: None,
115 default_str: None,
116 use_default: false,
117 resolve_with: None,
118 nested: false,
119 skip: false,
120 sensitive: false,
121 override_prefix: None,
122 };
123
124 for attr in &field.attrs {
125 if !attr.path().is_ident("setting") {
126 continue;
127 }
128
129 if let Meta::List(_) = &attr.meta {
130 attr.parse_nested_meta(|meta| {
131 if meta.path.is_ident("envs") {
132 attrs.envs = parse_env_list(&meta)?;
133 return Ok(());
134 }
135 if meta.path.is_ident("r#override") || meta.path.is_ident("override") {
136 attrs.envs_override = true;
137 return Ok(());
138 }
139 if meta.path.is_ident("default") {
140 if meta.input.peek(Token![=]) {
141 let value = meta.value()?;
142 let lit: Lit = value.parse()?;
143 attrs.default = Some(lit);
144 } else {
145 attrs.use_default = true;
146 }
147 return Ok(());
148 }
149 if meta.path.is_ident("default_str") {
150 let value = meta.value()?;
151 let lit: syn::LitStr = value.parse()?;
152 attrs.default_str = Some(lit.value());
153 return Ok(());
154 }
155 if meta.path.is_ident("resolve_with") {
156 let value = meta.value()?;
157 let lit: syn::LitStr = value.parse()?;
158 let path: syn::Path = lit.parse()?;
159 attrs.resolve_with = Some(path);
160 return Ok(());
161 }
162 if meta.path.is_ident("nested") {
163 attrs.nested = true;
164 return Ok(());
165 }
166 if meta.path.is_ident("skip") {
167 attrs.skip = true;
168 return Ok(());
169 }
170 if meta.path.is_ident("sensitive") {
171 attrs.sensitive = true;
172 return Ok(());
173 }
174 if meta.path.is_ident("override_prefix") {
175 if meta.input.peek(Token![=]) {
176 let value = meta.value()?;
177 let lit: syn::LitStr = value.parse()?;
178 attrs.override_prefix = Some(Some(lit.value()));
179 } else {
180 attrs.override_prefix = Some(None);
181 }
182 return Ok(());
183 }
184 Err(meta.error("unknown setting attribute"))
185 })?;
186 }
187 }
188
189 let span = field.ident.as_ref().map_or_else(|| field.span(), |ident| ident.span());
190
191 let has_any_default = attrs.default.is_some() || attrs.default_str.is_some() || attrs.use_default;
192 if (attrs.default.is_some() as u8 + attrs.default_str.is_some() as u8 + attrs.use_default as u8) > 1 {
193 return Err(syn::Error::new(span, "only one of default, default = value, or default_str allowed"));
194 }
195 if attrs.skip && (has_any_default || attrs.resolve_with.is_some() || !attrs.envs.is_empty() || attrs.envs_override || attrs.nested || attrs.sensitive) {
196 return Err(syn::Error::new(span, "skip cannot be combined with other setting attributes"));
197 }
198 if attrs.nested && (has_any_default || attrs.resolve_with.is_some() || !attrs.envs.is_empty() || attrs.envs_override || attrs.sensitive) {
199 return Err(syn::Error::new(span, "nested cannot be combined with default, default_str, resolve_with, envs, override, or sensitive"));
200 }
201 if attrs.override_prefix.is_some() && !attrs.nested {
202 return Err(syn::Error::new(span, "override_prefix requires nested"));
203 }
204
205 Ok(attrs)
206}
207
208fn field_name_to_env_key(name: &str) -> String {
209 name.to_uppercase()
210}
211
212fn build_key_list(prefix: &Option<String>, field_name: &str, attrs: &FieldAttrs) -> Vec<String> {
213 let mut keys = Vec::new();
214
215 let names = if attrs.envs.is_empty() { vec![field_name_to_env_key(field_name)] } else { attrs.envs.clone() };
216
217 for name in &names {
218 let key = if attrs.envs_override {
219 name.clone()
220 } else {
221 match prefix {
222 Some(pfx) => format!("{pfx}_{name}"),
223 None => name.clone(),
224 }
225 };
226 if !keys.contains(&key) {
227 keys.push(key);
228 }
229 }
230
231 keys
232}
233
234fn gen_resolve_with_call(keys_expr: TokenStream2, func: &syn::Path, attrs: &FieldAttrs) -> TokenStream2 {
235 if let Some(lit) = &attrs.default {
236 return quote! {
237 ::conflaguration::resolve_with_or(#keys_expr, #func, #lit)?
238 };
239 }
240
241 if attrs.use_default {
242 return quote! {
243 ::conflaguration::resolve_with_or(#keys_expr, #func, ::core::default::Default::default())?
244 };
245 }
246
247 if let Some(default_str) = &attrs.default_str {
248 return quote! {
249 ::conflaguration::resolve_with_or_str(#keys_expr, #func, #default_str)?
250 };
251 }
252
253 quote! {
254 ::conflaguration::resolve_with(#keys_expr, #func)?
255 }
256}
257
258fn gen_resolve_call(keys_expr: TokenStream2, attrs: &FieldAttrs) -> TokenStream2 {
259 if let Some(func) = &attrs.resolve_with {
260 return gen_resolve_with_call(keys_expr, func, attrs);
261 }
262
263 if let Some(lit) = &attrs.default {
264 if matches!(lit, Lit::Str(_)) {
265 let lit_str = match lit {
266 Lit::Str(strlit) => strlit.value(),
267 _ => unreachable!(),
268 };
269 return quote! {
270 ::conflaguration::resolve_or_parse(#keys_expr, #lit_str)?
271 };
272 }
273 return quote! {
274 ::conflaguration::resolve_or(#keys_expr, #lit)?
275 };
276 }
277
278 if attrs.use_default {
279 return quote! {
280 ::conflaguration::resolve_or_else(#keys_expr, || ::core::default::Default::default())?
281 };
282 }
283
284 if let Some(default_str) = &attrs.default_str {
285 return quote! {
286 ::conflaguration::resolve_or_parse(#keys_expr, #default_str)?
287 };
288 }
289
290 quote! {
291 ::conflaguration::resolve(#keys_expr)?
292 }
293}
294
295enum PrefixMode<'a> {
296 Static(&'a Option<String>),
297 Dynamic,
298}
299
300fn nested_prefix(field_type: &syn::Type, attrs: &FieldAttrs, prefix_mode: &PrefixMode<'_>) -> Option<TokenStream2> {
301 match &attrs.override_prefix {
302 Some(Some(explicit)) => Some(quote! { #explicit.to_owned() }),
303 Some(None) => {
304 let pfx = match prefix_mode {
305 PrefixMode::Static(Some(pfx)) => quote! { #pfx },
306 PrefixMode::Dynamic => quote! { __prefix },
307 PrefixMode::Static(None) => return None,
308 };
309 Some(quote! {
310 match <#field_type as ::conflaguration::Settings>::PREFIX {
311 Some(__inner) => ::std::format!("{}_{}", #pfx, __inner),
312 None => (#pfx).to_owned(),
313 }
314 })
315 }
316 None => None,
317 }
318}
319
320fn gen_nested_construct(field_type: &syn::Type, prefix: Option<TokenStream2>) -> TokenStream2 {
321 match prefix {
322 Some(pfx) => quote! {
323 { let __nested = #pfx; <#field_type as ::conflaguration::Settings>::from_env_with_prefix(&__nested)? }
324 },
325 None => quote! { <#field_type as ::conflaguration::Settings>::from_env()? },
326 }
327}
328
329fn gen_nested_override(field_name: &syn::Ident, prefix: Option<TokenStream2>) -> TokenStream2 {
330 match prefix {
331 Some(pfx) => quote! {
332 { let __nested = #pfx; ::conflaguration::Settings::override_from_env_with_prefix(&mut self.#field_name, &__nested)?; }
333 },
334 None => quote! { ::conflaguration::Settings::override_from_env(&mut self.#field_name)?; },
335 }
336}
337
338fn dynamic_key_tokens(field_name_str: &str, attrs: &FieldAttrs) -> (TokenStream2, TokenStream2) {
339 let names = if attrs.envs.is_empty() { vec![field_name_to_env_key(field_name_str)] } else { attrs.envs.clone() };
340 let names_ref = &names;
341 let keys_setup = if attrs.envs_override {
342 quote! { let __keys: Vec<String> = vec![#(#names_ref.to_string()),*]; }
343 } else {
344 quote! { let __keys: Vec<String> = vec![#(::std::format!("{}_{}", __prefix, #names_ref)),*]; }
345 };
346 let refs_setup = quote! { let __key_refs: Vec<&str> = __keys.iter().map(|s| s.as_str()).collect(); };
347 (keys_setup, refs_setup)
348}
349
350fn gen_override_guard(field_name: &syn::Ident, keys_ref: TokenStream2, resolve_with: Option<&syn::Path>) -> TokenStream2 {
351 let assign = match resolve_with {
352 Some(func) => quote! {
353 self.#field_name = ::conflaguration::resolve_with(#keys_ref, #func)?;
354 },
355 None => quote! {
356 self.#field_name = ::conflaguration::resolve(#keys_ref)?;
357 },
358 };
359 quote! {
360 if (#keys_ref).iter().any(|__k| ::std::env::var(__k).is_ok()) {
361 #assign
362 }
363 }
364}
365
366fn gen_construct_resolve(field_name_str: &str, attrs: &FieldAttrs, prefix_mode: &PrefixMode<'_>) -> TokenStream2 {
367 match prefix_mode {
368 PrefixMode::Static(prefix) => {
369 let keys = build_key_list(prefix, field_name_str, attrs);
370 let keys_ref = &keys;
371 gen_resolve_call(quote! { &[#(#keys_ref),*] }, attrs)
372 }
373 PrefixMode::Dynamic => {
374 let (keys_setup, refs_setup) = dynamic_key_tokens(field_name_str, attrs);
375 let resolve = gen_resolve_call(quote! { &__key_refs }, attrs);
376 quote! { { #keys_setup #refs_setup #resolve } }
377 }
378 }
379}
380
381fn gen_override_resolve(field_name: &syn::Ident, field_name_str: &str, attrs: &FieldAttrs, prefix_mode: &PrefixMode<'_>) -> TokenStream2 {
382 match prefix_mode {
383 PrefixMode::Static(prefix) => {
384 let keys = build_key_list(prefix, field_name_str, attrs);
385 let keys_ref = &keys;
386 let keys_expr = quote! { &[#(#keys_ref),*] };
387 let guard = gen_override_guard(field_name, quote! { __keys }, attrs.resolve_with.as_ref());
388 quote! { { let __keys: &[&str] = #keys_expr; #guard } }
389 }
390 PrefixMode::Dynamic => {
391 let (keys_setup, refs_setup) = dynamic_key_tokens(field_name_str, attrs);
392 let guard = gen_override_guard(field_name, quote! { &__key_refs }, attrs.resolve_with.as_ref());
393 quote! { { #keys_setup #refs_setup #guard } }
394 }
395 }
396}
397
398fn gen_field_construct(field: &syn::Field, prefix_mode: &PrefixMode<'_>, struct_attrs: &StructAttrs) -> syn::Result<TokenStream2> {
399 let field_name = field
400 .ident
401 .as_ref()
402 .ok_or_else(|| syn::Error::new(field.span(), "tuple struct fields not supported"))?;
403 let mut attrs = parse_field_attrs(field)?;
404
405 if attrs.resolve_with.is_none() && attrs.default.is_none() && !attrs.use_default {
406 attrs.resolve_with.clone_from(&struct_attrs.resolve_with);
407 }
408
409 if attrs.skip {
410 return Ok(quote! { ::core::default::Default::default() });
411 }
412 if attrs.nested {
413 let prefix = nested_prefix(&field.ty, &attrs, prefix_mode);
414 return Ok(gen_nested_construct(&field.ty, prefix));
415 }
416 Ok(gen_construct_resolve(&field_name.to_string(), &attrs, prefix_mode))
417}
418
419fn gen_field_override(field: &syn::Field, prefix_mode: &PrefixMode<'_>, struct_attrs: &StructAttrs) -> syn::Result<TokenStream2> {
420 let field_name = field
421 .ident
422 .as_ref()
423 .ok_or_else(|| syn::Error::new(field.span(), "tuple struct fields not supported"))?;
424 let mut attrs = parse_field_attrs(field)?;
425
426 if attrs.resolve_with.is_none() && attrs.default.is_none() && !attrs.use_default {
427 attrs.resolve_with.clone_from(&struct_attrs.resolve_with);
428 }
429
430 if attrs.skip {
431 return Ok(quote! {});
432 }
433 if attrs.nested {
434 let prefix = nested_prefix(&field.ty, &attrs, prefix_mode);
435 return Ok(gen_nested_override(field_name, prefix));
436 }
437 Ok(gen_override_resolve(field_name, &field_name.to_string(), &attrs, prefix_mode))
438}
439
440fn derive_settings_impl(input: TokenStream2) -> syn::Result<TokenStream2> {
441 let input: DeriveInput = syn::parse2(input)?;
442 let struct_attrs = parse_struct_attrs(&input)?;
443
444 let fields = match &input.data {
445 Data::Struct(data) => match &data.fields {
446 Fields::Named(named) => &named.named,
447 _ => return Err(syn::Error::new(input.ident.span(), "only named struct fields supported")),
448 },
449 _ => return Err(syn::Error::new(input.ident.span(), "Settings can only be derived on structs")),
450 };
451
452 let static_prefix = PrefixMode::Static(&struct_attrs.prefix);
453 let dynamic_prefix = PrefixMode::Dynamic;
454
455 let mut static_exprs = Vec::new();
456 let mut dynamic_exprs = Vec::new();
457 let mut override_static_stmts = Vec::new();
458 let mut override_dynamic_stmts = Vec::new();
459 for field in fields {
460 let field_name = field
461 .ident
462 .as_ref()
463 .ok_or_else(|| syn::Error::new(field.span(), "tuple struct fields not supported"))?;
464 let static_expr = gen_field_construct(field, &static_prefix, &struct_attrs)?;
465 let dynamic_expr = gen_field_construct(field, &dynamic_prefix, &struct_attrs)?;
466 let override_static = gen_field_override(field, &static_prefix, &struct_attrs)?;
467 let override_dynamic = gen_field_override(field, &dynamic_prefix, &struct_attrs)?;
468 static_exprs.push(quote! { #field_name: #static_expr });
469 dynamic_exprs.push(quote! { #field_name: #dynamic_expr });
470 override_static_stmts.push(override_static);
471 override_dynamic_stmts.push(override_dynamic);
472 }
473
474 let struct_name = &input.ident;
475 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
476
477 let prefix_const = match &struct_attrs.prefix {
478 Some(pfx) => quote! { const PREFIX: Option<&'static str> = Some(#pfx); },
479 None => quote! { const PREFIX: Option<&'static str> = None; },
480 };
481
482 Ok(quote! {
483 impl #impl_generics ::conflaguration::Settings for #struct_name #type_generics #where_clause {
484 #prefix_const
485
486 fn from_env() -> ::conflaguration::Result<Self> {
487 Ok(Self {
488 #(#static_exprs),*
489 })
490 }
491
492 fn from_env_with_prefix(__prefix: &str) -> ::conflaguration::Result<Self> {
493 Ok(Self {
494 #(#dynamic_exprs),*
495 })
496 }
497
498 fn override_from_env(&mut self) -> ::conflaguration::Result<()> {
499 #(#override_static_stmts)*
500 Ok(())
501 }
502
503 fn override_from_env_with_prefix(&mut self, __prefix: &str) -> ::conflaguration::Result<()> {
504 #(#override_dynamic_stmts)*
505 Ok(())
506 }
507 }
508 })
509}
510
511fn derive_validate_impl(input: TokenStream2) -> syn::Result<TokenStream2> {
512 let input: DeriveInput = syn::parse2(input)?;
513
514 let fields = match &input.data {
515 Data::Struct(data) => match &data.fields {
516 Fields::Named(named) => &named.named,
517 _ => return Err(syn::Error::new(input.ident.span(), "only named struct fields supported")),
518 },
519 _ => return Err(syn::Error::new(input.ident.span(), "Validate can only be derived on structs")),
520 };
521
522 let mut validate_calls = Vec::new();
523 for field in fields {
524 let field_name = field
525 .ident
526 .as_ref()
527 .ok_or_else(|| syn::Error::new(field.span(), "tuple struct fields not supported"))?;
528 let field_name_str = field_name.to_string();
529 let attrs = parse_field_attrs(field)?;
530
531 if attrs.nested {
532 validate_calls.push(quote! {
533 if let Err(__err) = ::conflaguration::Validate::validate(&self.#field_name) {
534 match __err {
535 ::conflaguration::Error::Validation { errors: __inner } => {
536 for mut __ve in __inner {
537 __ve.prepend_path(#field_name_str);
538 __errors.push(__ve);
539 }
540 }
541 __other => return Err(__other),
542 }
543 }
544 });
545 }
546 }
547
548 let struct_name = &input.ident;
549 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
550
551 if validate_calls.is_empty() {
552 return Ok(quote! {
553 impl #impl_generics ::conflaguration::Validate for #struct_name #type_generics #where_clause {
554 fn validate(&self) -> ::conflaguration::Result<()> {
555 Ok(())
556 }
557 }
558 });
559 }
560
561 Ok(quote! {
562 impl #impl_generics ::conflaguration::Validate for #struct_name #type_generics #where_clause {
563 fn validate(&self) -> ::conflaguration::Result<()> {
564 let mut __errors: Vec<::conflaguration::ValidationMessage> = vec![];
565 #(#validate_calls)*
566 if __errors.is_empty() {
567 Ok(())
568 } else {
569 Err(::conflaguration::Error::Validation { errors: __errors })
570 }
571 }
572 }
573 })
574}
575
576fn gen_display_skip(field_name_str: &str, field_name: &syn::Ident) -> TokenStream2 {
577 quote! { ::std::writeln!(__f, "{}{} = {:?} (skipped)", __indent, #field_name_str, self.#field_name)?; }
578}
579
580fn gen_display_nested_static(field_name_str: &str, field_name: &syn::Ident) -> TokenStream2 {
581 quote! {
582 ::std::writeln!(__f, "{}{}:", __indent, #field_name_str)?;
583 ::conflaguration::ConfigDisplay::fmt_config(&self.#field_name, __f, __depth + 1)?;
584 }
585}
586
587fn gen_display_nested_dynamic(field_name_str: &str, field_name: &syn::Ident, field_type: &syn::Type, attrs: &FieldAttrs) -> TokenStream2 {
588 match &attrs.override_prefix {
589 Some(Some(explicit)) => quote! {
590 ::std::writeln!(__f, "{}{}:", __indent, #field_name_str)?;
591 ::conflaguration::ConfigDisplay::fmt_config_with_prefix(&self.#field_name, __f, __depth + 1, #explicit)?;
592 },
593 Some(None) => quote! {
594 ::std::writeln!(__f, "{}{}:", __indent, #field_name_str)?;
595 {
596 let __nested_pfx = match <#field_type as ::conflaguration::Settings>::PREFIX {
597 Some(__inner) => ::std::format!("{}_{}", __prefix, __inner),
598 None => __prefix.to_string(),
599 };
600 ::conflaguration::ConfigDisplay::fmt_config_with_prefix(&self.#field_name, __f, __depth + 1, &__nested_pfx)?;
601 }
602 },
603 None => quote! {
604 ::std::writeln!(__f, "{}{}:", __indent, #field_name_str)?;
605 ::conflaguration::ConfigDisplay::fmt_config(&self.#field_name, __f, __depth + 1)?;
606 },
607 }
608}
609
610fn gen_display_value(field_name_str: &str, field_name: &syn::Ident, attrs: &FieldAttrs, keys_display_expr: TokenStream2) -> TokenStream2 {
611 if attrs.sensitive {
612 quote! { ::std::writeln!(__f, "{}{} = *** ({})", __indent, #field_name_str, #keys_display_expr)?; }
613 } else {
614 quote! { ::std::writeln!(__f, "{}{} = {:?} ({})", __indent, #field_name_str, self.#field_name, #keys_display_expr)?; }
615 }
616}
617
618fn derive_config_display_impl(input: TokenStream2) -> syn::Result<TokenStream2> {
619 let input: DeriveInput = syn::parse2(input)?;
620 let struct_attrs = parse_struct_attrs(&input)?;
621
622 let fields = match &input.data {
623 Data::Struct(data) => match &data.fields {
624 Fields::Named(named) => &named.named,
625 _ => return Err(syn::Error::new(input.ident.span(), "only named struct fields supported")),
626 },
627 _ => return Err(syn::Error::new(input.ident.span(), "ConfigDisplay can only be derived on structs")),
628 };
629
630 let mut static_lines = Vec::new();
631 let mut dynamic_lines = Vec::new();
632
633 for field in fields {
634 let field_name = field
635 .ident
636 .as_ref()
637 .ok_or_else(|| syn::Error::new(field.span(), "tuple struct fields not supported"))?;
638 let field_name_str = field_name.to_string();
639 let attrs = parse_field_attrs(field)?;
640
641 let static_keys = build_key_list(&struct_attrs.prefix, &field_name_str, &attrs);
642 let static_keys_display = static_keys.join(", ");
643 static_lines.push(if attrs.skip {
644 gen_display_skip(&field_name_str, field_name)
645 } else if attrs.nested {
646 gen_display_nested_static(&field_name_str, field_name)
647 } else {
648 gen_display_value(&field_name_str, field_name, &attrs, quote! { #static_keys_display })
649 });
650
651 let names = if attrs.envs.is_empty() {
652 vec![field_name_to_env_key(&field_name_str)]
653 } else {
654 attrs.envs.clone()
655 };
656 let names_ref = &names;
657 let dynamic_keys_expr = if attrs.envs_override {
658 let joined = names.join(", ");
659 quote! { #joined }
660 } else {
661 quote! {
662 {
663 let __keys: Vec<String> = vec![#(::std::format!("{}_{}", __prefix, #names_ref)),*];
664 __keys.join(", ")
665 }
666 }
667 };
668 dynamic_lines.push(if attrs.skip {
669 gen_display_skip(&field_name_str, field_name)
670 } else if attrs.nested {
671 gen_display_nested_dynamic(&field_name_str, field_name, &field.ty, &attrs)
672 } else {
673 gen_display_value(&field_name_str, field_name, &attrs, dynamic_keys_expr)
674 });
675 }
676
677 let struct_name = &input.ident;
678 let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
679
680 Ok(quote! {
681 impl #impl_generics ::conflaguration::ConfigDisplay for #struct_name #type_generics #where_clause {
682 fn fmt_config(&self, __f: &mut ::std::fmt::Formatter<'_>, __depth: usize) -> ::std::fmt::Result {
683 let __indent = " ".repeat(__depth);
684 #(#static_lines)*
685 Ok(())
686 }
687
688 fn fmt_config_with_prefix(&self, __f: &mut ::std::fmt::Formatter<'_>, __depth: usize, __prefix: &str) -> ::std::fmt::Result {
689 let __indent = " ".repeat(__depth);
690 #(#dynamic_lines)*
691 Ok(())
692 }
693 }
694
695 impl #impl_generics ::std::fmt::Display for #struct_name #type_generics #where_clause {
696 fn fmt(&self, __f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
697 ::conflaguration::ConfigDisplay::fmt_config(self, __f, 0)
698 }
699 }
700 })
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn settings_rejects_enum() {
709 let input: TokenStream2 = quote! { enum Foo { A, B } };
710 let result = derive_settings_impl(input);
711 let err = result.unwrap_err();
712 assert!(err.to_string().contains("structs"));
713 }
714
715 #[test]
716 fn settings_rejects_tuple_struct() {
717 let input: TokenStream2 = quote! { struct Foo(u16); };
718 let result = derive_settings_impl(input);
719 let err = result.unwrap_err();
720 assert!(err.to_string().contains("named"));
721 }
722
723 #[test]
724 fn validate_rejects_enum() {
725 let input: TokenStream2 = quote! { enum Bar { X } };
726 let result = derive_validate_impl(input);
727 let err = result.unwrap_err();
728 assert!(err.to_string().contains("structs"));
729 }
730
731 #[test]
732 fn validate_rejects_tuple_struct() {
733 let input: TokenStream2 = quote! { struct Bar(String); };
734 let result = derive_validate_impl(input);
735 let err = result.unwrap_err();
736 assert!(err.to_string().contains("named"));
737 }
738
739 #[test]
740 fn config_display_rejects_enum() {
741 let input: TokenStream2 = quote! { enum Baz { Y } };
742 let result = derive_config_display_impl(input);
743 let err = result.unwrap_err();
744 assert!(err.to_string().contains("structs"));
745 }
746
747 #[test]
748 fn unknown_settings_attribute_errors() {
749 let input: TokenStream2 = quote! {
750 #[settings(bogus = "nope")]
751 struct Bad {
752 field: u16,
753 }
754 };
755 let result = derive_settings_impl(input);
756 assert!(result.is_err());
757 }
758
759 #[test]
760 fn unknown_setting_field_attribute_errors() {
761 let input: TokenStream2 = quote! {
762 struct Bad {
763 #[setting(bogus)]
764 field: u16,
765 }
766 };
767 let result = derive_settings_impl(input);
768 assert!(result.is_err());
769 }
770}