1 use config::{Config, Environment, Source};
2 use serde_derive::Deserialize;
3 
4 /// Reminder that tests using env variables need to use different env variable names, since
5 /// tests can be run in parallel
6 
7 #[test]
test_default()8 fn test_default() {
9     temp_env::with_var("A_B_C", Some("abc"), || {
10         let environment = Environment::default();
11 
12         assert!(environment.collect().unwrap().contains_key("a_b_c"));
13     })
14 }
15 
16 #[test]
test_prefix_is_removed_from_key()17 fn test_prefix_is_removed_from_key() {
18     temp_env::with_var("B_A_C", Some("abc"), || {
19         let environment = Environment::with_prefix("B");
20 
21         assert!(environment.collect().unwrap().contains_key("a_c"));
22     })
23 }
24 
25 #[test]
test_prefix_with_variant_forms_of_spelling()26 fn test_prefix_with_variant_forms_of_spelling() {
27     temp_env::with_var("a_A_C", Some("abc"), || {
28         let environment = Environment::with_prefix("a");
29 
30         assert!(environment.collect().unwrap().contains_key("a_c"));
31     });
32 
33     temp_env::with_var("aB_A_C", Some("abc"), || {
34         let environment = Environment::with_prefix("aB");
35 
36         assert!(environment.collect().unwrap().contains_key("a_c"));
37     });
38 
39     temp_env::with_var("Ab_A_C", Some("abc"), || {
40         let environment = Environment::with_prefix("ab");
41 
42         assert!(environment.collect().unwrap().contains_key("a_c"));
43     });
44 }
45 
46 #[test]
test_separator_behavior()47 fn test_separator_behavior() {
48     temp_env::with_var("C_B_A", Some("abc"), || {
49         let environment = Environment::with_prefix("C").separator("_");
50 
51         assert!(environment.collect().unwrap().contains_key("b.a"));
52     })
53 }
54 
55 #[test]
test_empty_value_is_ignored()56 fn test_empty_value_is_ignored() {
57     temp_env::with_var("C_A_B", Some(""), || {
58         let environment = Environment::default().ignore_empty(true);
59 
60         assert!(!environment.collect().unwrap().contains_key("c_a_b"));
61     })
62 }
63 
64 #[test]
test_keep_prefix()65 fn test_keep_prefix() {
66     temp_env::with_var("C_A_B", Some(""), || {
67         // Do not keep the prefix
68         let environment = Environment::with_prefix("C");
69 
70         assert!(environment.collect().unwrap().contains_key("a_b"));
71 
72         let environment = Environment::with_prefix("C").keep_prefix(false);
73 
74         assert!(environment.collect().unwrap().contains_key("a_b"));
75 
76         // Keep the prefix
77         let environment = Environment::with_prefix("C").keep_prefix(true);
78 
79         assert!(environment.collect().unwrap().contains_key("c_a_b"));
80     })
81 }
82 
83 #[test]
test_custom_separator_behavior()84 fn test_custom_separator_behavior() {
85     temp_env::with_var("C.B.A", Some("abc"), || {
86         let environment = Environment::with_prefix("C").separator(".");
87 
88         assert!(environment.collect().unwrap().contains_key("b.a"));
89     })
90 }
91 
92 #[test]
test_custom_prefix_separator_behavior()93 fn test_custom_prefix_separator_behavior() {
94     temp_env::with_var("C-B.A", Some("abc"), || {
95         let environment = Environment::with_prefix("C")
96             .separator(".")
97             .prefix_separator("-");
98 
99         assert!(environment.collect().unwrap().contains_key("b.a"));
100     })
101 }
102 
103 #[test]
test_parse_int()104 fn test_parse_int() {
105     // using a struct in an enum here to make serde use `deserialize_any`
106     #[derive(Deserialize, Debug)]
107     #[serde(tag = "tag")]
108     enum TestIntEnum {
109         Int(TestInt),
110     }
111 
112     #[derive(Deserialize, Debug)]
113     struct TestInt {
114         int_val: i32,
115     }
116 
117     temp_env::with_var("INT_VAL", Some("42"), || {
118         let environment = Environment::default().try_parsing(true);
119 
120         let config = Config::builder()
121             .set_default("tag", "Int")
122             .unwrap()
123             .add_source(environment)
124             .build()
125             .unwrap();
126 
127         let config: TestIntEnum = config.try_deserialize().unwrap();
128 
129         assert!(matches!(config, TestIntEnum::Int(TestInt { int_val: 42 })));
130     })
131 }
132 
133 #[test]
test_parse_uint()134 fn test_parse_uint() {
135     // using a struct in an enum here to make serde use `deserialize_any`
136     #[derive(Deserialize, Debug)]
137     #[serde(tag = "tag")]
138     enum TestUintEnum {
139         Uint(TestUint),
140     }
141 
142     #[derive(Deserialize, Debug)]
143     struct TestUint {
144         int_val: u32,
145     }
146 
147     temp_env::with_var("INT_VAL", Some("42"), || {
148         let environment = Environment::default().try_parsing(true);
149 
150         let config = Config::builder()
151             .set_default("tag", "Uint")
152             .unwrap()
153             .add_source(environment)
154             .build()
155             .unwrap();
156 
157         let config: TestUintEnum = config.try_deserialize().unwrap();
158 
159         assert!(matches!(
160             config,
161             TestUintEnum::Uint(TestUint { int_val: 42 })
162         ));
163     })
164 }
165 
166 #[test]
test_parse_float()167 fn test_parse_float() {
168     // using a struct in an enum here to make serde use `deserialize_any`
169     #[derive(Deserialize, Debug)]
170     #[serde(tag = "tag")]
171     enum TestFloatEnum {
172         Float(TestFloat),
173     }
174 
175     #[derive(Deserialize, Debug)]
176     struct TestFloat {
177         float_val: f64,
178     }
179 
180     temp_env::with_var("FLOAT_VAL", Some("42.3"), || {
181         let environment = Environment::default().try_parsing(true);
182 
183         let config = Config::builder()
184             .set_default("tag", "Float")
185             .unwrap()
186             .add_source(environment)
187             .build()
188             .unwrap();
189 
190         let config: TestFloatEnum = config.try_deserialize().unwrap();
191 
192         // can't use `matches!` because of float value
193         match config {
194             TestFloatEnum::Float(TestFloat { float_val }) => {
195                 assert!(float_cmp::approx_eq!(f64, float_val, 42.3))
196             }
197         }
198     })
199 }
200 
201 #[test]
test_parse_bool()202 fn test_parse_bool() {
203     // using a struct in an enum here to make serde use `deserialize_any`
204     #[derive(Deserialize, Debug)]
205     #[serde(tag = "tag")]
206     enum TestBoolEnum {
207         Bool(TestBool),
208     }
209 
210     #[derive(Deserialize, Debug)]
211     struct TestBool {
212         bool_val: bool,
213     }
214 
215     temp_env::with_var("BOOL_VAL", Some("true"), || {
216         let environment = Environment::default().try_parsing(true);
217 
218         let config = Config::builder()
219             .set_default("tag", "Bool")
220             .unwrap()
221             .add_source(environment)
222             .build()
223             .unwrap();
224 
225         let config: TestBoolEnum = config.try_deserialize().unwrap();
226 
227         assert!(matches!(
228             config,
229             TestBoolEnum::Bool(TestBool { bool_val: true })
230         ));
231     })
232 }
233 
234 #[test]
235 #[should_panic(expected = "invalid type: string \"42\", expected i32")]
test_parse_off_int()236 fn test_parse_off_int() {
237     // using a struct in an enum here to make serde use `deserialize_any`
238     #[derive(Deserialize, Debug)]
239     #[serde(tag = "tag")]
240     enum TestIntEnum {
241         Int(TestInt),
242     }
243 
244     #[derive(Deserialize, Debug)]
245     struct TestInt {
246         #[allow(dead_code)]
247         int_val_1: i32,
248     }
249 
250     temp_env::with_var("INT_VAL_1", Some("42"), || {
251         let environment = Environment::default().try_parsing(false);
252 
253         let config = Config::builder()
254             .set_default("tag", "Int")
255             .unwrap()
256             .add_source(environment)
257             .build()
258             .unwrap();
259 
260         config.try_deserialize::<TestIntEnum>().unwrap();
261     })
262 }
263 
264 #[test]
265 #[should_panic(expected = "invalid type: string \"42.3\", expected f64")]
test_parse_off_float()266 fn test_parse_off_float() {
267     // using a struct in an enum here to make serde use `deserialize_any`
268     #[derive(Deserialize, Debug)]
269     #[serde(tag = "tag")]
270     enum TestFloatEnum {
271         Float(TestFloat),
272     }
273 
274     #[derive(Deserialize, Debug)]
275     struct TestFloat {
276         #[allow(dead_code)]
277         float_val_1: f64,
278     }
279 
280     temp_env::with_var("FLOAT_VAL_1", Some("42.3"), || {
281         let environment = Environment::default().try_parsing(false);
282 
283         let config = Config::builder()
284             .set_default("tag", "Float")
285             .unwrap()
286             .add_source(environment)
287             .build()
288             .unwrap();
289 
290         config.try_deserialize::<TestFloatEnum>().unwrap();
291     })
292 }
293 
294 #[test]
295 #[should_panic(expected = "invalid type: string \"true\", expected a boolean")]
test_parse_off_bool()296 fn test_parse_off_bool() {
297     // using a struct in an enum here to make serde use `deserialize_any`
298     #[derive(Deserialize, Debug)]
299     #[serde(tag = "tag")]
300     enum TestBoolEnum {
301         Bool(TestBool),
302     }
303 
304     #[derive(Deserialize, Debug)]
305     struct TestBool {
306         #[allow(dead_code)]
307         bool_val_1: bool,
308     }
309 
310     temp_env::with_var("BOOL_VAL_1", Some("true"), || {
311         let environment = Environment::default().try_parsing(false);
312 
313         let config = Config::builder()
314             .set_default("tag", "Bool")
315             .unwrap()
316             .add_source(environment)
317             .build()
318             .unwrap();
319 
320         config.try_deserialize::<TestBoolEnum>().unwrap();
321     })
322 }
323 
324 #[test]
325 #[should_panic(expected = "invalid type: string \"not an int\", expected i32")]
test_parse_int_fail()326 fn test_parse_int_fail() {
327     // using a struct in an enum here to make serde use `deserialize_any`
328     #[derive(Deserialize, Debug)]
329     #[serde(tag = "tag")]
330     enum TestIntEnum {
331         Int(TestInt),
332     }
333 
334     #[derive(Deserialize, Debug)]
335     struct TestInt {
336         #[allow(dead_code)]
337         int_val_2: i32,
338     }
339 
340     temp_env::with_var("INT_VAL_2", Some("not an int"), || {
341         let environment = Environment::default().try_parsing(true);
342 
343         let config = Config::builder()
344             .set_default("tag", "Int")
345             .unwrap()
346             .add_source(environment)
347             .build()
348             .unwrap();
349 
350         config.try_deserialize::<TestIntEnum>().unwrap();
351     })
352 }
353 
354 #[test]
355 #[should_panic(expected = "invalid type: string \"not a float\", expected f64")]
test_parse_float_fail()356 fn test_parse_float_fail() {
357     // using a struct in an enum here to make serde use `deserialize_any`
358     #[derive(Deserialize, Debug)]
359     #[serde(tag = "tag")]
360     enum TestFloatEnum {
361         Float(TestFloat),
362     }
363 
364     #[derive(Deserialize, Debug)]
365     struct TestFloat {
366         #[allow(dead_code)]
367         float_val_2: f64,
368     }
369 
370     temp_env::with_var("FLOAT_VAL_2", Some("not a float"), || {
371         let environment = Environment::default().try_parsing(true);
372 
373         let config = Config::builder()
374             .set_default("tag", "Float")
375             .unwrap()
376             .add_source(environment)
377             .build()
378             .unwrap();
379 
380         config.try_deserialize::<TestFloatEnum>().unwrap();
381     })
382 }
383 
384 #[test]
385 #[should_panic(expected = "invalid type: string \"not a bool\", expected a boolean")]
test_parse_bool_fail()386 fn test_parse_bool_fail() {
387     // using a struct in an enum here to make serde use `deserialize_any`
388     #[derive(Deserialize, Debug)]
389     #[serde(tag = "tag")]
390     enum TestBoolEnum {
391         Bool(TestBool),
392     }
393 
394     #[derive(Deserialize, Debug)]
395     struct TestBool {
396         #[allow(dead_code)]
397         bool_val_2: bool,
398     }
399 
400     temp_env::with_var("BOOL_VAL_2", Some("not a bool"), || {
401         let environment = Environment::default().try_parsing(true);
402 
403         let config = Config::builder()
404             .set_default("tag", "Bool")
405             .unwrap()
406             .add_source(environment)
407             .build()
408             .unwrap();
409 
410         config.try_deserialize::<TestBoolEnum>().unwrap();
411     })
412 }
413 
414 #[test]
test_parse_string_and_list()415 fn test_parse_string_and_list() {
416     // using a struct in an enum here to make serde use `deserialize_any`
417     #[derive(Deserialize, Debug)]
418     #[serde(tag = "tag")]
419     enum TestStringEnum {
420         String(TestString),
421     }
422 
423     #[derive(Deserialize, Debug)]
424     struct TestString {
425         string_val: String,
426         string_list: Vec<String>,
427     }
428 
429     temp_env::with_vars(
430         vec![
431             ("LIST_STRING_LIST", Some("test,string")),
432             ("LIST_STRING_VAL", Some("test,string")),
433         ],
434         || {
435             let environment = Environment::default()
436                 .prefix("LIST")
437                 .list_separator(",")
438                 .with_list_parse_key("string_list")
439                 .try_parsing(true);
440 
441             let config = Config::builder()
442                 .set_default("tag", "String")
443                 .unwrap()
444                 .add_source(environment)
445                 .build()
446                 .unwrap();
447 
448             let config: TestStringEnum = config.try_deserialize().unwrap();
449 
450             match config {
451                 TestStringEnum::String(TestString {
452                     string_val,
453                     string_list,
454                 }) => {
455                     assert_eq!(String::from("test,string"), string_val);
456                     assert_eq!(
457                         vec![String::from("test"), String::from("string")],
458                         string_list
459                     );
460                 }
461             }
462         },
463     )
464 }
465 
466 #[test]
test_parse_string()467 fn test_parse_string() {
468     // using a struct in an enum here to make serde use `deserialize_any`
469     #[derive(Deserialize, Debug)]
470     #[serde(tag = "tag")]
471     enum TestStringEnum {
472         String(TestString),
473     }
474 
475     #[derive(Deserialize, Debug)]
476     struct TestString {
477         string_val: String,
478     }
479 
480     temp_env::with_var("STRING_VAL", Some("test string"), || {
481         let environment = Environment::default().try_parsing(true);
482 
483         let config = Config::builder()
484             .set_default("tag", "String")
485             .unwrap()
486             .add_source(environment)
487             .build()
488             .unwrap();
489 
490         let config: TestStringEnum = config.try_deserialize().unwrap();
491 
492         let test_string = String::from("test string");
493 
494         match config {
495             TestStringEnum::String(TestString { string_val }) => {
496                 assert_eq!(test_string, string_val)
497             }
498         }
499     })
500 }
501 
502 #[test]
test_parse_string_list()503 fn test_parse_string_list() {
504     // using a struct in an enum here to make serde use `deserialize_any`
505     #[derive(Deserialize, Debug)]
506     #[serde(tag = "tag")]
507     enum TestListEnum {
508         StringList(TestList),
509     }
510 
511     #[derive(Deserialize, Debug)]
512     struct TestList {
513         string_list: Vec<String>,
514     }
515 
516     temp_env::with_var("STRING_LIST", Some("test string"), || {
517         let environment = Environment::default().try_parsing(true).list_separator(" ");
518 
519         let config = Config::builder()
520             .set_default("tag", "StringList")
521             .unwrap()
522             .add_source(environment)
523             .build()
524             .unwrap();
525 
526         let config: TestListEnum = config.try_deserialize().unwrap();
527 
528         let test_string = vec![String::from("test"), String::from("string")];
529 
530         match config {
531             TestListEnum::StringList(TestList { string_list }) => {
532                 assert_eq!(test_string, string_list)
533             }
534         }
535     })
536 }
537 
538 #[test]
test_parse_off_string()539 fn test_parse_off_string() {
540     // using a struct in an enum here to make serde use `deserialize_any`
541     #[derive(Deserialize, Debug)]
542     #[serde(tag = "tag")]
543     enum TestStringEnum {
544         String(TestString),
545     }
546 
547     #[derive(Deserialize, Debug)]
548     struct TestString {
549         string_val_1: String,
550     }
551 
552     temp_env::with_var("STRING_VAL_1", Some("test string"), || {
553         let environment = Environment::default().try_parsing(false);
554 
555         let config = Config::builder()
556             .set_default("tag", "String")
557             .unwrap()
558             .add_source(environment)
559             .build()
560             .unwrap();
561 
562         let config: TestStringEnum = config.try_deserialize().unwrap();
563 
564         let test_string = String::from("test string");
565 
566         match config {
567             TestStringEnum::String(TestString { string_val_1 }) => {
568                 assert_eq!(test_string, string_val_1);
569             }
570         }
571     })
572 }
573 
574 #[test]
test_parse_int_default()575 fn test_parse_int_default() {
576     #[derive(Deserialize, Debug)]
577     struct TestInt {
578         int_val: i32,
579     }
580 
581     let environment = Environment::default().try_parsing(true);
582 
583     let config = Config::builder()
584         .set_default("int_val", 42_i32)
585         .unwrap()
586         .add_source(environment)
587         .build()
588         .unwrap();
589 
590     let config: TestInt = config.try_deserialize().unwrap();
591     assert_eq!(config.int_val, 42);
592 }
593 
594 #[test]
test_parse_uint_default()595 fn test_parse_uint_default() {
596     #[derive(Deserialize, Debug)]
597     struct TestUint {
598         int_val: u32,
599     }
600 
601     let environment = Environment::default().try_parsing(true);
602 
603     let config = Config::builder()
604         .set_default("int_val", 42_u32)
605         .unwrap()
606         .add_source(environment)
607         .build()
608         .unwrap();
609 
610     let config: TestUint = config.try_deserialize().unwrap();
611     assert_eq!(config.int_val, 42);
612 }
613