1mod default_attr;
2mod normalize_type_path;
3
4use darling::{ast, FromDeriveInput, FromField};
5use default_attr::*;
6use normalize_type_path::*;
7use proc_macro::TokenStream;
8use quote::*;
9use syn::spanned::Spanned;
10
11#[proc_macro_derive(EnvStruct, attributes(env))]
13pub fn derive(input: TokenStream) -> TokenStream {
14 let derive_input: syn::DeriveInput = syn::parse(input).expect("Failed to parse derive input");
15 let receiver = EnvStructInputReceiver::from_derive_input(&derive_input)
16 .expect("Failed to parse input for darling receiver");
17 quote!(#receiver).into()
18}
19
20#[derive(Debug, FromDeriveInput)]
22#[darling(attributes(EnvStruct), supports(any))]
23struct EnvStructInputReceiver {
24 ident: syn::Ident,
25 generics: syn::Generics,
26 data: ast::Data<(), EnvStructFieldReceiver>,
27}
28
29#[derive(Debug, FromField)]
31#[darling(attributes(env))]
32struct EnvStructFieldReceiver {
33 ident: Option<syn::Ident>,
34 ty: syn::Type,
35 name: Option<String>,
36 default: Option<DefaultAttr>,
37 with: Option<syn::Expr>,
38 #[darling(default)]
39 flatten: bool,
40 #[darling(default)]
41 skip: bool,
42}
43
44impl EnvStructFieldReceiver {
45 pub fn name_exr(&self, index: usize) -> proc_macro2::TokenStream {
47 self.ident
48 .as_ref()
49 .map(quote::ToTokens::to_token_stream)
50 .unwrap_or_else(|| {
51 let index = syn::Index::from(index);
52 quote!(#index)
53 })
54 }
55
56 pub fn type_expr(&self) -> proc_macro2::TokenStream {
58 self.with
59 .as_ref()
60 .map(|ty| quote_spanned! { ty.span() => #ty })
61 .unwrap_or({
62 let ty = normalize_type_path(&self.ty);
63 quote_spanned! { ty.span() => #ty }
64 })
65 }
66
67 pub fn default_expr(&self) -> proc_macro2::TokenStream {
69 self.default
70 .as_ref()
71 .map(|default| match default {
72 DefaultAttr::String(str) => {
73 quote!(Some(#str))
74 }
75 DefaultAttr::Type(typ) => {
76 quote!(Some(&#typ.to_string()))
77 }
78 DefaultAttr::Default => {
79 let ty = normalize_type_path(&self.ty);
80 quote!(Some(&#ty::default().to_string()))
81 }
82 })
83 .unwrap_or_else(|| quote!(None))
84 }
85
86 pub fn var_name_expr(&self) -> proc_macro2::TokenStream {
88 let var_name = self.name.clone().unwrap_or_else(|| {
89 self.ident
90 .as_ref()
91 .map(|v| quote!(#v).to_string())
92 .unwrap_or_default()
93 });
94
95 if self.flatten {
96 quote!(&prefix)
97 } else {
98 quote!(::envstruct::concat_env_name(&prefix, #var_name))
99 }
100 }
101}
102
103impl ToTokens for EnvStructInputReceiver {
104 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
105 let EnvStructInputReceiver {
106 ident,
107 generics,
108 data,
109 } = self;
110 let (imp, ty, where_clause) = generics.split_for_impl();
111
112 let impl_block = match data {
113 ast::Data::Enum(_) => {
114 quote_spanned! {ty.span() =>
115 impl #imp ::envstruct::EnvParsePrimitive for #ident #ty #where_clause {
116 fn parse(val: &str) -> std::result::Result<Self, ::envstruct::BoxError> {
117 Ok(val.parse::<#ident>()?)
118 }
119 }
120 }
121 }
122 ast::Data::Struct(fields) => {
123 let field_exprs: Vec<_> = fields
124 .iter()
125 .enumerate()
126 .map(|(index, field)| {
127 let field_name = field.name_exr(index);
128 let field_type = field.type_expr();
129 let var_default = field.default_expr();
130 let var_name_expr = field.var_name_expr();
131
132 if field.skip {
133 quote_spanned! {field.ty.span() =>
134 #field_name: Default::default()
135 }
136 } else {
137 quote_spanned! {field.ty.span() =>
138 #field_name: #field_type::parse_from_env_var(#var_name_expr, #var_default)?.into()
139 }
140 }
141
142 })
143 .collect();
144
145 let inspect_exprs: Vec<_> = fields
146 .iter()
147 .filter(|field| !field.skip)
148 .map(|field| {
149 let field_type = field.type_expr();
150 let var_default = field.default_expr();
151 let var_name_expr = field.var_name_expr();
152
153 quote_spanned! {field.ty.span() =>
154 #field_type::get_env_entries(#var_name_expr, #var_default)?
155 }
156 })
157 .collect();
158
159 quote! {
160 #[allow(clippy::useless_conversion)]
161 impl #imp ::envstruct::EnvParseNested for #ident #ty #where_clause {
162 fn parse_from_env_var(prefix: impl AsRef<str>, default: Option<&str>) -> std::result::Result<Self, ::envstruct::EnvStructError> {
163 Ok(Self {
164 #( #field_exprs, )*
165 })
166 }
167
168 fn get_env_entries(prefix: impl AsRef<str>, default: Option<&str>) -> std::result::Result<Vec<::envstruct::EnvEntry>, ::envstruct::EnvStructError> {
169 Ok(vec![#( #inspect_exprs, )*].into_iter().flatten().collect())
170 }
171 }
172 }
173 }
174 };
175
176 tokens.extend(impl_block);
177 }
178}