init4_bin_base/utils/
from_env.rs

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