Skip to content

Commit 829d942

Browse files
authored
feat: skip for derive (#13)
* feat: skip for derive * docs: document skip * chore: util methods * docs: copy line to other one * feat: util impls on fromenvvar and fromenv * fix: doc
1 parent 31f9889 commit 829d942

File tree

6 files changed

+213
-7
lines changed

6 files changed

+213
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ name = "init4-bin-base"
44
description = "Internal utilities for binaries produced by the init4 team"
55
keywords = ["init4", "bin", "base"]
66

7-
version = "0.3.0"
7+
version = "0.3.1"
88
edition = "2021"
99
rust-version = "1.81"
1010
authors = ["init4", "James Prestwich"]

from-env-derive/Cargo.toml

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

2222
[dev-dependencies]
23-
init4-bin-base = "0.2"
23+
init4-bin-base = "0.3"

from-env-derive/src/field.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use heck::ToPascalCase;
22
use proc_macro2::TokenStream;
33
use quote::quote;
4-
use syn::{Ident, LitStr, spanned::Spanned};
4+
use syn::{spanned::Spanned, Ident, LitStr};
55

66
/// A parsed Field of a struct
77
pub(crate) struct Field {
@@ -11,6 +11,7 @@ pub(crate) struct Field {
1111

1212
optional: bool,
1313
infallible: bool,
14+
skip: bool,
1415
desc: Option<String>,
1516

1617
_attrs: Vec<syn::Attribute>,
@@ -26,13 +27,18 @@ impl TryFrom<&syn::Field> for Field {
2627
let mut env_var = None;
2728
let mut infallible = false;
2829
let mut desc = None;
30+
let mut skip = false;
2931

3032
field
3133
.attrs
3234
.iter()
3335
.filter(|attr| attr.path().is_ident("from_env"))
3436
.for_each(|attr| {
3537
let _ = attr.parse_nested_meta(|meta| {
38+
if meta.path.is_ident("skip") {
39+
skip = true;
40+
return Ok(());
41+
}
3642
if meta.path.is_ident("optional") {
3743
optional = true;
3844
return Ok(());
@@ -68,6 +74,7 @@ impl TryFrom<&syn::Field> for Field {
6874
field_name,
6975
field_type,
7076
optional,
77+
skip,
7178
infallible,
7279
desc,
7380
_attrs: field
@@ -115,7 +122,7 @@ impl Field {
115122

116123
/// Produces the name of the enum variant for the field
117124
pub(crate) fn enum_variant_name(&self, idx: usize) -> Option<TokenStream> {
118-
if self.infallible {
125+
if self.skip || self.infallible {
119126
return None;
120127
}
121128

@@ -143,10 +150,16 @@ impl Field {
143150

144151
/// Produces the a line for the `inventory` function
145152
/// of the form
146-
/// items.push(...);
153+
/// items.push(...); // (if this is a FromEnvVar)
154+
/// or
155+
/// items.extend(...); // (if this is a FromEnv)
147156
/// or
148-
/// items.extend(...);
157+
/// // nothing if this is a skip
149158
pub(crate) fn expand_env_item_info(&self) -> TokenStream {
159+
if self.skip {
160+
return quote! {};
161+
}
162+
150163
let description = self.desc.clone().unwrap_or_default();
151164
let optional = self.optional;
152165

@@ -197,10 +210,20 @@ impl Field {
197210

198211
// // OR
199212
// let field_name = FromEnv::from_env().map_err()?;
213+
214+
// // OR
215+
// let field_name = Default::default();
216+
200217
//```
201218
let variant = self.enum_variant_name(idx);
202219
let field_name = self.field_name(idx);
203220

221+
if self.skip {
222+
return quote! {
223+
let #field_name = Default::default();
224+
};
225+
}
226+
204227
let fn_invoc = if let Some(ref env_var) = self.env_var {
205228
quote! { FromEnvVar::from_env_var(#env_var) }
206229
} else {

from-env-derive/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use proc_macro::TokenStream as Ts;
22
use proc_macro2::TokenStream;
33
use quote::quote;
4-
use syn::{DeriveInput, parse_macro_input};
4+
use syn::{parse_macro_input, DeriveInput};
55

66
mod field;
77
use field::Field;

from-env-derive/tests/macro.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub struct FromEnvTest {
2525
desc = "Oliver is an Option<String>"
2626
)]
2727
pub oliver: Option<String>,
28+
29+
#[from_env(skip)]
30+
memo: std::sync::OnceLock<String>,
2831
}
2932

3033
#[derive(Debug, FromEnv)]
@@ -36,6 +39,13 @@ pub struct Nested {
3639
pub from_env_test: FromEnvTest,
3740
}
3841

42+
impl FromEnvTest {
43+
/// Get the memoized value
44+
pub fn get_memo(&self) -> &str {
45+
self.memo.get_or_init(|| "hello world".to_string())
46+
}
47+
}
48+
3949
#[cfg(test)]
4050
mod test {
4151
use super::*;

src/utils/from_env.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ use std::{convert::Infallible, env::VarError, num::ParseIntError, str::FromStr};
1212
/// occur when trying to create an instance of the struct from environment
1313
/// variables. This error type is used in the `FromEnv` trait implementation.
1414
///
15+
/// ## Attributes
16+
///
17+
/// The macro supports the following attributes:
18+
/// - `var = ""`: The name of the environment variable. This is required if the
19+
/// prop implements [`FromEnvVar`].
20+
/// - `desc = ""`: A description of the environment variable. This is required
21+
/// if the prop implements [`FromEnvVar`].
22+
/// - `optional`: Marks the prop as optional. This is currently only used in the
23+
/// generated `fn inventory`, and is informational.
24+
/// - `infallible`: Marks the prop as infallible. This means that the prop
25+
/// cannot fail to be parsed after the environment variable is loaded.
26+
/// - `skip`: Marks the prop as skipped. This means that the prop will not be
27+
/// loaded from the environment, and will be generated via
28+
/// `Default::default()` instead.
29+
///
1530
/// ## Conditions of use
1631
///
1732
/// There are a few usage requirements:
@@ -266,6 +281,87 @@ pub trait FromEnv: core::fmt::Debug + Sized + 'static {
266281
fn from_env() -> Result<Self, FromEnvErr<Self::Error>>;
267282
}
268283

284+
impl<T> FromEnv for Option<T>
285+
where
286+
T: FromEnv,
287+
{
288+
type Error = T::Error;
289+
290+
fn inventory() -> Vec<&'static EnvItemInfo> {
291+
T::inventory()
292+
}
293+
294+
fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
295+
T::check_inventory()
296+
}
297+
298+
fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
299+
match T::from_env() {
300+
Ok(v) => Ok(Some(v)),
301+
Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(None),
302+
Err(e) => Err(e),
303+
}
304+
}
305+
}
306+
307+
impl<T> FromEnv for Box<T>
308+
where
309+
T: FromEnv,
310+
{
311+
type Error = T::Error;
312+
313+
fn inventory() -> Vec<&'static EnvItemInfo> {
314+
T::inventory()
315+
}
316+
317+
fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
318+
T::check_inventory()
319+
}
320+
321+
fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
322+
T::from_env().map(Box::new)
323+
}
324+
}
325+
326+
impl<T> FromEnv for std::sync::Arc<T>
327+
where
328+
T: FromEnv,
329+
{
330+
type Error = T::Error;
331+
332+
fn inventory() -> Vec<&'static EnvItemInfo> {
333+
T::inventory()
334+
}
335+
336+
fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
337+
T::check_inventory()
338+
}
339+
340+
fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
341+
T::from_env().map(std::sync::Arc::new)
342+
}
343+
}
344+
345+
impl<T, U> FromEnv for std::borrow::Cow<'static, U>
346+
where
347+
T: FromEnv,
348+
U: std::borrow::ToOwned<Owned = T> + core::fmt::Debug,
349+
{
350+
type Error = T::Error;
351+
352+
fn inventory() -> Vec<&'static EnvItemInfo> {
353+
T::inventory()
354+
}
355+
356+
fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
357+
T::check_inventory()
358+
}
359+
360+
fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
361+
T::from_env().map(std::borrow::Cow::Owned)
362+
}
363+
}
364+
269365
/// Trait for loading primitives from the environment. These are simple types
270366
/// that should correspond to a single environment variable. It has been
271367
/// implemented for common integer types, [`String`], [`url::Url`],
@@ -331,6 +427,49 @@ pub trait FromEnvVar: core::fmt::Debug + Sized + 'static {
331427

332428
/// Load the primitive from the environment at the given variable.
333429
fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>>;
430+
431+
/// Load the primitive from the environment at the given variable. If the
432+
/// variable is unset or empty, return the default value.
433+
///
434+
/// This function will return an error if the environment variable is set
435+
/// but cannot be parsed.
436+
fn from_env_var_or(env_var: &str, default: Self) -> Result<Self, FromEnvErr<Self::Error>> {
437+
match Self::from_env_var(env_var) {
438+
Ok(v) => Ok(v),
439+
Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(default),
440+
Err(e) => Err(e),
441+
}
442+
}
443+
444+
/// Load the primitive from the environment at the given variable. If the
445+
/// variable is unset or empty, call the provided function to get the
446+
/// default value.
447+
///
448+
/// This function will return an error if the environment variable is set
449+
/// but cannot be parsed.
450+
fn from_env_var_or_else(
451+
env_var: &str,
452+
default: impl FnOnce() -> Self,
453+
) -> Result<Self, FromEnvErr<Self::Error>> {
454+
match Self::from_env_var(env_var) {
455+
Ok(v) => Ok(v),
456+
Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(default()),
457+
Err(e) => Err(e),
458+
}
459+
}
460+
461+
/// Load the primitive from the environment at the given variable. If the
462+
/// variable is unset or empty, return the value generated by
463+
/// [`Default::default`].
464+
///
465+
/// This function will return an error if the environment variable is set
466+
/// but cannot be parsed.
467+
fn from_env_var_or_default(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>>
468+
where
469+
Self: Default,
470+
{
471+
Self::from_env_var_or_else(env_var, Self::default)
472+
}
334473
}
335474

336475
impl<T> FromEnvVar for Option<T>
@@ -348,6 +487,40 @@ where
348487
}
349488
}
350489

490+
impl<T> FromEnvVar for Box<T>
491+
where
492+
T: FromEnvVar,
493+
{
494+
type Error = T::Error;
495+
496+
fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
497+
T::from_env_var(env_var).map(Box::new)
498+
}
499+
}
500+
501+
impl<T> FromEnvVar for std::sync::Arc<T>
502+
where
503+
T: FromEnvVar,
504+
{
505+
type Error = T::Error;
506+
507+
fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
508+
T::from_env_var(env_var).map(std::sync::Arc::new)
509+
}
510+
}
511+
512+
impl<T, U> FromEnvVar for std::borrow::Cow<'static, U>
513+
where
514+
T: FromEnvVar,
515+
U: std::borrow::ToOwned<Owned = T> + core::fmt::Debug,
516+
{
517+
type Error = T::Error;
518+
519+
fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
520+
T::from_env_var(env_var).map(std::borrow::Cow::Owned)
521+
}
522+
}
523+
351524
impl FromEnvVar for String {
352525
type Error = std::convert::Infallible;
353526

0 commit comments

Comments
 (0)