envy_toml/
lib.rs

1pub struct EnvyToml;
2
3impl EnvyToml {
4    pub fn from_str<T>(s: &str) -> Result<T, Box<dyn std::error::Error>>
5    where
6        T: serde::de::DeserializeOwned,
7    {
8        let toml = toml::from_str::<toml::Value>(s)?;
9        let table = toml.as_table().ok_or("Extracts the table value error")?;
10        Self::set_env_from_table(None, table);
11        Ok(envy::from_env::<T>()?)
12    }
13
14    fn set_env_from_table(prefix: Option<String>, table: &toml::Table) {
15        table.iter().for_each(|(k, v)| {
16            let key = prefix.clone().map_or(k.clone(), |p| format!("{p}_{k}"));
17            match v.clone() {
18                toml::Value::String(s) => Self::set_env_from_string(&key, &s),
19                toml::Value::Integer(i) => Self::set_env_from_string(&key, &i.to_string()),
20                toml::Value::Table(t) => Self::set_env_from_table(Some(key), &t),
21                value => panic!("Unimplemented handler for value type: {:?}", value),
22            };
23        })
24    }
25
26    fn set_env_from_string(key: &str, v_toml: &str) {
27        let k1 = key.to_lowercase();
28        let k2 = key.to_uppercase();
29        match (std::env::var(&k1), std::env::var(&k2)) {
30            (Ok(_v1), Ok(_v2)) => {
31                std::env::remove_var(&k2);
32                #[cfg(debug_assertions)]
33                println!("\x1B[1mEnvironment var `{k1}` overridden with `{_v1}` (unused `{k2}`=`{_v2}`)\x1B[0m")
34            }
35            (Ok(_v), Err(_)) => {
36                #[cfg(debug_assertions)]
37                println!("\x1B[1mEnvironment var `{k1}` overridden with `{_v} (in config.toml `{v_toml}`)`\x1B[0m",)
38            }
39            (Err(_), Ok(_v)) => {
40                #[cfg(debug_assertions)]
41                println!("\x1B[1mEnvironment var `{k2}` overridden with `{_v} (in config.toml `{v_toml}`)`\x1B[0m",)
42            }
43            (Err(_), Err(_)) => std::env::set_var(key.to_uppercase(), v_toml),
44        };
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use serde::Deserialize;
52
53    #[test]
54    fn it_works() {
55        std::env::set_var("overridden_both", "value_from_both_env");
56        std::env::set_var("OVERRIDDEN_BOTH", "value_from_both_env");
57        std::env::set_var("overridden_lower", "value_from_lower_env");
58        std::env::set_var("OVERRIDDEN_UPPER", "value_from_upper_env");
59
60        #[derive(Deserialize)]
61        struct Config {
62            example_key: String,
63            overridden_both: String,
64            overridden_lower: String,
65            overridden_upper: String,
66        }
67
68        let config = EnvyToml::from_str::<Config>(
69            r#"
70            example_key = "example_key_from_config"
71
72            [overridden]
73            both  = "both_from_config"
74            lower = "lower_from_config"
75            upper = "upper_from_config"
76            "#,
77        )
78        .unwrap();
79
80        assert_eq!(config.example_key, "example_key_from_config");
81        // Should be overridden with env var in lowercase when input both cases
82        assert_eq!(config.overridden_both, "value_from_both_env");
83        // Should be overridden with env var in lowercase
84        assert_eq!(config.overridden_lower, "value_from_lower_env");
85        // Should be overridden with env var in uppercase
86        assert_eq!(config.overridden_upper, "value_from_upper_env");
87    }
88}