init4_from_env_derive/
lib.rs1use 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#[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 = 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}