init4_bin_base/utils/
from_env.rs

1use std::{convert::Infallible, env::VarError, num::ParseIntError, str::FromStr};
2
3/// The `derive(FromEnv)` macro.
4///
5/// This macro generates a [`FromEnv`] implementation for the struct it is
6/// applied to. It will generate a `from_env` function that loads the struct
7/// from the environment. It will also generate an `inventory` function that
8/// returns a list of all environment variables that are required to load the
9/// struct.
10///
11/// The macro also generates a `____EnvError` type that captures errors that can
12/// occur when trying to create an instance of the struct from environment
13/// variables. This error type is used in the `FromEnv` trait implementation.
14///
15/// ## [`FromEnv`] vs [`FromEnvVar`]
16///
17/// While [`FromEnvVar`] deals with loading simple types from the environment,
18/// [`FromEnv`] is for loading complex types. It builds a struct from the
19/// environment, usually be delegating each field to a [`FromEnvVar`] or
20/// [`FromEnv`] implementation.
21///
22/// When using the derive macro, the props of the struct must implement
23/// [`FromEnv`] or [`FromEnvVar`]. Props that implement [`FromEnv`] contain all
24/// the information needed to load the struct from the environment. Props
25/// that implement [`FromEnvVar`] need additional information via attributes.
26///
27/// ## Attributes
28///
29/// The macro supports the following attributes:
30/// - `var = ""`: The name of the environment variable. **This is required if
31///   the prop implements [`FromEnvVar`] and forbidden if the prop implements
32///   [`FromEnv`].**
33/// - `desc = ""`: A description of the environment variable. **This is required
34///   if the prop implements [`FromEnvVar`] and forbidden if the prop
35///   implements [`FromEnv`].**
36/// - `optional`: Marks the prop as optional. This is currently only used in the
37///   generated `fn inventory`, and is informational.
38/// - `infallible`: Marks the prop as infallible. This means that the prop
39///   cannot fail to be parsed after the environment variable is loaded.
40/// - `skip`: Marks the prop as skipped. This means that the prop will not be
41///   loaded from the environment, and will be generated via
42///   `Default::default()` instead.
43///
44/// ## Conditions of use
45///
46/// There are a few usage requirements:
47///
48/// - Struct props MUST implement either [`FromEnvVar`] or [`FromEnv`].
49/// - If the prop implements [`FromEnvVar`], it must be tagged as follows:
50///     - `var = "ENV_VAR_NAME"`: The environment variable name to load.
51///     - `desc = "description"`: A description of the environment variable.
52/// - If the prop is an [`Option<T>`], it must be tagged as follows:
53///     - `optional`
54/// - If the prop's associated error type is [`Infallible`], it must be tagged
55///   as follows:
56///     - `infallible`
57/// - If used within this crate (`init4_bin_base`), the entire struct must be
58///   tagged with `#[from_env(crate)]` (see the [`SlotCalculator`] for an
59///   example).
60///
61/// # Examples
62///
63/// The following example shows how to use the macro:
64///
65/// ```
66/// # // I am unsure why we need this, as identical code works in
67/// # // integration tests. However, compile test fails without it.
68/// # #![allow(proc_macro_derive_resolution_fallback)]
69/// use init4_bin_base::utils::from_env::{FromEnv};
70///
71/// #[derive(Debug, FromEnv)]
72/// pub struct MyCfg {
73///     #[from_env(var = "COOL_DUDE", desc = "Some u8 we like :o)")]
74///     pub my_cool_u8: u8,
75///
76///     #[from_env(var = "CHUCK", desc = "Charles is a u64")]
77///     pub charles: u64,
78///
79///     #[from_env(
80///         var = "PERFECT",
81///         desc = "A bold and neat string",
82///         infallible,
83///     )]
84///     pub strings_cannot_fail: String,
85///
86///     #[from_env(
87///         var = "MAYBE_NOT_NEEDED",
88///         desc = "This is an optional string",
89///         optional,
90///         infallible,
91///     )]
92///     maybe_not_needed: Option<String>,
93/// }
94///
95/// #[derive(Debug, FromEnv)]
96/// pub struct MyBiggerCfg {
97///     #[from_env(var = "BIGGGG_CONFIGGGG", desc = "A big config", infallible)]
98///     pub big_config: String,
99///
100///     // Note that becuase `MyCfg` implements `FromEnv`, we do not need to
101///     // specify the `var` and `desc` attributes.
102///     pub little_config: MyCfg,
103/// }
104///
105/// // The [`FromEnv`] trait is implemented for the struct, and the struct can
106/// // be loaded from the environment.
107/// # fn use_it() {
108/// if let Err(missing) = MyBiggerCfg::check_inventory() {
109///     println!("Missing environment variables:");
110///     for var in missing {
111///         println!("{}: {}", var.var, var.description);
112///     }
113/// }
114/// # }
115/// ```
116///
117/// This will generate a [`FromEnv`] implementation for the struct, and a
118/// `MyCfgEnvError` type that is used to represent errors that can occur when
119/// loading from the environment. The error generated will look like this:
120///
121/// ```ignore
122/// pub enum MyCfgEnvError {
123///     MyCoolU8(<u8 as FromEnvVar>::Error),
124///     Charles(<u64 as FromEnvVar>::Error),
125///     // No variants for infallible errors.
126/// }
127/// ```
128///
129/// [`Infallible`]: std::convert::Infallible
130/// [`SlotCalculator`]: crate::utils::SlotCalculator
131/// [`FromEnv`]: crate::utils::from_env::FromEnv
132/// [`FromEnvVar`]: crate::utils::from_env::FromEnvVar
133pub use init4_from_env_derive::FromEnv;
134
135/// Details about an environment variable. This is used to generate
136/// documentation for the environment variables and by the [`FromEnv`] trait to
137/// check if necessary environment variables are present.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct EnvItemInfo {
140    /// The environment variable name.
141    pub var: &'static str,
142    /// A description of the environment variable function in the CFG.
143    pub description: &'static str,
144    /// Whether the environment variable is optional or not.
145    pub optional: bool,
146}
147
148/// Error type for loading from the environment. See the [`FromEnv`] trait for
149/// more information.
150#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
151pub enum FromEnvErr<Inner> {
152    /// The environment variable is missing.
153    #[error("error reading variable {0}: {1}")]
154    EnvError(String, VarError),
155    /// The environment variable is empty.
156    #[error("environment variable {0} is empty")]
157    Empty(String),
158    /// The environment variable is present, but the value could not be parsed.
159    #[error("failed to parse environment variable {0}")]
160    ParseError(#[from] Inner),
161}
162
163impl FromEnvErr<Infallible> {
164    /// Convert the error into another error type.
165    pub fn infallible_into<T>(self) -> FromEnvErr<T> {
166        match self {
167            Self::EnvError(s, e) => FromEnvErr::EnvError(s, e),
168            Self::Empty(s) => FromEnvErr::Empty(s),
169            Self::ParseError(_) => unreachable!(),
170        }
171    }
172}
173
174impl<Inner> FromEnvErr<Inner> {
175    /// Create a new error from another error type.
176    pub fn from<Other>(other: FromEnvErr<Other>) -> Self
177    where
178        Inner: From<Other>,
179    {
180        match other {
181            FromEnvErr::EnvError(s, e) => Self::EnvError(s, e),
182            FromEnvErr::Empty(s) => Self::Empty(s),
183            FromEnvErr::ParseError(e) => Self::ParseError(Inner::from(e)),
184        }
185    }
186
187    /// Map the error to another type. This is useful for converting the error
188    /// type to a different type, while keeping the other error information
189    /// intact.
190    pub fn map<New>(self, f: impl FnOnce(Inner) -> New) -> FromEnvErr<New> {
191        match self {
192            Self::EnvError(s, e) => FromEnvErr::EnvError(s, e),
193            Self::Empty(s) => FromEnvErr::Empty(s),
194            Self::ParseError(e) => FromEnvErr::ParseError(f(e)),
195        }
196    }
197
198    /// Missing env var.
199    pub fn env_err(var: &str, e: VarError) -> Self {
200        Self::EnvError(var.to_string(), e)
201    }
202
203    /// Empty env var.
204    pub fn empty(var: &str) -> Self {
205        Self::Empty(var.to_string())
206    }
207
208    /// Error while parsing.
209    pub const fn parse_error(err: Inner) -> Self {
210        Self::ParseError(err)
211    }
212}
213
214/// Convenience function for parsing a value from the environment, if present
215/// and non-empty.
216pub fn parse_env_if_present<T: FromStr>(env_var: &str) -> Result<T, FromEnvErr<T::Err>> {
217    let s = std::env::var(env_var).map_err(|e| FromEnvErr::env_err(env_var, e))?;
218
219    if s.is_empty() {
220        Err(FromEnvErr::empty(env_var))
221    } else {
222        s.parse().map_err(Into::into)
223    }
224}
225
226/// Trait for loading from the environment.
227///
228/// This trait is for structs or other complex objects, that need to be loaded
229/// from the environment. It expects that
230///
231/// - The struct is [`Sized`] and `'static`.
232/// - The struct elements can be parsed from strings.
233/// - Struct elements are at fixed env vars, known by the type at compile time.
234///
235/// As such, unless the env is modified, these are essentially static runtime
236/// values. We do not recommend using dynamic env vars.
237///
238/// ## [`FromEnv`] vs [`FromEnvVar`]
239///
240/// While [`FromEnvVar`] deals with loading simple types from the environment,
241/// [`FromEnv`] is for loading complex types. It builds a struct from the
242/// environment, usually be delegating each field to a [`FromEnvVar`] or
243/// [`FromEnv`] implementation. [`FromEnv`] effectively defines a singleton
244/// configuration object, which is produced by loading many env vars, while
245/// [`FromEnvVar`] defines a procedure for loading data from a single
246/// environment variable.
247///
248/// ## Implementing [`FromEnv`]
249///
250/// Please use the [`FromEnv`](macro@FromEnv) derive macro to implement this
251/// trait.
252///
253/// ## Note on error types
254///
255/// [`FromEnv`] and [`FromEnvVar`] are often deeply nested. This means that
256/// error types are often nested as well. To avoid this, we use a single error
257/// type [`FromEnvVar`] that wraps an inner error type. This allows us to
258/// ensure that env-related errors (e.g. missing env vars) are not lost in the
259/// recursive structure of parsing errors. Environment errors are always at the
260/// top level, and should never be nested. **Do not use [`FromEnvErr<T>`] as
261/// the `Error` associated type in [`FromEnv`].**
262///
263/// ```no_compile
264/// // Do not do this
265/// impl FromEnv for MyType {
266///     type Error = FromEnvErr<MyTypeErr>;
267/// }
268///
269/// // Instead do this:
270/// impl FromEnv for MyType {
271///    type Error = MyTypeErr;
272/// }
273/// ```
274///
275pub trait FromEnv: core::fmt::Debug + Sized + 'static {
276    /// Error type produced when loading from the environment.
277    type Error: core::error::Error + Clone;
278
279    /// Get the required environment variable names for this type.
280    ///
281    /// ## Note
282    ///
283    /// This MUST include the environment variable names for all fields in the
284    /// struct, including optional vars.
285    fn inventory() -> Vec<&'static EnvItemInfo>;
286
287    /// Get a list of missing environment variables.
288    ///
289    /// This will check all environment variables in the inventory, and return
290    /// a list of those that are non-optional and missing. This is useful for
291    /// reporting missing environment variables.
292    fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
293        let mut missing = Vec::new();
294        for var in Self::inventory() {
295            if std::env::var(var.var).is_err() && !var.optional {
296                missing.push(var);
297            }
298        }
299        if missing.is_empty() {
300            Ok(())
301        } else {
302            Err(missing)
303        }
304    }
305
306    /// Load from the environment.
307    fn from_env() -> Result<Self, FromEnvErr<Self::Error>>;
308}
309
310impl<T> FromEnv for Option<T>
311where
312    T: FromEnv,
313{
314    type Error = T::Error;
315
316    fn inventory() -> Vec<&'static EnvItemInfo> {
317        T::inventory()
318    }
319
320    fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
321        T::check_inventory()
322    }
323
324    fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
325        match T::from_env() {
326            Ok(v) => Ok(Some(v)),
327            Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(None),
328            Err(e) => Err(e),
329        }
330    }
331}
332
333impl<T> FromEnv for Box<T>
334where
335    T: FromEnv,
336{
337    type Error = T::Error;
338
339    fn inventory() -> Vec<&'static EnvItemInfo> {
340        T::inventory()
341    }
342
343    fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
344        T::check_inventory()
345    }
346
347    fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
348        T::from_env().map(Box::new)
349    }
350}
351
352impl<T> FromEnv for std::sync::Arc<T>
353where
354    T: FromEnv,
355{
356    type Error = T::Error;
357
358    fn inventory() -> Vec<&'static EnvItemInfo> {
359        T::inventory()
360    }
361
362    fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
363        T::check_inventory()
364    }
365
366    fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
367        T::from_env().map(std::sync::Arc::new)
368    }
369}
370
371impl<T, U> FromEnv for std::borrow::Cow<'static, U>
372where
373    T: FromEnv,
374    U: std::borrow::ToOwned<Owned = T> + core::fmt::Debug + ?Sized,
375{
376    type Error = T::Error;
377
378    fn inventory() -> Vec<&'static EnvItemInfo> {
379        T::inventory()
380    }
381
382    fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
383        T::check_inventory()
384    }
385
386    fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
387        T::from_env().map(std::borrow::Cow::Owned)
388    }
389}
390
391/// Trait for loading primitives from the environment. These are simple types
392/// that should correspond to a single environment variable. It has been
393/// implemented for common integer types, [`String`], [`url::Url`],
394/// [`tracing::Level`], and [`std::time::Duration`].
395///
396/// It aims to make [`FromEnv`] implementations easier to write, by providing a
397/// default implementation for common types.
398///
399/// ## Note on error types
400///
401/// [`FromEnv`] and [`FromEnvVar`] are often deeply nested. This means that
402/// error types are often nested as well. To avoid this, we use a single error
403/// type [`FromEnvVar`] that wraps an inner error type. This allows us to
404/// ensure that env-related errors (e.g. missing env vars) are not lost in the
405/// recursive structure of parsing errors. Environment errors are always at the
406/// top level, and should never be nested. **Do not use [`FromEnvErr<T>`] as
407/// the `Error` associated type in [`FromEnv`].**
408///
409/// ```no_compile
410/// // Do not do this
411/// impl FromEnv for MyType {
412///     type Error = FromEnvErr<MyTypeErr>;
413/// }
414///
415/// // Instead do this:
416/// impl FromEnv for MyType {
417///    type Error = MyTypeErr;
418/// }
419/// ```
420///
421/// ## Implementing [`FromEnv`]
422///
423/// [`FromEnvVar`] is a trait for loading simple types from the environment. It
424/// represents a type that can be loaded from a single environment variable. It
425/// is similar to [`FromStr`] and will usually be using an existing [`FromStr`]
426/// impl.
427///
428/// ```
429/// # use init4_bin_base::utils::from_env::{FromEnvVar, FromEnvErr};
430/// # use std::str::FromStr;
431/// # #[derive(Debug)]
432/// # pub struct MyCoolType;
433/// # impl std::str::FromStr for MyCoolType {
434/// #    type Err = std::convert::Infallible;
435/// #    fn from_str(s: &str) -> Result<Self, Self::Err> {
436/// #        Ok(MyCoolType)
437/// #    }
438/// # }
439///
440/// // We can re-use the `FromStr` implementation for our `FromEnvVar` impl.
441/// impl FromEnvVar for MyCoolType {
442///     type Error = <MyCoolType as FromStr>::Err;
443///
444///     fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>>
445///     {
446///         String::from_env_var(env_var).unwrap().parse().map_err(Into::into)
447///     }
448/// }
449/// ```
450pub trait FromEnvVar: core::fmt::Debug + Sized + 'static {
451    /// Error type produced when parsing the primitive.
452    type Error: core::error::Error;
453
454    /// Load the primitive from the environment at the given variable.
455    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>>;
456
457    /// Load the primitive from the environment at the given variable. If the
458    /// variable is unset or empty, return the default value.
459    ///
460    /// This function will return an error if the environment variable is set
461    /// but cannot be parsed.
462    fn from_env_var_or(env_var: &str, default: Self) -> Result<Self, FromEnvErr<Self::Error>> {
463        match Self::from_env_var(env_var) {
464            Ok(v) => Ok(v),
465            Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(default),
466            Err(e) => Err(e),
467        }
468    }
469
470    /// Load the primitive from the environment at the given variable. If the
471    /// variable is unset or empty, call the provided function to get the
472    /// default value.
473    ///
474    /// This function will return an error if the environment variable is set
475    /// but cannot be parsed.
476    fn from_env_var_or_else(
477        env_var: &str,
478        default: impl FnOnce() -> Self,
479    ) -> Result<Self, FromEnvErr<Self::Error>> {
480        match Self::from_env_var(env_var) {
481            Ok(v) => Ok(v),
482            Err(FromEnvErr::Empty(_)) | Err(FromEnvErr::EnvError(_, _)) => Ok(default()),
483            Err(e) => Err(e),
484        }
485    }
486
487    /// Load the primitive from the environment at the given variable. If the
488    /// variable is unset or empty, return the value generated by
489    /// [`Default::default`].
490    ///
491    /// This function will return an error if the environment variable is set
492    /// but cannot be parsed.
493    fn from_env_var_or_default(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>>
494    where
495        Self: Default,
496    {
497        Self::from_env_var_or_else(env_var, Self::default)
498    }
499}
500
501impl<T> FromEnvVar for Option<T>
502where
503    T: FromEnvVar,
504{
505    type Error = T::Error;
506
507    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
508        match std::env::var(env_var) {
509            Ok(s) if s.is_empty() => Ok(None),
510            Ok(_) => T::from_env_var(env_var).map(Some),
511            Err(_) => Ok(None),
512        }
513    }
514}
515
516impl<T> FromEnvVar for Box<T>
517where
518    T: FromEnvVar,
519{
520    type Error = T::Error;
521
522    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
523        T::from_env_var(env_var).map(Box::new)
524    }
525}
526
527impl<T> FromEnvVar for std::sync::Arc<T>
528where
529    T: FromEnvVar,
530{
531    type Error = T::Error;
532
533    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
534        T::from_env_var(env_var).map(std::sync::Arc::new)
535    }
536}
537
538impl<T, U> FromEnvVar for std::borrow::Cow<'static, U>
539where
540    T: FromEnvVar,
541    U: std::borrow::ToOwned<Owned = T> + core::fmt::Debug + ?Sized,
542{
543    type Error = T::Error;
544
545    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
546        T::from_env_var(env_var).map(std::borrow::Cow::Owned)
547    }
548}
549
550impl FromEnvVar for String {
551    type Error = std::convert::Infallible;
552
553    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
554        std::env::var(env_var).map_err(|_| FromEnvErr::empty(env_var))
555    }
556}
557
558impl FromEnvVar for std::time::Duration {
559    type Error = ParseIntError;
560
561    fn from_env_var(s: &str) -> Result<Self, FromEnvErr<Self::Error>> {
562        u64::from_env_var(s).map(Self::from_millis)
563    }
564}
565
566impl<T> FromEnvVar for Vec<T>
567where
568    T: From<String> + core::fmt::Debug + 'static,
569{
570    type Error = Infallible;
571
572    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
573        let s = std::env::var(env_var).map_err(|e| FromEnvErr::env_err(env_var, e))?;
574        if s.is_empty() {
575            return Ok(vec![]);
576        }
577        Ok(s.split(',')
578            .map(str::to_string)
579            .map(Into::into)
580            .collect::<Vec<_>>())
581    }
582}
583
584macro_rules! impl_for_parseable {
585    ($($t:ty),*) => {
586        $(
587            impl FromEnvVar for $t {
588                type Error = <$t as FromStr>::Err;
589
590                fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
591                    parse_env_if_present(env_var)
592                }
593            }
594        )*
595    }
596}
597
598impl_for_parseable!(
599    u8,
600    u16,
601    u32,
602    u64,
603    u128,
604    usize,
605    i8,
606    i16,
607    i32,
608    i64,
609    i128,
610    isize,
611    url::Url,
612    tracing::Level
613);
614
615#[cfg(feature = "alloy")]
616impl_for_parseable!(
617    alloy::primitives::Address,
618    alloy::primitives::Bytes,
619    alloy::primitives::U256
620);
621
622#[cfg(feature = "alloy")]
623impl<const N: usize> FromEnvVar for alloy::primitives::FixedBytes<N> {
624    type Error = <alloy::primitives::FixedBytes<N> as FromStr>::Err;
625
626    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
627        parse_env_if_present(env_var)
628    }
629}
630
631impl FromEnvVar for bool {
632    type Error = std::str::ParseBoolError;
633
634    fn from_env_var(env_var: &str) -> Result<Self, FromEnvErr<Self::Error>> {
635        let s: String = std::env::var(env_var).map_err(|e| FromEnvErr::env_err(env_var, e))?;
636        Ok(!s.is_empty())
637    }
638}
639
640#[cfg(test)]
641mod test {
642    use std::{borrow::Cow, time::Duration};
643
644    use super::*;
645
646    fn set<T>(env: &str, val: &T)
647    where
648        T: ToString + ?Sized,
649    {
650        std::env::set_var(env, val.to_string());
651    }
652
653    fn load_expect_err<T>(env: &str, err: FromEnvErr<T::Error>)
654    where
655        T: FromEnvVar,
656        T::Error: PartialEq,
657    {
658        let res = T::from_env_var(env).unwrap_err();
659        assert_eq!(res, err);
660    }
661
662    fn test<T>(env: &str, val: T)
663    where
664        T: ToString + FromEnvVar + PartialEq + std::fmt::Debug,
665    {
666        set(env, &val);
667
668        let res = T::from_env_var(env).unwrap();
669        assert_eq!(res, val);
670    }
671
672    fn test_expect_err<T, U>(env: &str, value: U, err: FromEnvErr<T::Error>)
673    where
674        T: FromEnvVar,
675        U: ToString,
676        T::Error: PartialEq,
677    {
678        set(env, &value);
679        load_expect_err::<T>(env, err);
680    }
681
682    #[test]
683    fn test_primitives() {
684        test("U8", 42u8);
685        test("U16", 42u16);
686        test("U32", 42u32);
687        test("U64", 42u64);
688        test("U128", 42u128);
689        test("Usize", 42usize);
690        test("I8", 42i8);
691        test("I8-NEG", -42i16);
692        test("I16", 42i16);
693        test("I32", 42i32);
694        test("I64", 42i64);
695        test("I128", 42i128);
696        test("Isize", 42isize);
697        test("String", "hello".to_string());
698        test("Url", url::Url::parse("http://example.com").unwrap());
699        test("Level", tracing::Level::INFO);
700    }
701
702    #[test]
703    fn test_duration() {
704        let amnt = 42;
705        let val = Duration::from_millis(42);
706
707        set("Duration", &amnt);
708        let res = Duration::from_env_var("Duration").unwrap();
709
710        assert_eq!(res, val);
711    }
712
713    #[test]
714    fn test_a_few_errors() {
715        test_expect_err::<u8, _>(
716            "U8_",
717            30000u16,
718            FromEnvErr::parse_error("30000".parse::<u8>().unwrap_err()),
719        );
720
721        test_expect_err::<u8, _>("U8_", "", FromEnvErr::empty("U8_"));
722    }
723
724    #[test]
725    fn is_cow_str_from_env_var() {
726        let s = "hello";
727        set("COW", s);
728        let res: Cow<'static, str> = Cow::from_env_var("COW").unwrap();
729        assert_eq!(res, Cow::<'static, str>::Owned(s.to_owned()));
730    }
731}