derive_from_env_proc/
lib.rs1extern 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}