pepe_config/
lib.rs

1// re-export
2pub use config::*;
3pub use duration_string::DurationString;
4pub use secrecy::*;
5use serde::Deserialize;
6
7pub mod kafka;
8pub mod postgres;
9pub mod redis;
10
11pub fn load_yaml<'a, T: serde::de::Deserialize<'a>>(file: &str) -> Result<T, ConfigError> {
12    load(file, FileFormat::Yaml)
13}
14
15#[derive(Deserialize)]
16#[serde(deny_unknown_fields)]
17struct BaseConfig<T> {
18    #[serde(flatten)]
19    config: T,
20}
21
22pub fn load<'a, T: serde::de::Deserialize<'a>>(
23    file: &str,
24    format: FileFormat,
25) -> Result<T, ConfigError> {
26    let builder = Config::builder();
27    let base: BaseConfig<T> = match std::env::var("CONFIG_PATH") {
28        Ok(env_file_path) => builder.add_source(File::with_name(&env_file_path)),
29        Err(_) => builder.add_source(File::from_str(file, format)),
30    }
31    .add_source(
32        Environment::default()
33            .separator("__")
34            .prefix("cfg")
35            .prefix_separator("__"),
36    )
37    .build()?
38    .try_deserialize()?;
39    Ok(base.config)
40}
41
42#[cfg(test)]
43mod tests {
44    use config::FileFormat;
45    use std::{collections::HashMap, env};
46
47    use crate::load;
48
49    #[test]
50    fn test_load_yaml() {
51        use serde::Deserialize;
52
53        #[derive(Debug, Deserialize)]
54        struct Author {
55            pub name: String,
56            pub books: Vec<String>,
57        }
58
59        #[derive(Debug, Deserialize)]
60        struct Test {
61            pub count: u32,
62            pub author: Author,
63        }
64
65        let expected_data: Test = Test {
66            count: 10,
67            author: Author {
68                name: "John Ronald Reuel Tolkien".to_string(),
69                books: vec!["The Lord of the Rings".to_string()],
70            },
71        };
72
73        let data: Test = load(include_str!("./test_config.yaml"), FileFormat::Yaml).unwrap();
74
75        assert_eq!(data.count, expected_data.count);
76        assert_eq!(data.author.name, expected_data.author.name);
77        assert_eq!(data.author.books, expected_data.author.books);
78    }
79
80    #[test]
81    fn test_load_yaml_with_secrets() {
82        use secrecy::{ExposeSecret, SecretString};
83        use serde::Deserialize;
84
85        #[derive(Debug, Deserialize)]
86        struct Config {
87            pub login: String,
88            pub password: SecretString,
89        }
90        let expected_data = Config {
91            login: "pepe".to_string(),
92            password: SecretString::from("the_frog"),
93        };
94
95        let data: Config = load("login: pepe\npassword: the_frog", FileFormat::Yaml).unwrap();
96        assert_eq!(data.login, expected_data.login);
97        assert_eq!(
98            *data.password.expose_secret(),
99            *expected_data.password.expose_secret()
100        );
101    }
102
103    #[test]
104    fn test_load_yaml_map() {
105        use serde::Deserialize;
106
107        #[derive(Debug, Deserialize)]
108        struct Config {
109            pub words_dict: HashMap<String, String>,
110            pub digits_dict: HashMap<String, String>,
111        }
112        let expected_data = Config {
113            words_dict: HashMap::from_iter(vec![
114                ("a".to_owned(), "a".to_owned()),
115                ("b".to_owned(), "B".to_owned()),
116                ("C".to_owned(), "c".to_owned()),
117                ("D".to_owned(), "D".to_owned()),
118            ]),
119            digits_dict: HashMap::from_iter(vec![
120                ("1".to_owned(), "one".to_owned()),
121                ("2".to_owned(), "two".to_owned()),
122            ]),
123        };
124
125        let data: Config = load(
126            "words_dict:\n a: a\n b: B\n C: c\n D: D\ndigits_dict:\n 1: one\n 2: two",
127            FileFormat::Yaml,
128        )
129        .unwrap();
130        assert_eq!(data.words_dict, expected_data.words_dict);
131        assert_eq!(data.digits_dict, expected_data.digits_dict);
132    }
133
134    #[test]
135    #[ignore = "can't be running on multithread due to env usage"]
136    fn test_env_string_override() {
137        use serde::Deserialize;
138
139        #[derive(Debug, Deserialize)]
140        #[serde(deny_unknown_fields)]
141        struct Author {
142            pub name: String,
143            pub books: Vec<String>,
144        }
145
146        #[derive(Debug, Deserialize)]
147        #[serde(deny_unknown_fields)]
148        struct Test {
149            pub count: u32,
150            pub author: Author,
151        }
152
153        let expected_data: Test = Test {
154            count: 10,
155            author: Author {
156                name: "Fyodor Dostoevsky".to_string(),
157                books: vec!["The Lord of the Rings".to_string()],
158            },
159        };
160
161        env::set_var("cfg__author__name", "Fyodor Dostoevsky");
162        let data: Test = load(include_str!("./test_config.yaml"), FileFormat::Yaml).unwrap();
163        env::remove_var("cfg__author__name");
164
165        assert_eq!(data.count, expected_data.count);
166        assert_eq!(data.author.name, expected_data.author.name);
167        assert_eq!(data.author.books, expected_data.author.books);
168    }
169}