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}