Skip to main content

azalia_config/
env.rs

1// 🐻‍❄️🪚 azalia: Noelware's Rust commons library.
2// Copyright (c) 2024-2025 Noelware, LLC. <team@noelware.org>
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in all
12// copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20// SOFTWARE.
21
22//! Traits, types, and utilities when dealing with system environment variables.
23
24use std::{
25    char::ParseCharError,
26    collections::{BTreeMap, BTreeSet, HashSet},
27    convert::Infallible,
28    env::{VarError, remove_var},
29    ffi::OsStr,
30    fmt::{Debug, Display},
31    hash::{Hash, Hasher},
32    marker::PhantomData,
33    num::{
34        NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32,
35        NonZeroU64, NonZeroU128, NonZeroUsize, ParseFloatError, ParseIntError,
36    },
37    rc::Rc,
38    str::ParseBoolError,
39};
40
41/// When reading from the system environment variables, types might want to convert
42/// the value from `getenv` to something useful and this is where this trait comes in.
43pub trait FromEnvValue: Sized {
44    /// Implicit conversion between a environment variable's value to `Self::Output`.
45    fn from_env_value(value: String) -> Self;
46}
47
48impl FromEnvValue for String {
49    fn from_env_value(value: String) -> Self {
50        value
51    }
52}
53
54/// Analognous to [`FromEnvValue`] but it can fail if the given value is not right.
55pub trait TryFromEnvValue: Sized {
56    /// Error type.
57    type Error;
58
59    // TODO(@auguwu):
60    // add `type Output = Self` once GAT defaults are stablised (probably never)
61
62    /// Implicit conversion between a environment variable's value to `Ok(Self::Output)`
63    /// if successful.
64    fn try_from_env_value(value: String) -> Result<Self, Self::Error>;
65}
66
67impl<K: TryFromEnvValue + Eq + Hash, V: TryFromEnvValue> TryFromEnvValue for std::collections::HashMap<K, V> {
68    type Error = MapTryFromEnvError<K::Error, V::Error>;
69
70    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
71        let elements = value.split(',');
72        let size_hint = elements.size_hint().0;
73        let mut map = std::collections::HashMap::with_capacity(size_hint);
74
75        for line in elements {
76            if let Some((key, value)) = line.split_once('=') {
77                if value.contains('=') {
78                    continue;
79                }
80
81                let key = K::try_from_env_value(key.to_owned()).map_err(MapTryFromEnvError::Key)?;
82                let value = V::try_from_env_value(value.to_owned()).map_err(MapTryFromEnvError::Value)?;
83
84                map.insert(key, value);
85            }
86        }
87
88        Ok(map)
89    }
90}
91
92impl<K: TryFromEnvValue + Ord, V: TryFromEnvValue> TryFromEnvValue for BTreeMap<K, V> {
93    type Error = MapTryFromEnvError<K::Error, V::Error>;
94
95    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
96        let elements = value.split(',');
97        let mut map = BTreeMap::new();
98
99        for line in elements {
100            if let Some((key, value)) = line.split_once('=') {
101                if value.contains('=') {
102                    continue;
103                }
104
105                let key = K::try_from_env_value(key.to_owned()).map_err(MapTryFromEnvError::Key)?;
106                let value = V::try_from_env_value(value.to_owned()).map_err(MapTryFromEnvError::Value)?;
107
108                map.insert(key, value);
109            }
110        }
111
112        Ok(map)
113    }
114}
115
116/// Error variant for <code>impl [`TryFromEnvValue`] for [`std::collections::HashMap`]<K, V></code>
117/// and <code>impl [`TryFromEnvValue`] for [`std::collections::BTreeMap`]<K, V></code>.
118#[derive(Debug)]
119pub enum MapTryFromEnvError<K, V> {
120    Key(K),
121    Value(V),
122}
123
124impl<K: Display, V: Display> Display for MapTryFromEnvError<K, V> {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        match self {
127            Self::Key(s) => Display::fmt(s, f),
128            Self::Value(v) => Display::fmt(v, f),
129        }
130    }
131}
132
133impl<K: std::error::Error + 'static, V: std::error::Error + 'static> std::error::Error for MapTryFromEnvError<K, V> {
134    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135        match self {
136            Self::Key(k) => Some(k),
137            Self::Value(v) => Some(v),
138        }
139    }
140}
141
142impl<T: TryFromEnvValue + Eq + Hash> TryFromEnvValue for HashSet<T> {
143    type Error = T::Error;
144
145    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
146        value
147            .split(',')
148            .map(|v| T::try_from_env_value(v.to_owned()))
149            .collect::<Result<_, T::Error>>()
150    }
151}
152
153impl<T: TryFromEnvValue + Ord> TryFromEnvValue for BTreeSet<T> {
154    type Error = T::Error;
155
156    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
157        value
158            .split(',')
159            .map(|v| T::try_from_env_value(v.to_owned()))
160            .collect::<Result<_, T::Error>>()
161    }
162}
163
164impl<T: TryFromEnvValue> TryFromEnvValue for Vec<T> {
165    type Error = T::Error;
166
167    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
168        value
169            .split(',')
170            .map(|v| T::try_from_env_value(v.to_owned()))
171            .collect::<Result<Vec<_>, T::Error>>()
172    }
173}
174
175#[cfg(feature = "tracing")]
176#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
177impl TryFromEnvValue for tracing::Level {
178    type Error = InvalidLevel;
179
180    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
181        match &*value.to_ascii_lowercase() {
182            "trace" => Ok(tracing::Level::TRACE),
183            "info" | "information" => Ok(tracing::Level::INFO),
184            "debug" => Ok(tracing::Level::DEBUG),
185            "warn" | "warning" => Ok(tracing::Level::WARN),
186            "error" => Ok(tracing::Level::ERROR),
187            level => Err(InvalidLevel(level.to_owned())),
188        }
189    }
190}
191
192#[cfg(feature = "tracing")]
193#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
194#[derive(Debug)]
195/// A invalid level was given from the [`TryFromEnvValue`] implementation
196/// for [`tracing::Level`]
197pub struct InvalidLevel(String);
198
199#[cfg(feature = "tracing")]
200#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
201impl std::fmt::Display for InvalidLevel {
202    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
203        write!(f, "invalid log level: '{}'", self.0)
204    }
205}
206
207#[cfg(feature = "tracing")]
208#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "tracing")))]
209impl std::error::Error for InvalidLevel {}
210
211macro_rules! impl_try_from_env {
212    ($($(#[$meta:meta])* $Ty:ty: $Error:ty;)*) => {
213        $(
214            $(#[$meta])*
215            /// This implementation will forward to the [`FromStr`] implementation
216            /// of the concrete type.
217            impl $crate::env::TryFromEnvValue for $Ty {
218                type Error = $Error;
219
220                fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
221                    value.parse()
222                }
223            }
224        )*
225    };
226}
227
228impl_try_from_env!(
229    bool: ParseBoolError;
230    char: ParseCharError;
231
232    f32: ParseFloatError;
233    f64: ParseFloatError;
234
235    NonZeroI8: ParseIntError;
236    NonZeroI16: ParseIntError;
237    NonZeroI32: ParseIntError;
238    NonZeroI64: ParseIntError;
239    NonZeroI128: ParseIntError;
240    NonZeroIsize: ParseIntError;
241
242    i8: ParseIntError;
243    i16: ParseIntError;
244    i32: ParseIntError;
245    i64: ParseIntError;
246    i128: ParseIntError;
247    isize: ParseIntError;
248
249    NonZeroU8: ParseIntError;
250    NonZeroU16: ParseIntError;
251    NonZeroU32: ParseIntError;
252    NonZeroU64: ParseIntError;
253    NonZeroU128: ParseIntError;
254    NonZeroUsize: ParseIntError;
255
256    u8: ParseIntError;
257    u16: ParseIntError;
258    u32: ParseIntError;
259    u64: ParseIntError;
260    u128: ParseIntError;
261    usize: ParseIntError;
262
263    std::path::PathBuf: Infallible;
264
265    #[cfg(feature = "sentry")]
266    #[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "sentry")))]
267    sentry_types::Dsn: sentry_types::ParseDsnError;
268
269    #[cfg(feature = "url")]
270    #[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "url")))]
271    url::Url: url::ParseError;
272);
273
274impl<T: FromEnvValue> TryFromEnvValue for T {
275    type Error = Infallible;
276
277    fn try_from_env_value(value: String) -> Result<Self, Self::Error> {
278        Ok(T::from_env_value(value))
279    }
280}
281
282/// Parses an environment variable from a [`FromEnvValue`] implementation.
283pub fn parse<K: Into<String>, V: FromEnvValue>(key: K) -> Result<V, VarError> {
284    std::env::var(key.into()).map(V::from_env_value)
285}
286
287/// Parses an environment variable from a [`TryFromEnvValue`] implementation.
288pub fn try_parse<K: Into<String>, V: TryFromEnvValue>(key: K) -> Result<V, TryParseError<V::Error>> {
289    match std::env::var(key.into()) {
290        Ok(value) => V::try_from_env_value(value).map_err(TryParseError::Parse),
291        Err(e) => Err(TryParseError::System(e)),
292    }
293}
294
295/// Analogous to [`try_parse`] but uses a closure to compute the default value.
296pub fn try_parse_or<K: Into<String>, V: TryFromEnvValue>(
297    key: K,
298    default: impl FnOnce() -> V,
299) -> Result<V, TryParseError<V::Error>> {
300    match try_parse(key) {
301        Ok(value) => Ok(value),
302        Err(TryParseError::System(std::env::VarError::NotPresent)) => Ok(default()),
303        Err(e) => Err(e),
304    }
305}
306
307/// Analogous to [`try_parse`] but uses a default value if the environment variable was not found.
308pub fn try_parse_or_else<K: Into<String>, V: TryFromEnvValue>(
309    key: K,
310    default: V,
311) -> Result<V, TryParseError<V::Error>> {
312    match std::env::var(key.into()) {
313        Ok(value) => V::try_from_env_value(value).map_err(TryParseError::Parse),
314        Err(VarError::NotPresent) => Ok(default),
315        Err(e) => Err(TryParseError::System(e)),
316    }
317}
318
319/// Anlogous to [`try_parse`] but returns a <code>[`Option`]\<V\></code> instead.
320///
321/// When the environment variable by the name of `key` doesn't exist, it'll return `None`.
322pub fn try_parse_optional<K: Into<String>, V: TryFromEnvValue>(key: K) -> Result<Option<V>, TryParseError<V::Error>> {
323    match std::env::var(key.into()) {
324        Ok(value) => V::try_from_env_value(value).map(Some).map_err(TryParseError::Parse),
325        Err(VarError::NotPresent) => Ok(None),
326        Err(e) => Err(TryParseError::System(e)),
327    }
328}
329
330/// Error variant for [`try_parse`].
331#[derive(Debug)]
332pub enum TryParseError<V> {
333    System(VarError),
334    Parse(V),
335}
336
337impl<V: Display> Display for TryParseError<V> {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        match self {
340            TryParseError::System(s) => Display::fmt(s, f),
341            TryParseError::Parse(s) => Display::fmt(s, f),
342        }
343    }
344}
345
346impl<V: std::error::Error + 'static> std::error::Error for TryParseError<V> {
347    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
348        match self {
349            Self::System(v) => Some(v),
350            Self::Parse(v) => Some(v),
351        }
352    }
353}
354
355/// Represents a trait that allows conversion of a collection of system environment
356/// variables for structs or enumerations.
357///
358/// ## Example
359/// ```ignore
360/// use azalia_config::env::FromEnv;
361///
362/// pub struct Config {
363///     pub a: String,
364/// }
365///
366/// impl FromEnv for Config {
367///     fn from_env() -> Self {
368///         Config { a: Default::default() }
369///     }
370/// }
371///
372/// let config = Config::from_env();
373/// // => Config { a: "" }
374/// ```
375#[deprecated(
376    since = "0.1.0",
377    note = "trait is no longer needed as of azalia v0.1.0 (preparation of crates.io ver)"
378)]
379pub trait FromEnv: Sized {
380    /// Implicit conversion to return `Self`.
381    fn from_env() -> Self;
382}
383
384/// Analogous to [`FromEnv`] but falliable.
385///
386/// ***This is also a derive macro when the `macros` feature is enabled:
387/// <code>#[derive([`TryFromEnv`][derive-redirect])</code>***
388///
389/// ## Notes
390/// The **#[derive([`TryFromEnv`][derive-redirect])]** macro is unstable! Add
391/// the `unstable` crate feature to use it.
392///
393/// For the derive macro, specifying the error type is required:
394///
395/// ```ignore
396/// #[derive(TryFromEnv)]
397/// #[env(Box<dyn std::error::Error>)]
398/// ```
399///
400/// Since the procedural macro would have no idea on how to propagate errors
401/// based off the context, it is required.
402///
403/// ## Example
404/// ```ignore
405/// use azalia_config::env::TryFromEnv;
406///
407/// #[derive(TryFromEnv)]
408/// #[env(Box<dyn std::error::Error>, prefix = "APP")]
409/// pub struct Config {
410///     #[env("A", default)]
411///     pub a: String,
412/// }
413///
414/// let config = Config::try_from_env();
415/// assert!(config.is_ok());
416/// ```
417///
418/// [derive-redirect]: derive.TryFromEnv.html
419pub trait TryFromEnv: Sized {
420    /// Error type
421    type Error;
422
423    /// Implicit conversion to return a result of `Self`.
424    fn try_from_env() -> Result<Self, Self::Error>;
425}
426
427#[allow(deprecated)]
428impl<T: FromEnv> TryFromEnv for T {
429    type Error = Infallible;
430
431    fn try_from_env() -> Result<Self, Self::Error> {
432        Ok(T::from_env())
433    }
434}
435
436/// A guard type that drops the environment variable once the scope
437/// is being dropped.
438///
439/// This type is [`!Send`](std::marker::Send) and [`!Sync`](std::marker::Sync) as it is unsafe
440/// to drop environment variables in different threads.
441///
442/// ## Safety
443///
444/// <div class="warning">
445///
446/// As of Rust edition **2024**, `{set,remove}_var` is considered unsafe and
447/// will call either way but this is a fair warning when using in a non-testing
448/// environment.
449///
450/// </div>
451///
452/// This is only meant in testing environments so it is not our issue to deal
453/// with if anything outside of testing goes unsound.
454pub struct EnvGuard {
455    name: String,
456    _non_send_and_sync: PhantomData<Rc<()>>,
457}
458
459impl EnvGuard {
460    /// Enters the guard and sets the name of the environment variable
461    /// to the value of **1**.
462    ///
463    /// ## Safety
464    /// Environment variables are inheritely unsafe to test! See the [`EnvGuard`]'s
465    /// Safety documentation about it.
466    ///
467    /// ## Example
468    /// ```
469    /// use azalia_config::env::EnvGuard;
470    /// use std::env;
471    ///
472    /// // The guard lives on this scope
473    /// {
474    ///     let _guard = EnvGuard::enter("HELLO");
475    ///     assert!(env::var("HELLO").is_ok());
476    /// }
477    ///
478    /// // and it'll be removed when dropped from scope
479    /// assert!(env::var("HELLO").is_err());
480    /// ```
481    pub fn enter(name: impl Into<String>) -> Self {
482        EnvGuard::enter_with(name, "1")
483    }
484
485    /// Enters the guard and sets the **name** to a correspondant **value** into
486    /// the system environment variables.
487    ///
488    /// ## Safety
489    /// Environment variables are inheritely unsafe to test! See the [`EnvGuard`]'s
490    /// Safety documentation about it.
491    ///
492    /// ## Example
493    /// ```
494    /// use azalia_config::env::EnvGuard;
495    /// use std::env;
496    ///
497    /// // The guard lives on this scope
498    /// {
499    ///     let guard = EnvGuard::enter_with("HELLO", "world");
500    ///     assert_eq!(env::var("HELLO"), Ok(String::from("world")));
501    /// }
502    ///
503    /// // and it'll be removed when dropped from scope
504    /// assert!(env::var("HELLO").is_err());
505    /// ```
506    pub fn enter_with(name: impl Into<String>, value: impl AsRef<OsStr>) -> Self {
507        let name = name.into();
508
509        // Safety: rationale in Safety section of the struct
510        unsafe { std::env::set_var(&name, value) };
511        EnvGuard {
512            name,
513            _non_send_and_sync: PhantomData,
514        }
515    }
516}
517
518impl PartialEq for EnvGuard {
519    fn eq(&self, other: &Self) -> bool {
520        self.name == other.name
521    }
522}
523
524impl Eq for EnvGuard {}
525
526impl Hash for EnvGuard {
527    fn hash<H: Hasher>(&self, state: &mut H) {
528        self.name.hash(state);
529    }
530}
531
532impl Drop for EnvGuard {
533    fn drop(&mut self) {
534        unsafe { remove_var(&self.name) }
535    }
536}
537
538/// A guard analogous to [`EnvGuard`] but holds a set of guards to be dropped
539/// once the scope is finished.
540///
541/// This type is [`!Send`](std::marker::Send) and [`!Sync`](std::marker::Sync) as it is unsafe
542/// to drop environment variables in different threads.
543///
544/// ## Safety
545///
546/// <div class="warning">
547///
548/// As of Rust edition **2024**, `{set,remove}_var` is considered unsafe and
549/// will call either way but this is a fair warning when using in a non-testing
550/// environment.
551///
552/// </div>
553///
554/// This is only meant in testing environments so it is not our issue to deal
555/// with if anything outside of testing goes unsound.
556pub struct MultipleEnvGuard {
557    _variables: HashSet<EnvGuard>,
558    _non_send_sync: PhantomData<Rc<()>>,
559}
560
561impl MultipleEnvGuard {
562    /// Enters the guard and sets a iterator of `(key, value)` as [`EnvGuard`]s. On [`Drop`], it'll
563    /// call [`remove_var`] of the specified environment variables.
564    ///
565    /// ## Safety
566    /// Environment variables are inheritely unsafe to test! See the [`MultipleEnvGuard`]'s
567    /// Safety documentation about it.
568    ///
569    /// ## Example
570    /// ```
571    /// use azalia_config::env::MultipleEnvGuard;
572    /// use std::env::var;
573    ///
574    /// {
575    ///     let _guard = MultipleEnvGuard::enter([
576    ///         ("HELLO", "world"),
577    ///         ("NOEL_IS_CUTE", "true")
578    ///     ]);
579    ///
580    ///     assert_eq!(var("HELLO"), Ok(String::from("world")));
581    ///     assert_ne!(var("NOEL_IS_CUTE"), Ok(String::from("false")));
582    /// }
583    ///
584    /// assert!(var("HELLO").is_err());
585    /// assert!(var("NOEL_IS_CUTE").is_err());
586    /// ```
587    pub fn enter(values: impl IntoIterator<Item = (impl Into<String>, impl AsRef<OsStr>)>) -> Self {
588        MultipleEnvGuard {
589            _non_send_sync: PhantomData,
590            _variables: values
591                .into_iter()
592                .map(|(key, value)| EnvGuard::enter_with(key, value))
593                .collect(),
594        }
595    }
596}
597
598/// Enters the [`EnvGuard`] by setting **key** to **1** and calls `f`.
599///
600/// ## Safety
601/// Environment variables are inheritely unsafe to test! See the [`EnvGuard`]'s
602/// Safety documentation about it.
603pub fn enter(key: impl Into<String>, f: impl FnOnce()) {
604    let _guard = EnvGuard::enter(key);
605    f()
606}
607
608/// Enters the [`EnvGuard`] by setting **key** to the **value** and calls `f`.
609///
610/// ## Safety
611/// Environment variables are inheritely unsafe to test! See the [`EnvGuard`]'s
612/// Safety documentation about it.
613pub fn enter_with(key: impl Into<String>, value: impl AsRef<OsStr>, f: impl FnOnce()) {
614    let _guard = EnvGuard::enter_with(key, value);
615    f()
616}
617
618/// Enters the [`EnvGuard`] by setting multiple environment variables via an iterator
619/// implementation and calls **f**.
620///
621/// ## Safety
622/// Environment variables are inheritely unsafe to test! See the [`MultipleEnvGuard`]'s
623/// Safety documentation about it.
624pub fn enter_multiple(iter: impl IntoIterator<Item = (impl Into<String>, impl AsRef<OsStr>)>, f: impl FnOnce()) {
625    let _guard = MultipleEnvGuard::enter(iter);
626    f()
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
633
634    // this is a hack since we export `test` if `--features unstable` is enabled
635    #[test]
636    fn drop_multiple_env_guard() {
637        {
638            let mut guards = HashSet::new();
639            guards.insert(EnvGuard::enter("HELLO"));
640
641            assert!(std::env::var("HELLO").is_ok());
642        }
643
644        assert!(std::env::var("HELLO").is_err());
645
646        {
647            let _guard = MultipleEnvGuard::enter([("HELLO", "world")]);
648            assert!(std::env::var("HELLO").is_ok());
649        }
650
651        assert!(std::env::var("HELLO").is_err());
652    }
653
654    #[test]
655    fn map_try_from_env_value() {
656        assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("hello=world".into()).is_ok());
657        assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
658        assert!(<HashMap<String, String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
659        assert!(
660            <HashMap<String, String> as TryFromEnvValue>::try_from_env_value(
661                "hello=world,weow=fluff;wwww,s=true".into()
662            )
663            .is_ok()
664        );
665
666        assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("hello=world".into()).is_ok());
667        assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
668        assert!(<BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
669        assert!(
670            <BTreeMap<String, String> as TryFromEnvValue>::try_from_env_value(
671                "hello=world,weow=fluff;wwww,s=true".into()
672            )
673            .is_ok()
674        );
675    }
676
677    #[test]
678    fn set_try_from_env_value() {
679        assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("hello,world".into()).is_ok());
680        assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
681        assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
682        assert!(<HashSet<String> as TryFromEnvValue>::try_from_env_value("hello,world,weow,fluff".into()).is_ok());
683
684        assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("hello,world".into()).is_ok());
685        assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("helloworld".into()).is_ok());
686        assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("".into()).is_ok());
687        assert!(<BTreeSet<String> as TryFromEnvValue>::try_from_env_value("hello,world,weow,fluff".into()).is_ok());
688    }
689}