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