env_map/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, Data, DeriveInput, Fields};
5
6#[proc_macro_derive(EnvMap)]
7pub fn env_map_derive(input: TokenStream) -> TokenStream {
8    let ast = parse_macro_input!(input as DeriveInput);
9
10    let main_struct = ast.ident;
11    let const_value = Ident::new(
12        &format!("{}", &main_struct).to_uppercase(),
13        main_struct.span(),
14    );
15    let builder = format_ident!("{}Builder", &main_struct);
16    let fields = if let Data::Struct(data_struct) = &ast.data {
17        match &data_struct.fields {
18            Fields::Named(fields) => &fields.named,
19            Fields::Unnamed(fields) => &fields.unnamed,
20            Fields::Unit => panic!("Unit structs are not supported"),
21        }
22    } else {
23        panic!("ReplicateStruct is only implemented for structs");
24    };
25    let fields_clone = fields.clone();
26    let fields_iter = fields_clone.iter().filter_map(|f| f.ident.as_ref());
27    let fields_into_iter = fields.iter();
28    let gen = quote! {
29        use serde::Deserialize;
30        use std::{env, sync::OnceLock};
31
32        #[derive(Deserialize, Clone)]
33        struct #builder {
34            #(#fields_into_iter),*
35        }
36
37        impl Default for #builder {
38            fn default() -> Self {
39                let dotenv = dotenvy::dotenv();
40                match envy::from_env::<#builder>() {
41                    Ok(builder) => builder,
42                    Err(_) => panic!("missing env vars"),
43                }
44            }
45        }
46
47        impl #builder {
48            pub fn build(self) -> #main_struct {
49                #main_struct {
50                    #(#fields_iter: self.#fields_iter.clone(),)*
51                }
52            }
53        }
54
55        impl Default for #main_struct {
56            fn default() -> Self {
57                let builder = #builder::default();
58                builder.build()
59            }
60        }
61
62        impl #main_struct {
63            pub fn get_config() -> OnceLock<#main_struct> {
64                // #const_value.get_or_init(#main_struct::default)
65                #const_value
66            }
67        }
68
69        const #const_value: OnceLock<#main_struct> = OnceLock::new();
70    };
71    gen.into()
72}