derive_from_env_proc/
lib.rs

1extern crate proc_macro;
2
3use darling::FromField;
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{Data, DeriveInput, GenericArgument, PathArguments, Type};
7
8#[derive(FromField)]
9#[darling(attributes(from_env))]
10struct EnvField {
11    ident: Option<syn::Ident>,
12    ty: syn::Type,
13    #[darling(default)]
14    default: Option<syn::Lit>,
15    #[darling(default)]
16    no_prefix: bool,
17    #[darling(default)]
18    var: Option<syn::Lit>,
19    #[darling(default)]
20    from_str: bool,
21}
22
23#[proc_macro_derive(FromEnv, attributes(from_env))]
24pub fn from_env_proc_macro(item: TokenStream) -> TokenStream {
25    let DeriveInput { ident, data, .. } = syn::parse_macro_input!(item as syn::DeriveInput);
26    let struct_identifier = &ident;
27
28    match &data {
29        Data::Struct(syn::DataStruct { fields, .. }) => {
30            let env_fields = fields
31                .iter()
32                .map(|field| EnvField::from_field(field).unwrap())
33                .collect::<Vec<_>>();
34            let field_identifiers = env_fields
35                .iter()
36                .map(|f| f.ident.as_ref().unwrap())
37                .collect::<Vec<_>>();
38            let field_loaders = env_fields
39                .iter()
40                .map(|field| generate_field_loader(field, false))
41                .collect::<Vec<_>>();
42            let field_loaders_with_prefix = env_fields
43                .iter()
44                .map(|field| generate_field_loader(field, true))
45                .collect::<Vec<_>>();
46
47            quote! {
48                impl ::derive_from_env::_inner_trait::FromEnv for #struct_identifier {
49                    fn from_env() -> Result<Self, ::derive_from_env::FromEnvError> {
50                        use std::str::FromStr;
51                        Ok(Self {
52                            #(
53                                #field_identifiers: #field_loaders
54                            ),*
55                        })
56                    }
57                    fn from_env_with_prefix(prefix: &str) -> Result<Self, ::derive_from_env::FromEnvError> {
58                        use std::str::FromStr;
59                        Ok(Self {
60                            #(
61                                #field_identifiers: #field_loaders_with_prefix
62                            ),*
63                        })
64                    }
65                }
66                impl #struct_identifier {
67                    pub fn from_env() -> Result<Self, ::derive_from_env::FromEnvError> {
68                        <Self as ::derive_from_env::_inner_trait::FromEnv>::from_env()
69                    }
70                    pub fn from_env_with_prefix(prefix: &str) -> Result<Self, ::derive_from_env::FromEnvError> {
71                        <Self as ::derive_from_env::_inner_trait::FromEnv>::from_env_with_prefix(prefix)
72                    }
73                }
74            }.into()
75        }
76        _ => unimplemented!(),
77    }
78}
79
80fn impl_from_str(ty: &Type) -> bool {
81    matches!(ty,
82        Type::Path(type_path) if type_path.path.segments.iter().all(|seg|
83            matches!(seg.ident.to_string().as_str(),
84                "i8" | "i16" | "i32" | "i64" | "i128" |
85                "u8" | "u16" | "u32" | "u64" | "u128" |
86                "f32" | "f64" | "bool" | "char" | "usize" |
87                "isize" | "String" | "IpAddr" | "SocketAddr" |
88                "PathBuf" | "IpV4Addr" | "IpV6Addr"
89            )
90        )
91    )
92}
93
94fn extract_inner_type_if_option(ty: &Type) -> Option<&Type> {
95    if let Type::Path(type_path) = ty {
96        if type_path.qself.is_none() && type_path.path.segments.len() == 1 {
97            let segment = &type_path.path.segments[0];
98            if segment.ident == "Option" {
99                if let PathArguments::AngleBracketed(ref args) = segment.arguments {
100                    if args.args.len() == 1 {
101                        if let GenericArgument::Type(ref inner_type) = args.args[0] {
102                            return Some(inner_type);
103                        }
104                    }
105                }
106            }
107        }
108    }
109    None
110}
111
112fn generate_field_loader(field: &EnvField, prefix: bool) -> proc_macro2::TokenStream {
113    let field_name = field.ident.as_ref().unwrap().to_string();
114    let field_type = &field.ty;
115    let inner_field_type = extract_inner_type_if_option(field_type);
116    let default_value = &field.default;
117    let no_prefix = field.no_prefix;
118    let from_str = field.from_str;
119    let var_name = &field.var;
120
121    let env_var_name = if prefix {
122        quote! { format!("{}_{}", prefix, #field_name.to_uppercase()) }
123    } else {
124        quote! { #field_name.to_uppercase() }
125    };
126    if let Some(field_type) = inner_field_type {
127        if !(impl_from_str(field_type) || from_str) {
128            panic!("Inner type of Option must implement FromStr");
129        }
130        if default_value.is_some() {
131            panic!("Default value is not supported for Option fields");
132        }
133        if let Some(var_name) = var_name {
134            quote! {
135                std::env::var(#var_name.to_string())
136                    .ok()
137                    .map(|s| #field_type::from_str(&s))
138                    .transpose()
139                    .map_err(|_| ::derive_from_env::FromEnvError::ParsingFailure{
140                        var_name: #var_name.to_string(),
141                        str_value: std::env::var(#var_name.to_string()).unwrap(),
142                        expected_type: stringify!(#field_type).to_string()
143                    })?
144            }
145        } else {
146            quote! {
147                std::env::var(#env_var_name)
148                    .ok()
149                    .map(|s| #field_type::from_str(&s))
150                    .transpose()
151                    .map_err(|_| ::derive_from_env::FromEnvError::ParsingFailure{
152                        var_name: #env_var_name.to_string(),
153                        str_value: std::env::var(#env_var_name).unwrap(),
154                        expected_type: stringify!(#field_type).to_string()
155                    })?
156            }
157        }
158    } else if impl_from_str(field_type) || from_str || inner_field_type.is_some() {
159        match (default_value, var_name) {
160            (Some(default), Some(var_name)) => {
161                quote! {
162                    #field_type::from_str(
163                        &std::env::var(#var_name.to_string())
164                            .unwrap_or_else(|_| #default.to_string())
165                    ).map_err(|_| ::derive_from_env::FromEnvError::ParsingFailure{
166                        var_name: #var_name.to_string(),
167                        str_value: std::env::var(#var_name.to_string()).unwrap_or_else(|_| #default.to_string()),
168                        expected_type: stringify!(#field_type).to_string()
169                    })?
170                }
171            }
172            (Some(default), None) => {
173                quote! {
174                    #field_type::from_str(
175                        &std::env::var(#env_var_name)
176                            .unwrap_or_else(|_| #default.to_string())
177                    ).map_err(|_|::derive_from_env::FromEnvError::ParsingFailure {
178                        var_name: #env_var_name.to_string(),
179                        str_value: std::env::var(#env_var_name).unwrap_or_else(|_| #default.to_string()),
180                        expected_type: stringify!(#field_type).to_string()
181                    })?
182                }
183            }
184            (None, Some(var_name)) => {
185                quote! {
186                    #field_type::from_str(&std::env::var(#var_name.to_string())
187                        .map_err(|_| ::derive_from_env::FromEnvError::MissingEnvVar{var_name: #var_name.to_string()})?)
188                        .map_err(|_| ::derive_from_env::FromEnvError::ParsingFailure{
189                            var_name: #var_name.to_string(),
190                            str_value: std::env::var(#var_name.to_string()).unwrap(),
191                            expected_type: stringify!(#field_type).to_string()
192                        })?
193                }
194            }
195            (None, None) => {
196                quote! {
197                    #field_type::from_str(&std::env::var(#env_var_name)
198                        .map_err(|_| ::derive_from_env::FromEnvError::MissingEnvVar{var_name: #env_var_name.to_string()})?)
199                        .map_err(|_| ::derive_from_env::FromEnvError::ParsingFailure{
200                            var_name: #env_var_name.to_string(),
201                            str_value: std::env::var(#env_var_name).unwrap(),
202                            expected_type: stringify!(#field_type).to_string()
203                        })?
204                }
205            }
206        }
207    } else {
208        if default_value.is_some() {
209            panic!("Default value is not supported for structs");
210        }
211        if var_name.is_some() {
212            panic!("Variable name specification is not suited for structured fields")
213        }
214        if no_prefix {
215            quote! {
216                <#field_type as ::derive_from_env::_inner_trait::FromEnv>::from_env()?
217            }
218        } else {
219            quote! {
220                <#field_type as ::derive_from_env::_inner_trait::FromEnv>::from_env_with_prefix(&#env_var_name)?
221            }
222        }
223    }
224}