envstruct_derive/
lib.rs

1mod default_attr;
2mod normalize_type_path;
3
4use darling::{ast, FromDeriveInput, FromField};
5use default_attr::*;
6use normalize_type_path::*;
7use proc_macro::TokenStream;
8use quote::*;
9use syn::spanned::Spanned;
10
11/// Derives the `EnvStruct` trait for a struct or enum.
12#[proc_macro_derive(EnvStruct, attributes(env))]
13pub fn derive(input: TokenStream) -> TokenStream {
14    let derive_input: syn::DeriveInput = syn::parse(input).expect("Failed to parse derive input");
15    let receiver = EnvStructInputReceiver::from_derive_input(&derive_input)
16        .expect("Failed to parse input for darling receiver");
17    quote!(#receiver).into()
18}
19
20/// Receiver for the `EnvStruct` derive input.
21#[derive(Debug, FromDeriveInput)]
22#[darling(attributes(EnvStruct), supports(any))]
23struct EnvStructInputReceiver {
24    ident: syn::Ident,
25    generics: syn::Generics,
26    data: ast::Data<(), EnvStructFieldReceiver>,
27}
28
29/// Receiver for the fields of the `EnvStruct`.
30#[derive(Debug, FromField)]
31#[darling(attributes(env))]
32struct EnvStructFieldReceiver {
33    ident: Option<syn::Ident>,
34    ty: syn::Type,
35    name: Option<String>,
36    default: Option<DefaultAttr>,
37    with: Option<syn::Expr>,
38    #[darling(default)]
39    flatten: bool,
40    #[darling(default)]
41    skip: bool,
42}
43
44impl EnvStructFieldReceiver {
45    /// Generates a token stream for the field name or index.
46    pub fn name_exr(&self, index: usize) -> proc_macro2::TokenStream {
47        self.ident
48            .as_ref()
49            .map(quote::ToTokens::to_token_stream)
50            .unwrap_or_else(|| {
51                let index = syn::Index::from(index);
52                quote!(#index)
53            })
54    }
55
56    /// Generates a token stream for the field type.
57    pub fn type_expr(&self) -> proc_macro2::TokenStream {
58        self.with
59            .as_ref()
60            .map(|ty| quote_spanned! { ty.span() => #ty })
61            .unwrap_or({
62                let ty = normalize_type_path(&self.ty);
63                quote_spanned! { ty.span() => #ty }
64            })
65    }
66
67    /// Generates a token stream for the default value of the field.
68    pub fn default_expr(&self) -> proc_macro2::TokenStream {
69        self.default
70            .as_ref()
71            .map(|default| match default {
72                DefaultAttr::String(str) => {
73                    quote!(Some(#str))
74                }
75                DefaultAttr::Type(typ) => {
76                    quote!(Some(&#typ.to_string()))
77                }
78                DefaultAttr::Default => {
79                    let ty = normalize_type_path(&self.ty);
80                    quote!(Some(&#ty::default().to_string()))
81                }
82            })
83            .unwrap_or_else(|| quote!(None))
84    }
85
86    /// Generates a token stream for the environment variable name.
87    pub fn var_name_expr(&self) -> proc_macro2::TokenStream {
88        let var_name = self.name.clone().unwrap_or_else(|| {
89            self.ident
90                .as_ref()
91                .map(|v| quote!(#v).to_string())
92                .unwrap_or_default()
93        });
94
95        if self.flatten {
96            quote!(&prefix)
97        } else {
98            quote!(::envstruct::concat_env_name(&prefix, #var_name))
99        }
100    }
101}
102
103impl ToTokens for EnvStructInputReceiver {
104    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
105        let EnvStructInputReceiver {
106            ident,
107            generics,
108            data,
109        } = self;
110        let (imp, ty, where_clause) = generics.split_for_impl();
111
112        let impl_block = match data {
113            ast::Data::Enum(_) => {
114                quote_spanned! {ty.span() =>
115                    impl #imp ::envstruct::EnvParsePrimitive for #ident #ty #where_clause {
116                        fn parse(val: &str) -> std::result::Result<Self, ::envstruct::BoxError> {
117                            Ok(val.parse::<#ident>()?)
118                        }
119                    }
120                }
121            }
122            ast::Data::Struct(fields) => {
123                let field_exprs: Vec<_> = fields
124                    .iter()
125                    .enumerate()
126                    .map(|(index, field)| {
127                        let field_name = field.name_exr(index);
128                        let field_type = field.type_expr();
129                        let var_default = field.default_expr();
130                        let var_name_expr = field.var_name_expr();
131
132                        if field.skip {
133                             quote_spanned! {field.ty.span() =>
134                                #field_name: Default::default()
135                            }
136                        } else {
137                            quote_spanned! {field.ty.span() =>
138                                #field_name: #field_type::parse_from_env_var(#var_name_expr, #var_default)?.into()
139                            }
140                        }
141
142                    })
143                    .collect();
144
145                let inspect_exprs: Vec<_> = fields
146                    .iter()
147                    .filter(|field| !field.skip)
148                    .map(|field| {
149                        let field_type = field.type_expr();
150                        let var_default = field.default_expr();
151                        let var_name_expr = field.var_name_expr();
152
153                        quote_spanned! {field.ty.span() =>
154                            #field_type::get_env_entries(#var_name_expr, #var_default)?
155                        }
156                    })
157                    .collect();
158
159                quote! {
160                    #[allow(clippy::useless_conversion)]
161                    impl #imp ::envstruct::EnvParseNested for #ident #ty #where_clause {
162                        fn parse_from_env_var(prefix: impl AsRef<str>, default: Option<&str>) -> std::result::Result<Self, ::envstruct::EnvStructError> {
163                            Ok(Self {
164                                #( #field_exprs, )*
165                            })
166                        }
167
168                        fn get_env_entries(prefix: impl AsRef<str>, default: Option<&str>) -> std::result::Result<Vec<::envstruct::EnvEntry>, ::envstruct::EnvStructError> {
169                            Ok(vec![#( #inspect_exprs, )*].into_iter().flatten().collect())
170                        }
171                    }
172                }
173            }
174        };
175
176        tokens.extend(impl_block);
177    }
178}