Skip to content

Commit eb05072

Browse files
committed
lol
1 parent 39df2f9 commit eb05072

File tree

7 files changed

+523
-195
lines changed

7 files changed

+523
-195
lines changed

from-env-derive/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

from-env-macro/Cargo.toml renamed to from-env-derive/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ syn = { version = "2.0.100", features = ["full", "parsing"] }
1414
proc-macro = true
1515

1616
[dev-dependencies]
17-
init4-bin-base = { path = ".." }
17+
init4-bin-base = "0.2"

from-env-derive/src/field.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use heck::ToPascalCase;
2+
use proc_macro2::TokenStream;
3+
use quote::quote;
4+
use syn::{Ident, LitStr, spanned::Spanned};
5+
6+
/// A parsed Field of a struct
7+
pub(crate) struct Field {
8+
env_var: Option<LitStr>,
9+
field_name: Option<Ident>,
10+
field_type: syn::Type,
11+
12+
optional: bool,
13+
infallible: bool,
14+
desc: Option<String>,
15+
16+
_attrs: Vec<syn::Attribute>,
17+
18+
span: proc_macro2::Span,
19+
}
20+
21+
impl From<&syn::Field> for Field {
22+
fn from(field: &syn::Field) -> Self {
23+
let mut optional = false;
24+
let mut env_var = None;
25+
let mut infallible = false;
26+
let mut desc = None;
27+
28+
field
29+
.attrs
30+
.iter()
31+
.filter(|attr| attr.path().is_ident("from_env"))
32+
.for_each(|attr| {
33+
let _ = attr.parse_nested_meta(|meta| {
34+
if meta.path.is_ident("optional") {
35+
optional = true;
36+
return Ok(());
37+
}
38+
if meta.path.is_ident("var") {
39+
env_var = Some(meta.value()?.parse::<LitStr>()?);
40+
return Ok(());
41+
}
42+
if meta.path.is_ident("desc") {
43+
desc = Some(meta.value()?.parse::<LitStr>()?.value());
44+
return Ok(());
45+
}
46+
if meta.path.is_ident("infallible") {
47+
infallible = true;
48+
}
49+
Ok(())
50+
});
51+
});
52+
53+
// check if the from_env_attrs_field contains either
54+
// - `from_env(var = "FIELD_NAME")` or
55+
// - `from_env(optional)`
56+
57+
let field_type = field.ty.clone();
58+
let field_name = field.ident.clone();
59+
let span = field.span();
60+
61+
Field {
62+
env_var,
63+
field_name,
64+
field_type,
65+
optional,
66+
infallible,
67+
desc,
68+
_attrs: field
69+
.attrs
70+
.iter()
71+
.filter(|attr| !attr.path().is_ident("from_env"))
72+
.cloned()
73+
.collect(),
74+
span,
75+
}
76+
}
77+
}
78+
79+
impl Field {
80+
pub(crate) fn trait_name(&self) -> TokenStream {
81+
self.env_var
82+
.as_ref()
83+
.map(|_| quote! { FromEnvVar })
84+
.unwrap_or(quote! { FromEnv })
85+
}
86+
87+
pub(crate) fn as_trait(&self) -> TokenStream {
88+
let field_trait = self.trait_name();
89+
let field_type = &self.field_type;
90+
91+
quote! { <#field_type as #field_trait> }
92+
}
93+
94+
pub(crate) fn assoc_err(&self) -> TokenStream {
95+
let as_trait = self.as_trait();
96+
97+
quote! { #as_trait::Error }
98+
}
99+
100+
pub(crate) fn field_name(&self, idx: usize) -> Ident {
101+
if let Some(field_name) = self.field_name.as_ref() {
102+
return field_name.clone();
103+
}
104+
105+
let n = format!("field_{}", idx);
106+
syn::parse_str::<Ident>(&n)
107+
.map_err(|_| syn::Error::new(self.span, "Failed to create field name"))
108+
.unwrap()
109+
}
110+
111+
/// Produces the name of the enum variant for the field
112+
pub(crate) fn enum_variant_name(&self, idx: usize) -> TokenStream {
113+
let n = self.field_name(idx).to_string().to_pascal_case();
114+
115+
let n: Ident = syn::parse_str::<Ident>(&n)
116+
.map_err(|_| syn::Error::new(self.span, "Failed to create field name"))
117+
.unwrap();
118+
119+
quote! { #n }
120+
}
121+
122+
/// Produces the variant, containing the error type
123+
pub(crate) fn expand_enum_variant(&self, idx: usize) -> TokenStream {
124+
let variant_name = self.enum_variant_name(idx);
125+
let var_name_str = variant_name.to_string();
126+
let assoc_err = self.assoc_err();
127+
128+
if self.infallible {
129+
return quote! {
130+
#[doc = "Error for "]
131+
#[doc = #var_name_str]
132+
#variant_name
133+
};
134+
}
135+
136+
quote! {
137+
#[doc = "Error for "]
138+
#[doc = #var_name_str]
139+
#variant_name(#assoc_err)
140+
}
141+
}
142+
143+
/// Produces the a line for the `inventory` function
144+
/// of the form
145+
/// items.push(...);
146+
/// or
147+
/// items.extend(...);
148+
pub(crate) fn expand_env_item_info(&self) -> TokenStream {
149+
let description = self.desc.clone().unwrap_or_default();
150+
let optional = self.optional;
151+
152+
if let Some(env_var) = &self.env_var {
153+
let var_name = env_var.value();
154+
155+
return quote! {
156+
items.push(&EnvItemInfo {
157+
var: #var_name,
158+
description: #description,
159+
optional: #optional,
160+
});
161+
};
162+
}
163+
164+
let field_ty = &self.field_type;
165+
quote! {
166+
items.extend(
167+
<#field_ty as FromEnv>::inventory()
168+
);
169+
}
170+
}
171+
172+
pub(crate) fn expand_variant_display(&self, idx: usize) -> TokenStream {
173+
let variant_name = self.enum_variant_name(idx);
174+
175+
if self.infallible {
176+
return quote! {
177+
Self::#variant_name => unreachable!("Infallible")
178+
};
179+
}
180+
181+
quote! {
182+
Self::#variant_name(err) => err.fmt(f)
183+
}
184+
}
185+
186+
pub(crate) fn expand_variant_source(&self, idx: usize) -> TokenStream {
187+
let variant_name = self.enum_variant_name(idx);
188+
189+
if self.infallible {
190+
return quote! {
191+
Self::#variant_name => unreachable!("Infallible")
192+
};
193+
}
194+
195+
quote! {
196+
Self::#variant_name(err) => Some(err)
197+
}
198+
}
199+
200+
pub(crate) fn expand_item_from_env(&self, err_ident: &Ident, idx: usize) -> TokenStream {
201+
// Produces code fo the following form:
202+
// ```rust
203+
// // EITHER
204+
// let field_name = env::var(#self.env_var.unwrap()).map_err(|e| e.map(#ErroEnum::FieldName))?;
205+
206+
// // OR
207+
// let field_name = FromEnvVar::from_env_var(#self.env_var.unwrap()).map_err(|e| e.map(#ErroEnum::FieldName))?;
208+
209+
// // OR
210+
// let field_name = FromEnv::from_env().map_err()?;
211+
//```
212+
let variant = self.enum_variant_name(idx);
213+
let field_name = self.field_name(idx);
214+
215+
let fn_invoc = if let Some(ref env_var) = self.env_var {
216+
quote! { FromEnvVar::from_env_var(#env_var) }
217+
} else {
218+
quote! { FromEnv::from_env() }
219+
};
220+
221+
let map_line = if self.infallible {
222+
quote! { FromEnvErr::infallible_into }
223+
} else {
224+
quote! { |e| e.map(#err_ident::#variant) }
225+
};
226+
227+
quote! {
228+
let #field_name = #fn_invoc
229+
.map_err(#map_line)?;
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)