north_config/
lib.rs

1#![allow(dead_code)]
2extern crate core;
3
4mod config_source;
5mod error;
6mod utils;
7
8pub mod serde_utils;
9pub(crate) mod watcher;
10
11pub use {
12    self::config_source::{
13        new_config, Case, ConfigSource, CustomConfigSource, EnvSourceOptions, FileSourceOptions,
14        NorthConfig, NorthConfigOptions,
15    },
16    self::error::Error,
17};
18
19#[cfg(test)]
20mod tests {
21    use crate::{ConfigSource, EnvSourceOptions, NorthConfigOptions};
22
23    #[derive(Clone, serde::Deserialize, Debug)]
24    struct NestedConfig {
25        pub foo: String,
26        pub bar: String,
27    }
28
29    #[derive(Clone, serde::Deserialize, Debug)]
30    struct DemoConfig {
31        pub host: Option<String>,
32        pub nested: NestedConfig,
33    }
34
35    #[derive(Clone, serde::Deserialize, Debug)]
36    struct SimpleDemoConfig {
37        pub host: Option<String>,
38    }
39
40    fn setup_env() {
41        destroy_env();
42        envmnt::set("NORTH_HOST", "address");
43        envmnt::set("NORTH_NESTED__FOO", "env foo");
44        envmnt::set("NORTH_NESTED__BAR", "env bar");
45    }
46
47    fn destroy_env() {
48        envmnt::remove("NORTH_HOST");
49        envmnt::remove("NORTH_NESTED__FOO");
50        envmnt::remove("NORTH_NESTED__BAR");
51    }
52
53    #[cfg(feature = "tokio")]
54    #[tokio::test]
55    async fn reads_config_from_file_with_release_mode() {
56        let config_options = NorthConfigOptions {
57            sources: vec![ConfigSource::File(
58                "../../examples/configs/test.{{env}}.json".to_string(),
59                None,
60            )],
61        };
62        let config = crate::new_config::<DemoConfig>(config_options).await;
63        let config = config.get_value().clone();
64        let host = config.host.unwrap();
65        assert_eq!(host, "0.0.0.245");
66    }
67
68    #[cfg(feature = "async-std")]
69    #[async_std::test]
70    async fn reads_config_from_file_with_release_mode() {
71        let config_options = NorthConfigOptions {
72            sources: vec![ConfigSource::File(
73                "../../examples/configs/test.{{env}}.json".to_string(),
74            )],
75        };
76        let config = crate::new_config::<DemoConfig>(config_options).await;
77        let config = config.get_value().clone();
78        let host = config.host.unwrap();
79        assert_eq!(host, "0.0.0.245");
80    }
81
82    #[cfg(not(any(feature = "tokio", feature = "async-std")))]
83    #[test]
84    fn reads_config_from_file_with_release_mode() {
85        let config_options = NorthConfigOptions {
86            sources: vec![ConfigSource::File(
87                "../../examples/configs/test.{{env}}.json".to_string(),
88                None,
89            )],
90        };
91        let config = crate::new_config::<DemoConfig>(config_options);
92        let config = config.get_value().clone();
93        assert_eq!(config.host.unwrap(), "0.0.0.245");
94    }
95
96    #[cfg(feature = "tokio")]
97    #[tokio::test]
98    async fn reads_config_from_file() {
99        let config_options = NorthConfigOptions {
100            sources: vec![ConfigSource::File(
101                "../../examples/configs/test.debug.json".to_string(),
102                None,
103            )],
104        };
105        let config = crate::new_config::<DemoConfig>(config_options).await;
106        let config = config.get_value().clone();
107        let host = config.host.unwrap();
108        assert_eq!(host, "0.0.0.245");
109    }
110
111    #[cfg(feature = "async-std")]
112    #[async_std::test]
113    async fn reads_config_from_file() {
114        let config_options = NorthConfigOptions {
115            sources: vec![ConfigSource::File(
116                "../../examples/configs/test.debug.json".to_string(),
117            )],
118        };
119        let config = crate::new_config::<DemoConfig>(config_options).await;
120        let config = config.get_value().clone();
121        let host = config.host.unwrap();
122        assert_eq!(host, "0.0.0.245");
123    }
124
125    #[cfg(not(any(feature = "tokio", feature = "async-std")))]
126    #[test]
127    fn reads_config_from_file() {
128        let config_options = NorthConfigOptions {
129            sources: vec![ConfigSource::File(
130                "../../examples/configs/test.debug.json".to_string(),
131                None,
132            )],
133        };
134        let config = crate::new_config::<DemoConfig>(config_options);
135        let config = config.get_value().clone();
136        assert_eq!(config.host.unwrap(), "0.0.0.245");
137    }
138
139    #[cfg(feature = "tokio")]
140    #[tokio::test]
141    async fn merge_and_overwrite_duplicate_keys_from_two_config_file() {
142        let config_options = NorthConfigOptions {
143            sources: vec![
144                ConfigSource::File("../../examples/configs/test.debug.json".to_string(), None),
145                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
146            ],
147        };
148        let config = crate::new_config::<DemoConfig>(config_options).await;
149        let config = config.get_value().clone();
150        let host = config.host.unwrap();
151        assert_eq!(host, "0.0.0.0");
152    }
153
154    #[cfg(not(any(feature = "tokio", feature = "async-std")))]
155    #[test]
156    fn merge_and_overwrite_duplicate_keys_from_two_config_file() {
157        let config_options = NorthConfigOptions {
158            sources: vec![
159                ConfigSource::File("../../examples/configs/test.debug.json".to_string(), None),
160                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
161            ],
162        };
163        let config = crate::new_config::<DemoConfig>(config_options);
164        let config = config.get_value().clone();
165        assert_eq!(config.host.unwrap(), "0.0.0.0");
166    }
167
168    #[cfg(feature = "yaml")]
169    #[test]
170    fn merge_two_files_from_json_and_yaml_sources() {
171        let config_options = NorthConfigOptions {
172            sources: vec![
173                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
174                ConfigSource::File("../../examples/configs/test.release.yaml".to_string(), None),
175            ],
176        };
177        let config = crate::new_config::<DemoConfig>(config_options);
178        let config = config.get_value().clone();
179        let host = config.host.unwrap();
180        assert_eq!(host, "127.0.0.1");
181    }
182
183    #[cfg(all(feature = "yaml", feature = "toml"))]
184    #[test]
185    fn merge_three_files_from_json_toml_and_yaml_sources() {
186        let config_options = NorthConfigOptions {
187            sources: vec![
188                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
189                ConfigSource::File("../../examples/configs/test.release.yaml".to_string(), None),
190                ConfigSource::File("../../examples/configs/test.release.toml".to_string(), None),
191            ],
192        };
193        let config = crate::new_config::<DemoConfig>(config_options);
194        let config = config.get_value().clone();
195        let host = config.host.unwrap();
196        assert_eq!(host, "test");
197    }
198
199    #[test]
200    fn merge_deep_nested_objects() {
201        let config_options = NorthConfigOptions {
202            sources: vec![
203                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
204                ConfigSource::File("../../examples/configs/test.debug.json".to_string(), None),
205            ],
206        };
207        let config = crate::new_config::<DemoConfig>(config_options);
208        let config = config.get_value().clone();
209        let host = config.host.unwrap();
210        let nested = config.nested;
211
212        assert_eq!(host, "0.0.0.245");
213        assert_eq!(nested.foo, "Well its foo");
214    }
215
216    #[test]
217    fn merge_env_and_file_sources() {
218        destroy_env();
219        envmnt::set("NORTH_HOST", "address");
220        envmnt::set("NORTH_NESTED__FOO", "env foo");
221
222        let config_options = NorthConfigOptions {
223            sources: vec![
224                ConfigSource::File("../../examples/configs/test.release.json".to_string(), None),
225                ConfigSource::File("../../examples/configs/test.debug.json".to_string(), None),
226                ConfigSource::Env(EnvSourceOptions{
227                    prefix: Some("NORTH_".to_string()),
228                    ..Default::default()
229                }),
230            ],
231        };
232        let config = crate::new_config::<DemoConfig>(config_options);
233        let config = config.get_value().clone();
234        let host = config.host.unwrap();
235        let nested = config.nested;
236
237        assert_eq!(host, "address");
238        assert_eq!(nested.foo, "env foo");
239        assert_eq!(nested.bar, "env bar");
240
241        envmnt::remove("NORTH_HOST");
242        envmnt::remove("NORTH_NESTED__FOO");
243        destroy_env();
244    }
245
246    #[test]
247    fn read_deep_config_from_env_sources_default_prefix() {
248        setup_env();
249
250        let config_options = NorthConfigOptions {
251            sources: vec![ConfigSource::Env(EnvSourceOptions::default())],
252        };
253        let config = crate::new_config::<DemoConfig>(config_options);
254        let config = config.get_value().clone();
255        let host = config.host.unwrap();
256        let nested = config.nested;
257
258        assert_eq!(host, "address");
259        assert_eq!(nested.foo, "env foo");
260        assert_eq!(nested.bar, "env bar");
261
262        destroy_env();
263    }
264
265    #[test]
266    fn read_deep_config_from_env_sources_custom_prefix() {
267        envmnt::set("TESTNORTH_HOST", "address");
268        envmnt::set("TESTNORTH_NESTED__FOO", "env foo 2");
269        envmnt::set("TESTNORTH_NESTED__BAR", "env bar 2");
270
271        let mut env_opts = EnvSourceOptions::default();
272        env_opts.prefix = Some("TESTNORTH".to_string());
273
274        let config_options = NorthConfigOptions {
275            sources: vec![ConfigSource::Env(env_opts)],
276        };
277
278        let config = crate::new_config::<DemoConfig>(config_options);
279        let config = config.get_value().clone();
280        let host = config.host.unwrap();
281        let nested = config.nested;
282
283        assert_eq!(host, "address");
284        assert_eq!(nested.foo, "env foo 2");
285        assert_eq!(nested.bar, "env bar 2");
286
287        envmnt::remove("TESTNORTH_HOST");
288        envmnt::remove("TESTNORTH_NESTED__FOO");
289        envmnt::remove("TESTNORTH_NESTED__BAR");
290    }
291
292    #[test]
293    fn read_deep_array_config_from_env_sources() {
294        #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
295        struct Names {
296            pub first: i32
297        }
298        #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
299        struct TestStr {
300            pub names: Vec<Names>
301        }
302
303        envmnt::set("NORTH_NAMES__0__FIRST", "50");
304
305        let mut env_opts = EnvSourceOptions::default();
306        env_opts.prefix = Some("NORTH".to_string());
307
308        let config_options = NorthConfigOptions {
309            sources: vec![ConfigSource::Env(env_opts)],
310        };
311
312        let config = crate::new_config::<TestStr>(config_options);
313        let config = config.get_value().clone();
314        let names = config.names;
315
316        assert_eq!(names.len(), 1);
317        assert_eq!(names[0].first, 50);
318
319        envmnt::remove("NORTH_NAMES__0__FIRST");
320    }
321
322    #[cfg(feature = "ron")]
323    #[test]
324    fn read_config_from_ron_source() {
325        let config_options = NorthConfigOptions {
326            sources: vec![ConfigSource::File(
327                "../../examples/configs/test.release.ron".to_string(),
328                None
329            )],
330        };
331        let config = crate::new_config::<DemoConfig>(config_options);
332        let config = config.get_value().clone();
333        let host = config.host.unwrap();
334
335        assert_eq!(host, "ron-host");
336    }
337
338    // #[test]
339    // fn read_config_from_ron_source() {
340    //     let config_options = NorthConfigOptions {
341    //         sources: vec![
342    //             ConfigSource::File("../../examples/configs/test.release.yaml".to_string()),
343    //         ],
344    //     };
345    //
346    //     assert_eq!(crate::new_config::<SimpleDemoConfig>(config_options), "missing yaml feature for crate, please enable yaml feature", &(), None);
347    // }
348}