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 assert_eq!(config.overridden_both, "value_from_both_env");
83 assert_eq!(config.overridden_lower, "value_from_lower_env");
85 assert_eq!(config.overridden_upper, "value_from_upper_env");
87 }
88}