init4_from_env_derive/
lib.rs

1use proc_macro::TokenStream as Ts;
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{parse_macro_input, DeriveInput};
5
6mod field;
7use field::Field;
8
9/// This macro generates an implementation of the `FromEnv` trait for a struct.
10/// See the documenetation in init4_bin_base for more details.
11#[proc_macro_derive(FromEnv, attributes(from_env))]
12pub fn derive(input: Ts) -> Ts {
13    let input = parse_macro_input!(input as DeriveInput);
14
15    if !matches!(input.data, syn::Data::Struct(_)) {
16        syn::Error::new(
17            input.ident.span(),
18            "FromEnv can only be derived for structs",
19        )
20        .to_compile_error();
21    };
22
23    let syn::Data::Struct(data) = &input.data else {
24        unreachable!()
25    };
26
27    let crate_name = input
28        .attrs
29        .iter()
30        .find(|attr| attr.path().is_ident("from_env"))
31        .and_then(|attr| attr.parse_args::<syn::Path>().ok())
32        .unwrap_or_else(|| syn::parse_str::<syn::Path>("::init4_bin_base").unwrap());
33
34    let tuple_like = matches!(data.fields, syn::Fields::Unnamed(_));
35
36    if matches!(data.fields, syn::Fields::Unit) {
37        syn::Error::new(
38            input.ident.span(),
39            "FromEnv can only be derived for structs with fields",
40        )
41        .to_compile_error();
42    }
43
44    let fields = match &data.fields {
45        syn::Fields::Named(fields) => fields.named.iter().map(Field::try_from),
46        syn::Fields::Unnamed(fields) => fields.unnamed.iter().map(Field::try_from),
47        syn::Fields::Unit => unreachable!(),
48    };
49
50    let fields = match fields.collect::<Result<Vec<_>, _>>() {
51        Ok(fields) => fields,
52        Err(err) => {
53            return err.to_compile_error().into();
54        }
55    };
56
57    let input = Input {
58        ident: input.ident.clone(),
59        fields,
60        crate_name,
61        tuple_like,
62    };
63
64    input.expand_mod().into()
65}
66
67struct Input {
68    ident: syn::Ident,
69
70    fields: Vec<Field>,
71
72    crate_name: syn::Path,
73
74    tuple_like: bool,
75}
76
77impl Input {
78    fn field_names(&self) -> Vec<syn::Ident> {
79        self.fields
80            .iter()
81            .enumerate()
82            .map(|(idx, field)| field.field_name(idx))
83            .collect()
84    }
85
86    fn instantiate_struct(&self) -> TokenStream {
87        let struct_name = &self.ident;
88        let field_names = self.field_names();
89
90        if self.tuple_like {
91            return quote! {
92                #struct_name(
93                    #(#field_names),*
94                )
95            };
96        }
97
98        quote! {
99            #struct_name {
100                #(#field_names),*
101            }
102        }
103    }
104
105    fn error_ident(&self) -> syn::Path {
106        if self.is_infallible() {
107            return syn::parse_str("::std::convert::Infallible").unwrap();
108        }
109
110        let error_name = format!("{}EnvError", self.ident);
111        syn::parse_str::<syn::Path>(&error_name)
112            .map_err(|_| {
113                syn::Error::new(self.ident.span(), "Failed to parse error ident").to_compile_error()
114            })
115            .unwrap()
116    }
117
118    fn is_infallible(&self) -> bool {
119        self.error_variants().is_empty()
120    }
121
122    fn error_variants(&self) -> Vec<TokenStream> {
123        self.fields
124            .iter()
125            .enumerate()
126            .flat_map(|(idx, field)| field.expand_enum_variant(idx))
127            .collect()
128    }
129
130    fn error_variant_displays(&self) -> Vec<TokenStream> {
131        self.fields
132            .iter()
133            .enumerate()
134            .flat_map(|(idx, field)| field.expand_variant_display(idx))
135            .collect::<Vec<_>>()
136    }
137
138    fn expand_variant_sources(&self) -> Vec<TokenStream> {
139        self.fields
140            .iter()
141            .enumerate()
142            .flat_map(|(idx, field)| field.expand_variant_source(idx))
143            .collect::<Vec<_>>()
144    }
145
146    fn item_from_envs(&self) -> Vec<TokenStream> {
147        let error_ident = self.error_ident();
148        self.fields
149            .iter()
150            .enumerate()
151            .map(|(idx, field)| field.expand_item_from_env(&error_ident, idx))
152            .collect()
153    }
154
155    fn expand_error(&self) -> TokenStream {
156        let error_ident = self.error_ident();
157        let struct_name_str = &self.ident.to_string();
158
159        let error_variants = self.error_variants();
160        let error_variant_displays = self.error_variant_displays();
161        let error_variant_sources = self.expand_variant_sources();
162
163        if error_variants.is_empty() {
164            return Default::default();
165        }
166
167        quote! {
168            #[doc = "Generated error type for [`FromEnv`] for"]
169            #[doc = #struct_name_str]
170            #[doc = ". This error type is used to represent errors that occur when trying to create an instance of the struct from environment variables."]
171            #[derive(Debug, PartialEq, Eq, Clone)]
172            pub enum #error_ident {
173                #(#error_variants),*
174            }
175
176            #[automatically_derived]
177            impl ::core::fmt::Display for #error_ident {
178                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
179                    match self {
180                        #(
181                            #error_variant_displays,
182                        )*
183                    }
184                }
185            }
186
187            #[automatically_derived]
188            impl ::core::error::Error for #error_ident {
189                fn source(&self) -> Option<&(dyn ::core::error::Error + 'static)> {
190                    match self {
191                        #(
192                            #error_variant_sources,
193                        )*
194                    }
195                }
196            }
197        }
198    }
199
200    fn env_item_info(&self) -> Vec<TokenStream> {
201        self.fields
202            .iter()
203            .map(|field| field.expand_env_item_info())
204            .collect()
205    }
206
207    fn expand_impl(&self) -> TokenStream {
208        let env_item_info = self.env_item_info();
209        let struct_name = &self.ident;
210
211        let error_ident = self.error_ident();
212
213        let item_from_envs = self.item_from_envs();
214        let struct_instantiation = self.instantiate_struct();
215
216        quote! {
217            #[automatically_derived]
218            impl FromEnv for #struct_name {
219                type Error = #error_ident;
220
221                fn inventory() -> ::std::vec::Vec<&'static EnvItemInfo> {
222                    let mut items = ::std::vec::Vec::new();
223                    #(
224                        #env_item_info
225                    )*
226                    items
227                }
228
229                fn from_env() -> ::std::result::Result<Self, FromEnvErr<Self::Error>> {
230                    #(
231                        #item_from_envs
232                    )*
233
234                    ::std::result::Result::Ok(#struct_instantiation)
235                }
236            }
237        }
238    }
239
240    fn expand_mod(&self) -> TokenStream {
241        // let expanded_impl = expand_impl(input);
242        let expanded_impl = self.expand_impl();
243        let crate_name = &self.crate_name;
244
245        let mod_ident =
246            syn::parse_str::<syn::Ident>(&format!("__from_env_impls_{}", self.ident)).unwrap();
247
248        let expanded_error = self.expand_error();
249
250        let use_err = if !expanded_error.is_empty() {
251            let error_ident = self.error_ident();
252            quote! {
253                pub use #mod_ident::#error_ident;
254            }
255        } else {
256            quote! {}
257        };
258
259        quote! {
260            #use_err
261            mod #mod_ident {
262                use super::*;
263                use #crate_name::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar, EnvItemInfo};
264
265                #expanded_impl
266
267                #expanded_error
268            }
269        }
270    }
271}