1mod field;
2
3use darling::{
4 FromDeriveInput,
5 ast::{Data, Fields},
6};
7use proc_macro2::TokenStream;
8use quote::{ToTokens, format_ident, quote};
9use syn::{DeriveInput, ExprPath, Ident, Visibility, parse_macro_input};
10
11use crate::field::{EnvAttribute, FromEnvFieldReceiver};
12
13#[proc_macro_derive(FromEnv, attributes(env))]
14pub fn derive_from_env(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 let input = parse_macro_input!(input as DeriveInput);
16
17 match impl_derive(input) {
18 Ok(output) => output.into(),
19 Err(err) => err.write_errors().into(),
20 }
21}
22
23fn impl_derive(input: DeriveInput) -> darling::Result<TokenStream> {
24 let config_struct = match FromEnvReceiver::from_derive_input(&input) {
25 Ok(config_struct) => config_struct,
26 Err(e) => {
27 return Err(e);
28 }
29 };
30
31 config_struct.validate()?;
32
33 Ok(config_struct.to_token_stream())
34}
35
36#[derive(FromDeriveInput)]
37#[darling(supports(struct_named))]
38struct FromEnvReceiver {
39 pub ident: Ident,
40 pub vis: Visibility,
41 pub data: Data<(), FromEnvFieldReceiver>,
42}
43
44struct ConstTokens {
45 private_path: TokenStream,
46 errors_ident: TokenStream,
47 builder_name: Ident,
48}
49
50impl ToTokens for FromEnvReceiver {
51 fn to_tokens(&self, tokens: &mut TokenStream) {
52 let consts = ConstTokens {
53 builder_name: format_ident!("{}Builder", self.ident),
54 private_path: quote!(__fromenv::__private),
55 errors_ident: quote!(__fromenv_derive_builder_errors),
56 };
57 let private_path = &consts.private_path;
58
59 let impl_struct = self.impl_struct(&consts);
60 let builder_struct = self.builder_struct(&consts);
61 let impl_from_env = self.impl_from_env(&consts);
62 let impl_from_env_builder = self.impl_from_env_builder(&consts);
63 let impl_builder = self.impl_builder(&consts);
64
65 let derive = quote! {
66 const _: () = {
67 extern crate fromenv as __fromenv;
68 use #private_path::Parser as _;
69
70 #impl_struct
71
72 #builder_struct
73
74 #impl_from_env
75
76 #impl_from_env_builder
77
78 #impl_builder
79 };
80 };
81
82 tokens.extend(derive);
83 }
84}
85
86impl FromEnvReceiver {
87 fn validate(&self) -> darling::Result<()> {
88 if !matches!(&self.vis, Visibility::Public(_)) {
89 let err = darling::Error::custom("FromEnv derive requires a public struct")
90 .with_span(&self.ident.span());
91 Err(err)
92 } else {
93 Ok(())
94 }
95 }
96
97 fn builder_struct(&self, consts: &ConstTokens) -> TokenStream {
98 let private_path = &consts.private_path;
99 let builder_name = &consts.builder_name;
100 let fields = self.get_fields().iter().map(|field| {
101 let ident = &field.ident;
102 let ty = &field.option.as_ref().unwrap_or(&field.ty);
103
104 match &field.env_attr {
105 EnvAttribute::Nested => {
106 quote! { #ident: Option<<#ty as #private_path::FromEnv>::FromEnvBuilder> }
107 }
108 EnvAttribute::Flat { .. } | EnvAttribute::None => {
109 quote! { #ident: Option<#ty> }
110 }
111 }
112 });
113
114 quote! {
115 pub struct #builder_name {
116 #(#fields,)*
117 }
118 }
119 }
120
121 fn impl_struct(&self, consts: &ConstTokens) -> TokenStream {
122 let struct_name = &self.ident;
123 let private_path = &consts.private_path;
124 let builder_name = &consts.builder_name;
125
126 quote! {
127 impl #struct_name {
128 pub fn from_env() -> #builder_name {
129 <Self as #private_path::FromEnv>::from_env()
130 }
131
132 pub fn requirements() -> String {
133 let mut requirements = ::std::string::String::new();
134 <Self as #private_path::FromEnv>::requirements(&mut requirements);
135 requirements
136 }
137 }
138 }
139 }
140
141 fn impl_from_env(&self, consts: &ConstTokens) -> TokenStream {
142 let struct_name = &self.ident;
143 let builder_name = &consts.builder_name;
144 let private_path = &consts.private_path;
145
146 let fields = self.get_fields().iter().map(|field| {
147 let ident = &field.ident;
148 let ty = field.option.as_ref().unwrap_or(&field.ty);
149
150 match &field.env_attr {
151 EnvAttribute::Nested => {
152 quote! { #ident: Some(<#ty as #private_path::FromEnv>::from_env()) }
153 }
154 EnvAttribute::Flat { .. } | EnvAttribute::None => {
155 quote! { #ident: None }
156 }
157 }
158 });
159
160 let requirements = self.get_fields().iter().map(|field| {
161 let ty = field.option.as_ref().unwrap_or(&field.ty);
162
163 match &field.env_attr {
164 EnvAttribute::Nested => {
165 quote! {
166 <#ty as #private_path::FromEnv>::requirements(requirements);
167 }
168 }
169 EnvAttribute::Flat {
170 from,
171 default,
172 with: _,
173 } => {
174 let from = from.value();
175 let default = default
176 .as_ref()
177 .map(|default| default.value())
178 .unwrap_or(String::new());
179 let out = format!("{from}={default}\n");
180
181 quote! {
182 requirements.push_str(#out);
183 }
184 }
185 _ => TokenStream::new(),
186 }
187 });
188
189 quote! {
190 impl #private_path::FromEnv for #struct_name {
191 type FromEnvBuilder = #builder_name;
192
193 fn from_env() -> Self::FromEnvBuilder {
194 #builder_name {
195 #(#fields,)*
196 }
197 }
198
199 fn requirements(requirements: &mut ::std::string::String) {
200 #(#requirements)*
201 }
202 }
203 }
204 }
205
206 fn impl_from_env_builder(&self, consts: &ConstTokens) -> TokenStream {
207 let struct_name = &self.ident;
208 let private_path = &consts.private_path;
209 let errors_ident = &consts.errors_ident;
210 let builder_name = &consts.builder_name;
211
212 let assignments = self.get_fields().iter().map(|field| {
215 let ident = &field.ident;
216 let path = format!("{struct_name}.{ident}");
217
218 match (&field.env_attr, field.option.is_some()) {
219 (EnvAttribute::Nested, false) => {
221 quote! {
222 let #ident = match #private_path::FromEnvBuilder::finalize(self.#ident.take().unwrap()) {
223 Ok(inner) => Ok(inner),
224 Err(errors) => {
225 #errors_ident.extend(errors);
226 Err(())
227 }
228 };
229 }
230 }
231 (EnvAttribute::Nested, true) => {
233 quote! {
234 let #ident = match #private_path::FromEnvBuilder::finalize(self.#ident.take().unwrap()) {
235 Ok(inner) => Ok(Some(inner)),
236 Err(errors) if errors.only_missing_errors() => Ok(None),
237 Err(errors) => {
238 #errors_ident.extend(errors);
239 Err(())
240 }
241 };
242 }
243 }
244 (
246 EnvAttribute::Flat {
247 from,
248 with,
249 default: Some(default),
250 },
251 false,
252 ) => {
253 let with = parser_path(consts, with.as_ref());
254
255 quote! {
256 let #ident = if let Some(inner) = self.#ident {
257 Ok(inner)
258 } else {
259 match #with.parse_from_env(#from) {
260 Some((_, Ok(val))) => Ok(val),
261 Some((value, Err(error))) => {
262 let err = #private_path::FromEnvError::ParseError {
263 path: #path.to_string(),
264 env_var: #from.to_string(),
265 value,
266 error,
267 };
268 #errors_ident.add(err);
269 Err(())
270 }
271 None => {
272 #with.parse(#default).map_err(|error| {
273 let err = #private_path::FromEnvError::ParseError {
274 path: #path.to_string(),
275 env_var: #from.to_string(),
276 value: #default.to_string(),
277 error: error.into(),
278 };
279 #errors_ident.add(err);
280 })
281 },
282 }
283 };
284 }
285 }
286 (
288 EnvAttribute::Flat {
289 from,
290 with,
291 default: None,
292 },
293 false,
294 ) => {
295 let with = parser_path(consts, with.as_ref());
296
297 quote! {
298 let #ident = if let Some(inner) = self.#ident {
299 Ok(inner)
300 } else {
301 match #with.parse_from_env(#from) {
302 Some((_, Ok(val))) => Ok(val),
303 Some((value, Err(error))) => {
304 let err = #private_path::FromEnvError::ParseError {
305 path: #path.to_string(),
306 env_var: #from.to_string(),
307 value,
308 error,
309 };
310 #errors_ident.add(err);
311 Err(())
312 }
313 None => {
314 let err = #private_path::FromEnvError::MissingEnv {
315 path: #path.to_string(),
316 env_var: #from.to_string(),
317 };
318 #errors_ident.add(err);
319 Err(())
320 }
321 }
322 };
323 }
324 }
325 (
327 EnvAttribute::Flat {
328 from,
329 with,
330 default: None,
331 },
332 true,
333 ) => {
334 let with = parser_path(consts, with.as_ref());
335
336 quote! {
337 let #ident = if let Some(inner) = self.#ident {
338 Ok(Some(inner))
339 } else {
340 match #with.parse_from_env(#from) {
341 Some((_, Ok(val))) => Ok(Some(val)),
342 Some((value, Err(error))) => {
343 let err = #private_path::FromEnvError::ParseError {
344 path: #path.to_string(),
345 env_var: #from.to_string(),
346 value,
347 error,
348 };
349 #errors_ident.add(err);
350 Err(())
351 }
352 None => {
353 Ok(None)
354 }
355 }
356 };
357 }
358 }
359 (
361 EnvAttribute::Flat {
362 from: _,
363 with: _,
364 default: Some(_),
365 },
366 true,
367 ) => unreachable!("we've already checked that Optional fields can't have a default"),
368 (EnvAttribute::None, false) => {
369 quote! {
370 let #ident = match self.#ident {
371 Some(inner) => Ok(inner),
372 None => {
373 let err = #private_path::FromEnvError::MissingValue {
374 path: #path.to_string(),
375 };
376 #errors_ident.add(err);
377 Err(())
378 }
379 };
380 }
381 }
382 (EnvAttribute::None, true) => {
383 quote! {
384 let #ident = self.#ident;
385 }
386 }
387 }
388 });
389
390 let fields = self.get_fields().iter().map(|field| {
391 let ident = &field.ident;
392
393 quote! {
394 #ident: match #ident {
395 Ok(val) => val,
396 Err(_) => {
397 return Err(#errors_ident);
398 }
399 }
400 }
401 });
402
403 quote! {
404 impl #private_path::FromEnvBuilder for #builder_name {
405 type Target = #struct_name;
406
407 fn finalize(mut self) -> Result<Self::Target, #private_path::FromEnvErrors> {
408 let mut #errors_ident = #private_path::FromEnvErrors::new();
409
410 #(#assignments)*
411
412 Ok(#struct_name {
413 #(#fields,)*
414 })
415 }
416 }
417 }
418 }
419
420 fn impl_builder(&self, consts: &ConstTokens) -> TokenStream {
421 let private_path = &consts.private_path;
422 let builder_name = &consts.builder_name;
423
424 let setters = self.get_fields().iter().map(|field| {
425 let ident = &field.ident;
426 let ty = &field.option.as_ref().unwrap_or(&field.ty);
427 let doc_attrs = &field.doc_attrs;
428
429 match &field.env_attr {
430 EnvAttribute::Nested => {
431 quote! {
432 #(#doc_attrs)*
433 pub fn #ident<F>(mut self, f: F) -> Self
434 where
435 F: FnOnce(<#ty as #private_path::FromEnv>::FromEnvBuilder) -> <#ty as #private_path::FromEnv>::FromEnvBuilder,
436 {
437 let nested = self.#ident.take().unwrap();
438 let nested = f(nested);
439 self.#ident = Some(nested);
440 self
441 }
442 }
443 }
444 EnvAttribute::Flat { .. } | EnvAttribute::None => {
445 quote! {
446 #(#doc_attrs)*
447 pub fn #ident(mut self, #ident: #ty) -> Self {
448 self.#ident = Some(#ident);
449 self
450 }
451 }
452 }
453 }
454 });
455
456 quote! {
457 impl #builder_name {
458 #(#setters)*
459
460 pub fn finalize(self) -> Result<<Self as #private_path::FromEnvBuilder>::Target, #private_path::FromEnvErrors> {
461 #private_path::FromEnvBuilder::finalize(self)
462 }
463 }
464 }
465 }
466
467 fn get_fields(&self) -> &Fields<FromEnvFieldReceiver> {
468 let Data::Struct(fields) = &self.data else {
469 panic!("we've asserted that it's a struct");
470 };
471
472 fields
473 }
474}
475
476fn parser_path(consts: &ConstTokens, path: Option<&ExprPath>) -> TokenStream {
477 let private_path = &consts.private_path;
478
479 if let Some(expr_path) = path {
480 if let Some(ident) = expr_path.path.get_ident() {
481 let ident_str = ident.to_string();
482
483 match ident_str.as_str() {
484 "from_str" => return quote!(#private_path::from_str),
485 "into" => return quote!(#private_path::into),
486 _ => {}
487 }
488 }
489
490 return quote!(#expr_path);
491 }
492
493 quote!(#private_path::from_str)
494}