drogue_bazaar/core/config/
mod.rs

1mod csv;
2
3pub use csv::*;
4
5use serde::Deserialize;
6use std::collections::HashMap;
7
8/// A default setup to extract configuration from environment variables.
9///
10/// The default setup uses `__` (double underscore) as a delimiter.
11///
12/// ```
13/// use drogue_bazaar::core::config::ConfigFromEnv;
14///
15/// #[derive(serde::Deserialize)]
16/// struct Config {
17///     sub: SubConfig,
18///     my_str: String,
19/// }
20///
21/// #[derive(serde::Deserialize)]
22/// struct SubConfig {
23///     my_str: String,
24///     #[serde(default)]
25///     my_bool: bool,
26///     #[serde(default)]
27///     my_opt_int: Option<u32>,
28/// }
29///
30/// fn run() -> anyhow::Result<()> {
31///     /*
32///     Assume the following env-vars are set:
33///         MY_STR = abc
34///         SUB__MY_STR = def
35///     */
36///     let config = Config::from_env()?;
37///
38///     /* The struct would be: {
39///         my_str: "abc",
40///         sub: {
41///             my_str: "def",
42///             my_bool: false,
43///             my_opt_int: None,
44///         }
45///     } */
46///
47///     Ok(())
48/// }
49/// ```
50pub trait ConfigFromEnv<'de>: Sized + Deserialize<'de> {
51    /// Get a configuration from the env-vars.
52    fn from_env() -> Result<Self, config::ConfigError> {
53        Self::from(config::Environment::default())
54    }
55
56    /// Get a configuration from the env-vars, prefixing all with the provided prefix **plus**
57    /// the separator.
58    fn from_env_prefix<S: AsRef<str>>(prefix: S) -> Result<Self, config::ConfigError> {
59        Self::from(config::Environment::with_prefix(prefix.as_ref()))
60    }
61
62    fn from(env: config::Environment) -> Result<Self, config::ConfigError>;
63
64    fn from_set<K, V>(set: HashMap<K, V>) -> Result<Self, config::ConfigError>
65    where
66        K: Into<String>,
67        V: Into<String>,
68    {
69        let set = set.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
70        Self::from(config::Environment::default().source(Some(set)))
71    }
72}
73
74impl<'de, T: Deserialize<'de> + Sized> ConfigFromEnv<'de> for T {
75    fn from(env: config::Environment) -> Result<T, config::ConfigError> {
76        let env = env.try_parsing(true).separator("__");
77
78        let cfg = config::Config::builder().add_source(env);
79        cfg.build()?.try_deserialize()
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86    use config::Environment;
87    use serde::Deserialize;
88    use std::collections::HashMap;
89
90    #[test]
91    fn test_prefix() {
92        #[derive(Debug, Deserialize)]
93        struct Foo {
94            pub bar: String,
95            pub r#bool: bool,
96        }
97
98        let mut env = HashMap::<String, String>::new();
99        env.insert("FOO__BAR".into(), "baz".into());
100        env.insert("FOO__BOOL".into(), "true".into());
101
102        let foo = <Foo as ConfigFromEnv>::from(Environment::with_prefix("FOO").source(Some(env)))
103            .unwrap();
104        assert_eq!(foo.bar, "baz");
105        assert_eq!(foo.r#bool, true);
106    }
107
108    #[test]
109    fn test_nested() {
110        #[derive(Debug, Deserialize)]
111        struct Foo {
112            #[serde(default)]
113            pub bar: Option<Bar>,
114        }
115        #[derive(Debug, Deserialize)]
116        struct Bar {
117            pub baz: Baz,
118        }
119        #[derive(Debug, Deserialize)]
120        struct Baz {
121            pub value: String,
122        }
123
124        let mut env = HashMap::<String, String>::new();
125        env.insert("FOO__BAR__BAZ__VALUE".into(), "s1".into());
126
127        let foo =
128            <Foo as ConfigFromEnv>::from(Environment::default().prefix("FOO").source(Some(env)))
129                .unwrap();
130
131        assert_eq!(foo.bar.unwrap().baz.value, "s1");
132    }
133}