init4_bin_base/utils/
from_env.rs

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