Skip to content

Commit d5aa73a

Browse files
committed
Start testing env.
1 parent 827ca1d commit d5aa73a

13 files changed

+507
-48
lines changed

crates/core/src/args.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ impl FromMeta for SerdeRenameArg {
5757
}
5858

5959
impl SerdeRenameArg {
60-
// pub fn get_name(&self, dir: SerdeIoDirection) -> Option<&str> {
61-
// match dir {
62-
// SerdeIoDirection::From => self.deserialize.as_deref(),
63-
// SerdeIoDirection::To => self.serialize.as_deref(),
64-
// }
65-
// }
60+
pub fn get_name(&self, dir: SerdeIoDirection) -> Option<&str> {
61+
match dir {
62+
SerdeIoDirection::From => self.deserialize.as_deref(),
63+
SerdeIoDirection::To => self.serialize.as_deref(),
64+
}
65+
}
6666

6767
pub fn get_meta(&self, key: &str) -> TokenStream {
6868
match (self.deserialize.as_deref(), self.serialize.as_deref()) {

crates/core/src/container.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,15 @@ impl Container {
178178
};
179179

180180
let default_values_method = self.impl_partial_default_values();
181+
let env_values_method = self.impl_partial_env_values();
181182

182183
quote! {
183184
#[automatically_derived]
184185
impl schematic::PartialConfig for #partial_name {
185186
type Context = #context;
186187

187188
#default_values_method
189+
#env_values_method
188190
}
189191

190192
#[automatically_derived]
@@ -214,11 +216,11 @@ impl Container {
214216
let res = field.impl_partial_default_value();
215217

216218
if !res.no_value {
217-
let name = field.ident.as_ref().unwrap();
219+
let key = field.get_key();
218220
let value = res.value;
219221

220222
rows.push(quote! {
221-
#name: #value,
223+
#key: #value,
222224
});
223225
}
224226

@@ -227,6 +229,7 @@ impl Container {
227229
}
228230
}
229231

232+
// Do not implement method
230233
if rows.is_empty() {
231234
return quote! {};
232235
}
@@ -263,6 +266,7 @@ impl Container {
263266
}
264267
}
265268

269+
// Do not implement method
266270
if all_none {
267271
return quote! {};
268272
}
@@ -319,6 +323,58 @@ impl Container {
319323
}
320324
}
321325
}
326+
327+
pub fn impl_partial_env_values(&self) -> TokenStream {
328+
let inner = match &self.inner {
329+
ContainerInner::NamedStruct { fields } | ContainerInner::UnnamedStruct { fields } => {
330+
let mut rows = vec![];
331+
332+
for field in fields {
333+
let res = field.impl_partial_env_value();
334+
335+
if !res.no_value {
336+
let key = field.get_key();
337+
let value = res.value;
338+
339+
rows.push(quote! {
340+
partial.#key = #value;
341+
});
342+
}
343+
}
344+
345+
// Do not implement method
346+
if rows.is_empty() {
347+
return quote! {};
348+
}
349+
350+
quote! {
351+
#(#rows)*
352+
}
353+
}
354+
355+
// Enums don't support env vars
356+
_ => return quote! {},
357+
};
358+
359+
let internal = ImplResult::impl_use_internal(true);
360+
361+
quote! {
362+
fn env_values() -> std::result::Result<Option<Self>, schematic::ConfigError> {
363+
#internal
364+
365+
let mut env = EnvManager::default();
366+
let mut partial = Self::default();
367+
368+
#inner
369+
370+
Ok(if env.is_empty() {
371+
None
372+
} else {
373+
Some(partial)
374+
})
375+
}
376+
}
377+
}
322378
}
323379

324380
impl ToTokens for Container {

crates/core/src/field.rs

Lines changed: 112 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use crate::args::{PartialArg, SerdeContainerArgs, SerdeFieldArgs, SerdeRenameArg};
1+
use crate::args::{
2+
PartialArg, SerdeContainerArgs, SerdeFieldArgs, SerdeIoDirection, SerdeRenameArg,
3+
};
24
use crate::container::ContainerArgs;
35
use crate::field_value::FieldValue;
46
use crate::utils::{ImplResult, preserve_str_literal, to_type_string};
57
use darling::{FromAttributes, FromMeta};
6-
use quote::ToTokens;
8+
use proc_macro2::{Literal, TokenStream};
9+
use quote::{ToTokens, TokenStreamExt, quote};
710
use std::ops::Deref;
811
use std::rc::Rc;
912
use syn::{Attribute, Expr, ExprPath, Field as NativeField, FieldMutability, Ident, Visibility};
@@ -154,37 +157,13 @@ impl Field {
154157
fn validate_args(&self) {
155158
#[cfg(feature = "env")]
156159
{
157-
// env
158-
if self.args.env.as_ref().is_some_and(|key| key.is_empty()) {
159-
panic!("Attribute `env` cannot be empty.");
160-
}
161-
162-
if self.args.env.is_some() && self.args.env_prefix.is_some() {
163-
panic!("Cannot use `env` and `env_prefix` together.");
164-
}
165-
166160
// env_prefix
167-
if self
168-
.args
169-
.env_prefix
170-
.as_ref()
171-
.is_some_and(|key| key.is_empty())
172-
{
173-
panic!("Attribute `env_prefix` cannot be empty.");
174-
}
175-
176161
if self.args.env_prefix.is_some() && self.args.nested.is_none() {
177162
panic!("Cannot use `env_prefix` without `nested`.");
178163
}
179164
}
180165

181166
// nested
182-
if self.args.nested.is_some() {
183-
#[cfg(feature = "env")]
184-
if self.args.env.is_some() {
185-
panic!("Cannot use `env` with `nested`, use `env_prefix` instead?");
186-
}
187-
}
188167

189168
#[cfg(feature = "env")]
190169
{
@@ -194,6 +173,83 @@ impl Field {
194173
}
195174
}
196175
}
176+
177+
#[cfg(not(feature = "env"))]
178+
pub fn get_env_var(&self) -> Option<String> {
179+
None
180+
}
181+
182+
#[cfg(feature = "env")]
183+
pub fn get_env_var(&self) -> Option<String> {
184+
if self.args.env.is_some() && self.args.env_prefix.is_some() {
185+
panic!("Cannot use `env` and `env_prefix` together.");
186+
}
187+
188+
if let Some(env_key) = &self.args.env {
189+
if env_key.is_empty() {
190+
panic!("Attribute `env` cannot be empty.");
191+
}
192+
193+
if self.is_nested() {
194+
panic!("Cannot use `env` with `nested`, use `env_prefix` instead?");
195+
}
196+
197+
return Some(env_key.to_owned());
198+
}
199+
200+
if let Some(env_prefix) = &self.container_args.env_prefix {
201+
if env_prefix.is_empty() {
202+
panic!("Attribute `env_prefix` cannot be empty.");
203+
}
204+
205+
return Some(format!("{env_prefix}{}", self.get_name()).to_uppercase());
206+
}
207+
208+
None
209+
}
210+
211+
pub fn get_key(&self) -> TokenStream {
212+
self.ident
213+
.as_ref()
214+
.map(|name| quote! { #name })
215+
.unwrap_or_else(|| {
216+
let index = Index(self.index);
217+
218+
quote! { #index }
219+
})
220+
}
221+
222+
pub fn get_name(&self) -> String {
223+
let dir = SerdeIoDirection::From;
224+
225+
if let Some(name) = self.args.rename.as_ref().and_then(|rn| rn.get_name(dir)) {
226+
return name.into();
227+
}
228+
229+
if let Some(name) = self
230+
.serde_args
231+
.rename
232+
.as_ref()
233+
.and_then(|rn| rn.get_name(dir))
234+
{
235+
return name.into();
236+
}
237+
238+
self.ident
239+
.as_ref()
240+
.expect("Name only usable on named fields!")
241+
.to_string()
242+
}
243+
244+
pub fn is_nested(&self) -> bool {
245+
self.args
246+
.nested
247+
.as_ref()
248+
.is_some_and(|nested| match nested {
249+
FieldNestedArg::Detect(inner) => *inner,
250+
FieldNestedArg::Ident(_) => true,
251+
})
252+
}
197253
}
198254

199255
// impl ToTokens for Field {
@@ -223,4 +279,34 @@ impl Field {
223279
pub fn impl_partial_default_value(&self) -> ImplResult {
224280
self.value.impl_partial_default_value(&self.args)
225281
}
282+
283+
#[cfg(not(feature = "env"))]
284+
pub fn impl_partial_env_value(&self) -> ImplResult {
285+
ImplResult::skipped()
286+
}
287+
288+
#[cfg(feature = "env")]
289+
pub fn impl_partial_env_value(&self) -> ImplResult {
290+
if self.is_nested() {
291+
return self.value.impl_partial_env_value(&self.args, "");
292+
}
293+
294+
let Some(env_key) = self.get_env_var() else {
295+
if self.args.parse_env.is_some() {
296+
panic!("Cannot use `parse_env` without `env` or a parent `env_prefix`.");
297+
}
298+
299+
return ImplResult::skipped();
300+
};
301+
302+
self.value.impl_partial_env_value(&self.args, &env_key)
303+
}
304+
}
305+
306+
struct Index(usize);
307+
308+
impl ToTokens for Index {
309+
fn to_tokens(&self, tokens: &mut TokenStream) {
310+
tokens.append(Literal::usize_unsuffixed(self.0));
311+
}
226312
}

crates/core/src/field_value.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,19 @@ impl FieldValue {
8484
self.inner_ty.as_ref().unwrap_or(&self.ty)
8585
}
8686

87+
pub fn is_collection(&self) -> bool {
88+
self.layers.iter().any(|layer| {
89+
matches!(
90+
layer,
91+
Layer::Map(_) | Layer::Set(_) | Layer::Vec(_) | Layer::Unknown(_)
92+
)
93+
})
94+
}
95+
8796
pub fn is_outer_option_wrapped(&self) -> bool {
8897
self.layers
8998
.first()
90-
.is_some_and(|wrapper| *wrapper == Layer::Option)
99+
.is_some_and(|layer| *layer == Layer::Option)
91100
}
92101

93102
pub fn impl_partial_default_value(&self, field_args: &FieldArgs) -> ImplResult {
@@ -104,8 +113,14 @@ impl FieldValue {
104113
panic!("Cannot use `default` with `nested`.");
105114
}
106115

116+
let ident = format_ident!("Partial{}", nested_ident);
117+
118+
// quote! {
119+
// <#nested_ident as schematic::PartialConfig>::default_values(context)?
120+
// }
121+
107122
quote! {
108-
<#nested_ident as schematic::PartialConfig>::default_values(content)?
123+
#ident::default_values(context)?
109124
}
110125
} else if let Some(expr) = &field_args.default {
111126
let ty = self.get_inner_type();
@@ -178,6 +193,34 @@ impl FieldValue {
178193
res.value = value;
179194
res
180195
}
196+
197+
pub fn impl_partial_env_value(&self, field_args: &FieldArgs, env_key: &str) -> ImplResult {
198+
let mut res = ImplResult::default();
199+
200+
if self.is_collection() {
201+
panic!("Collection types cannot be used with `env`.");
202+
} else if !self.layers.is_empty() {
203+
panic!("Wrapper types cannot be used with `env`.");
204+
}
205+
206+
res.value = if let Some(nested_ident) = &self.nested_ident {
207+
let ident = format_ident!("Partial{}", nested_ident);
208+
209+
quote! {
210+
env.nested(#ident::env_values()?)?
211+
}
212+
} else if let Some(parse_env) = &field_args.parse_env {
213+
quote! {
214+
env.get_and_parse(#env_key, #parse_env)?
215+
}
216+
} else {
217+
quote! {
218+
env.get(#env_key)?
219+
}
220+
};
221+
222+
res
223+
}
181224
}
182225

183226
fn extract_type_information(

crates/core/tests/field_default_test.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ mod field_default {
9696
assert_debug_snapshot!(defaults);
9797
}
9898

99+
#[test]
100+
fn supports_nested() {
101+
let container = Container::from(parse_quote! {
102+
#[derive(Config)]
103+
struct Example {
104+
#[setting(nested)]
105+
a: NestedConfig,
106+
#[setting(nested = CustomConfig)]
107+
b: CustomConfig,
108+
}
109+
});
110+
111+
assert_snapshot!(pretty(container.impl_partial_default_values()));
112+
}
113+
99114
#[test]
100115
fn renders_nothing_if_all_option_wrapped() {
101116
let container = Container::from(parse_quote! {

0 commit comments

Comments
 (0)