1use std::{
2 any::{type_name, Any},
3 borrow::Borrow,
4 cell::RefCell,
5 collections::HashSet,
6 env::var,
7 path::PathBuf,
8};
9
10use crate::{
11 cache::CacheConfigSource,
12 err::ConfigError,
13 impl_cache,
14 key::{CacheString, ConfigKey, PartialKeyIter},
15 macros::{cfg_log, impl_default},
16 source::{
17 cargo::Cargo, environment::PrefixEnvironment, memory::HashSource, register_by_ext,
18 register_files, ConfigSource, SourceOption,
19 },
20 value::ConfigValue,
21 value_ref::Refresher,
22 FromConfig, FromConfigWithPrefix, PartialKeyCollector,
23};
24
25#[allow(missing_debug_implementations)]
30pub struct ConfigContext<'a> {
31 key: ConfigKey<'a>,
32 source: &'a HashSource,
33 pub(crate) ref_value_flag: bool,
34}
35
36struct CacheValue {
37 buf: String,
38 stack: Vec<usize>,
39}
40
41impl CacheValue {
42 fn new() -> Self {
43 Self {
44 buf: String::with_capacity(10),
45 stack: Vec::with_capacity(3),
46 }
47 }
48
49 fn clear(&mut self) {
50 self.buf.clear();
51 self.stack.clear();
52 }
53}
54
55impl_cache!(CacheValue);
56
57impl HashSource {
58 pub(crate) fn new_context<'a>(&'a self, cache: &'a mut CacheString) -> ConfigContext<'a> {
59 ConfigContext {
60 key: cache.new_key(),
61 source: self,
62 ref_value_flag: false,
63 }
64 }
65}
66
67impl<'a> ConfigContext<'a> {
68 pub(crate) fn as_refresher(&self) -> &Refresher {
69 &self.source.refs
70 }
71
72 fn parse_placeholder(
73 source: &'a HashSource,
74 current_key: &ConfigKey<'_>,
75 val: &str,
76 history: &mut HashSet<String>,
77 ) -> Result<(bool, Option<ConfigValue<'a>>), ConfigError> {
78 let pat: &[_] = &['$', '\\', '}'];
79 CacheValue::with_key(move |cv| {
80 cv.clear();
81 let mut value = val;
82 let mut flag = true;
83 while let Some(pos) = value.find(pat) {
84 flag = false;
85 match &value[pos..=pos] {
86 "$" => {
87 let pos_1 = pos + 1;
88 if value.len() == pos_1 || &value[pos_1..=pos_1] != "{" {
89 return Err(ConfigError::ConfigParseError(
90 current_key.to_string(),
91 val.to_owned(),
92 ));
93 }
94 cv.buf.push_str(&value[..pos]);
95 cv.stack.push(cv.buf.len());
96 value = &value[pos + 2..];
97 }
98 "\\" => {
99 let pos_1 = pos + 1;
100 if value.len() == pos_1 {
101 return Err(ConfigError::ConfigParseError(
102 current_key.to_string(),
103 val.to_owned(),
104 ));
105 }
106 cv.buf.push_str(&value[..pos]);
107 cv.buf.push_str(&value[pos_1..=pos_1]);
108 value = &value[pos + 2..];
109 }
110 "}" => {
111 let last = cv.stack.pop().ok_or_else(|| {
112 ConfigError::ConfigParseError(current_key.to_string(), val.to_owned())
113 })?;
114
115 cv.buf.push_str(&value[..pos]);
116 let v = &(cv.buf.as_str())[last..];
117 let (key, def) = match v.find(':') {
118 Some(pos) => (&v[..pos], Some(&v[pos + 1..])),
119 _ => (v, None),
120 };
121 if !history.insert(key.to_string()) {
122 return Err(ConfigError::ConfigRecursiveError(current_key.to_string()));
123 }
124 let v = match CacheString::with_key_place(|cache| {
125 source
126 .new_context(cache)
127 .do_parse_config::<String, &str>(key, None, history)
128 }) {
129 Err(ConfigError::ConfigNotFound(v)) => match def {
130 Some(v) => v.to_owned(),
131 _ => return Err(ConfigError::ConfigRecursiveNotFound(v)),
132 },
133 ret => ret?,
134 };
135 history.remove(key);
136 cv.buf.truncate(last);
137 cv.buf.push_str(&v);
138 value = &value[pos + 1..];
139 }
140 _ => return Err(ConfigError::ConfigRecursiveError(current_key.to_string())),
141 }
142 }
143 if flag {
144 return Ok((true, None));
145 }
146
147 if cv.stack.is_empty() {
148 cv.buf.push_str(value);
149 return Ok((false, Some(cv.buf.to_string().into())));
150 }
151
152 Err(ConfigError::ConfigParseError(
153 current_key.to_string(),
154 val.to_owned(),
155 ))
156 })
157 }
158
159 #[inline]
160 pub(crate) fn do_parse_config<T: FromConfig, K: Into<PartialKeyIter<'a>>>(
161 &mut self,
162 partial_key: K,
163 default_value: Option<ConfigValue<'_>>,
164 history: &mut HashSet<String>,
165 ) -> Result<T, ConfigError> {
166 let mark = self.key.push(partial_key);
167 let value = match self.source.get_value(&self.key).or(default_value) {
168 Some(ConfigValue::StrRef(s)) => {
169 match Self::parse_placeholder(self.source, &self.key, s, history)? {
170 (true, _) => Some(ConfigValue::StrRef(s)),
171 (false, v) => v,
172 }
173 }
174 Some(ConfigValue::Str(s)) => {
175 match Self::parse_placeholder(self.source, &self.key, &s, history)? {
176 (true, _) => Some(ConfigValue::Str(s)),
177 (_, v) => v,
178 }
179 }
180 #[cfg(feature = "rand")]
181 Some(ConfigValue::Rand(s)) => Some(s.normalize()),
182 v => v,
183 };
184
185 let v = T::from_config(self, value);
186 self.key.pop(mark);
187 v
188 }
189
190 #[inline]
192 pub fn parse_config<T: FromConfig>(
193 &mut self,
194 partial_key: &'a str,
195 default_value: Option<ConfigValue<'_>>,
196 ) -> Result<T, ConfigError> {
197 self.do_parse_config(partial_key, default_value, &mut HashSet::new())
198 }
199
200 #[inline]
202 pub fn current_key(&self) -> String {
203 self.key.to_string()
204 }
205
206 #[allow(dead_code)]
207 pub(crate) fn current_key_str(&self) -> &str {
208 self.key.as_str()
209 }
210
211 #[inline]
212 pub(crate) fn type_mismatch<T: Any>(&self, value: &ConfigValue<'_>) -> ConfigError {
213 let tp = match value {
214 ConfigValue::StrRef(_) => "String",
215 ConfigValue::Str(_) => "String",
216 ConfigValue::Int(_) => "Integer",
217 ConfigValue::Float(_) => "Float",
218 ConfigValue::Bool(_) => "Bool",
219 #[cfg(feature = "rand")]
220 ConfigValue::Rand(_) => "Random",
221 };
222 ConfigError::ConfigTypeMismatch(self.current_key(), tp, type_name::<T>())
223 }
224
225 #[inline]
226 pub(crate) fn not_found(&self) -> ConfigError {
227 ConfigError::ConfigNotFound(self.current_key())
228 }
229
230 #[inline]
232 pub fn parse_error(&self, value: &str) -> ConfigError {
233 ConfigError::ConfigParseError(self.current_key(), value.to_owned())
234 }
235
236 pub(crate) fn collect_keys(&self) -> PartialKeyCollector<'a> {
237 let mut c = PartialKeyCollector::new();
238 self.source.collect_keys(&self.key, &mut c);
239 c
240 }
241}
242
243#[allow(missing_debug_implementations)]
246pub struct Configuration {
247 pub(crate) source: HashSource,
248 max: usize,
249 loaders: Vec<Box<dyn ConfigSource + Send + 'static>>,
250}
251
252impl_default!(Configuration);
253
254impl Configuration {
255 pub fn new() -> Self {
260 Self {
261 source: HashSource::new("configuration"),
262 max: 64,
263 loaders: vec![],
264 }
265 }
266
267 pub fn register_kv<N: Into<String>>(self, name: N) -> ManualSource {
269 ManualSource(self, HashSource::new(name))
270 }
271
272 pub fn register_prefix_env(self, prefix: &str) -> Result<Self, ConfigError> {
283 self.register_source(PrefixEnvironment::new(prefix))
284 }
285
286 pub fn register_file<P: Into<PathBuf>>(
295 self,
296 path: P,
297 required: bool,
298 ) -> Result<Self, ConfigError> {
299 register_by_ext(self, path.into(), required)
300 }
301
302 #[cfg(feature = "rand")]
318 #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
319 pub fn register_random(self) -> Result<Self, ConfigError> {
320 self.register_source(crate::source::random::Random)
321 }
322
323 pub fn register_source<L: ConfigSource + 'static>(
326 mut self,
327 loader: L,
328 ) -> Result<Self, ConfigError> {
329 if self.max <= self.loaders.len() {
330 return Err(ConfigError::TooManyInstances(self.max));
331 }
332 let loader = CacheConfigSource::new(loader);
333 let builder = &mut self.source.prefixed();
334 loader.load(builder)?;
335 cfg_log!(
336 log::Level::Debug,
337 "Config source {}:{} registered.",
338 self.loaders.len() + 1,
339 loader.name()
340 );
341 self.loaders.push(Box::new(loader));
342 Ok(self)
343 }
344
345 #[inline]
346 fn reload(&self) -> Result<(bool, Configuration), ConfigError> {
347 let mut s = Configuration::new();
348 let mut refreshed = false;
349 for i in self.loaders.iter() {
350 if i.refreshable()? {
351 refreshed = true;
352 }
353 }
354 if refreshed {
355 let c = &mut s.source.prefixed();
356 for i in self.loaders.iter() {
357 i.load(c)?;
358 }
359 self.source.refs.refresh(&s)?;
360 cfg_log!(log::Level::Info, "Configuration refreshed");
361 }
362 Ok((refreshed, s))
363 }
364
365 pub fn refresh_ref(&self) -> Result<bool, ConfigError> {
367 Ok(self.reload()?.0)
368 }
369
370 pub fn refresh(&mut self) -> Result<bool, ConfigError> {
372 let (x, c) = self.reload()?;
373 if x {
374 self.source.value = c.source.value;
375 }
376 Ok(x)
377 }
378
379 #[inline]
389 pub fn get<T: FromConfig>(&self, key: &str) -> Result<T, ConfigError> {
390 CacheString::with_key(|cache| {
391 let mut context = self.source.new_context(cache);
392 context.parse_config(key, None)
393 })
394 }
395
396 #[inline]
401 pub fn get_or<T: FromConfig>(&self, key: &str, def: T) -> Result<T, ConfigError> {
402 Ok(self.get::<Option<T>>(key)?.unwrap_or(def))
403 }
404
405 #[inline]
407 pub fn get_predefined<T: FromConfigWithPrefix>(&self) -> Result<T, ConfigError> {
408 self.get(T::prefix())
409 }
410
411 pub fn source_names(&self) -> Vec<&str> {
413 self.loaders.iter().map(|l| l.name()).collect()
414 }
415
416 pub fn with_predefined_builder() -> PredefinedConfigurationBuilder {
418 PredefinedConfigurationBuilder {
419 memory: HashSource::new("fixed:FromProgram/CommandLineArgs"),
420 cargo: None,
421 prefix: None,
422 init: None,
423 }
424 }
425
426 pub fn with_predefined() -> Result<Self, ConfigError> {
428 Self::with_predefined_builder().init()
429 }
430}
431
432#[allow(clippy::type_complexity, missing_debug_implementations)]
434pub struct PredefinedConfigurationBuilder {
435 memory: HashSource,
436 cargo: Option<Cargo>,
437 prefix: Option<String>,
438 init: Option<Box<dyn FnOnce(&Configuration) -> Result<(), ConfigError> + 'static>>,
439}
440
441impl PredefinedConfigurationBuilder {
442 pub fn set_prefix_env<K: ToString>(mut self, prefix: K) -> Self {
454 self.prefix = Some(prefix.to_string());
455 self
456 }
457
458 pub fn set_dir<V: Into<PathBuf>>(self, path: V) -> Self {
460 self.set("app.dir", path.into().display().to_string())
461 }
462
463 pub fn set_name<V: Into<String>>(self, name: V) -> Self {
465 self.set("app.name", name.into())
466 }
467
468 pub fn set_profile<V: Into<String>>(self, profile: V) -> Self {
470 self.set("app.profile", profile.into())
471 }
472
473 pub fn set<K: Borrow<str>, V: Into<ConfigValue<'static>>>(mut self, key: K, value: V) -> Self {
475 self.memory = self.memory.set(key, value);
476 self
477 }
478
479 pub fn set_cargo_env(mut self, cargo: Cargo) -> Self {
493 self.cargo = Some(cargo);
494 self
495 }
496
497 pub fn set_init<F: FnOnce(&Configuration) -> Result<(), ConfigError> + 'static>(
499 mut self,
500 f: F,
501 ) -> Self {
502 self.init = Some(Box::new(f));
503 self
504 }
505
506 pub fn init(self) -> Result<Configuration, ConfigError> {
529 let mut config = Configuration::new();
530
531 if let Some(cargo) = self.cargo {
533 config = config.register_source(cargo)?;
534 }
535
536 config = config.register_source(self.memory)?;
538
539 let option: SourceOption = config.get_predefined()?;
540
541 #[cfg(feature = "rand")]
543 if option.random.enabled {
544 config = config.register_random()?;
545 }
546
547 let prefix = self
549 .prefix
550 .or_else(|| config.get::<Option<String>>("env.prefix").ok().flatten())
551 .or_else(|| var("CFG_ENV_PREFIX").ok())
552 .unwrap_or_else(|| "CFG".to_owned());
553 config = config.register_prefix_env(&prefix)?;
554
555 if let Some(init) = self.init {
556 (init)(&config)?;
557 cfg_log!(log::Level::Info, "Early initialization completed.");
558 }
559
560 let app = config.get_predefined::<AppConfig>()?;
562 let mut path = PathBuf::new();
563 if let Some(d) = app.dir {
564 path.push(d);
565 };
566 if let Some(profile) = &app.profile {
567 let mut path = path.clone();
568 path.push(format!("{}-{}", app.name, profile));
569 config = register_files(config, &option, path, false)?;
570 }
571
572 path.push(app.name);
574 config = register_files(config, &option, path, false)?;
575
576 cfg_log!(
577 log::Level::Info,
578 "Predefined configuration initialization completed."
579 );
580 Ok(config)
581 }
582}
583
584#[derive(Debug, FromConfig)]
585#[config(prefix = "app", crate = "crate")]
586struct AppConfig {
587 #[config(default = "app")]
588 name: String,
589 dir: Option<String>,
590 profile: Option<String>,
591}
592
593#[allow(missing_debug_implementations)]
595pub struct ManualSource(Configuration, HashSource);
596
597impl ManualSource {
598 pub fn set<K: Borrow<str>, V: Into<ConfigValue<'static>>>(mut self, key: K, value: V) -> Self {
600 self.0.source = self.0.source.set(key, value);
601 self
602 }
603
604 pub fn finish(self) -> Result<Configuration, ConfigError> {
606 self.0.register_source(self.1)
607 }
608}
609
610#[cfg_attr(coverage_nightly, coverage(off))]
611#[cfg(test)]
612mod test {
613
614 use crate::{init_cargo_env, test::TestConfigExt};
615
616 use super::*;
617
618 macro_rules! should_eq {
619 ($context:ident: $val:literal as $t:ty = $x:expr ) => {
620 println!("{} key: {}", type_name::<$t>(), $val);
621 assert_eq!($x, &format!("{:?}", $context.get::<$t>($val)));
622 };
623 }
624
625 fn build_config() -> Configuration {
626 HashSource::new("test")
627 .set("a", "0")
628 .set("b", "${b}")
629 .set("c", "${a}")
630 .set("d", "${z}")
631 .set("e", "${z:}")
632 .set("f", "${z:${a}}")
633 .set("g", "a")
634 .set("h", "${${g}}")
635 .set("i", "\\$\\{a\\}")
636 .set("j", "${${g}:a}")
637 .set("k", "${a} ${a}")
638 .set("l", "${c}")
639 .set("m", "${no_found:${no_found_2:hello}}")
640 .set("n", "$")
641 .set("o", "\\")
642 .set("p", "}")
643 .set("q", "${")
644 .set("r", "${a}suffix")
645 .new_config()
646 .register_kv("test")
647 .set("a0", "0")
648 .set("a", "1")
649 .set("b", "1")
650 .set("c", "1")
651 .finish()
652 .unwrap()
653 }
654
655 #[test]
656 fn parse_string_test() {
657 let config = build_config();
658 should_eq!(config: "a0" as String = "Ok(\"0\")");
659
660 should_eq!(config: "a" as String = "Ok(\"0\")");
661 should_eq!(config: "b" as String = "Err(ConfigRecursiveError(\"b\"))");
662 should_eq!(config: "c" as String = "Ok(\"0\")");
663 should_eq!(config: "d" as String = "Err(ConfigRecursiveNotFound(\"z\"))");
664 should_eq!(config: "e" as String = "Ok(\"\")");
665 should_eq!(config: "f" as String = "Ok(\"0\")");
666 should_eq!(config: "g" as String = "Ok(\"a\")");
667 should_eq!(config: "h" as String = "Ok(\"0\")");
668 should_eq!(config: "i" as String = "Ok(\"${a}\")");
669 should_eq!(config: "j" as String = "Ok(\"0\")");
670 should_eq!(config: "k" as String = "Ok(\"0 0\")");
671 should_eq!(config: "l" as String = "Ok(\"0\")");
672 should_eq!(config: "m" as String = "Ok(\"hello\")");
673 should_eq!(config: "n" as String = "Err(ConfigParseError(\"n\", \"$\"))");
674 should_eq!(config: "o" as String = "Err(ConfigParseError(\"o\", \"\\\\\"))");
675 should_eq!(config: "p" as String = "Err(ConfigParseError(\"p\", \"}\"))");
676 should_eq!(config: "q" as String = "Err(ConfigParseError(\"q\", \"${\"))");
677 should_eq!(config: "r" as String = "Ok(\"0suffix\")");
678 }
679
680 #[test]
681 fn parse_bool_test() {
682 let config = build_config();
683 should_eq!(config: "a0" as bool = "Err(ConfigParseError(\"a0\", \"0\"))");
684
685 should_eq!(config: "a" as bool = "Err(ConfigParseError(\"a\", \"0\"))");
686 should_eq!(config: "b" as bool = "Err(ConfigRecursiveError(\"b\"))");
687 should_eq!(config: "c" as bool = "Err(ConfigParseError(\"c\", \"0\"))");
688 should_eq!(config: "d" as bool = "Err(ConfigRecursiveNotFound(\"z\"))");
689 should_eq!(config: "e" as bool = "Err(ConfigNotFound(\"e\"))");
690 should_eq!(config: "f" as bool = "Err(ConfigParseError(\"f\", \"0\"))");
691 should_eq!(config: "g" as bool = "Err(ConfigParseError(\"g\", \"a\"))");
692 should_eq!(config: "h" as bool = "Err(ConfigParseError(\"h\", \"0\"))");
693 should_eq!(config: "i" as bool = "Err(ConfigParseError(\"i\", \"${a}\"))");
694 should_eq!(config: "j" as bool = "Err(ConfigParseError(\"j\", \"0\"))");
695 should_eq!(config: "k" as bool = "Err(ConfigParseError(\"k\", \"0 0\"))");
696 should_eq!(config: "l" as bool = "Err(ConfigParseError(\"l\", \"0\"))");
697 should_eq!(config: "m" as bool = "Err(ConfigParseError(\"m\", \"hello\"))");
698 should_eq!(config: "n" as bool = "Err(ConfigParseError(\"n\", \"$\"))");
699 should_eq!(config: "o" as bool = "Err(ConfigParseError(\"o\", \"\\\\\"))");
700 should_eq!(config: "p" as bool = "Err(ConfigParseError(\"p\", \"}\"))");
701 should_eq!(config: "q" as bool = "Err(ConfigParseError(\"q\", \"${\"))");
702 }
703
704 #[test]
705 fn parse_u8_test() {
706 let config = build_config();
707 should_eq!(config: "a0" as u8 = "Ok(0)");
708
709 should_eq!(config: "a" as u8 = "Ok(0)");
710 should_eq!(config: "b" as u8 = "Err(ConfigRecursiveError(\"b\"))");
711 should_eq!(config: "c" as u8 = "Ok(0)");
712 should_eq!(config: "d" as u8 = "Err(ConfigRecursiveNotFound(\"z\"))");
713 should_eq!(config: "e" as u8 = "Err(ConfigNotFound(\"e\"))");
714 should_eq!(config: "f" as u8 = "Ok(0)");
715 should_eq!(config: "g" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
716 should_eq!(config: "h" as u8 = "Ok(0)");
717 should_eq!(config: "i" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
718 should_eq!(config: "j" as u8 = "Ok(0)");
719 should_eq!(config: "k" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
720 should_eq!(config: "l" as u8 = "Ok(0)");
721 should_eq!(config: "m" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
722 should_eq!(config: "n" as u8 = "Err(ConfigParseError(\"n\", \"$\"))");
723 should_eq!(config: "o" as u8 = "Err(ConfigParseError(\"o\", \"\\\\\"))");
724 should_eq!(config: "p" as u8 = "Err(ConfigParseError(\"p\", \"}\"))");
725 should_eq!(config: "q" as u8 = "Err(ConfigParseError(\"q\", \"${\"))");
726 }
727
728 #[test]
729 fn get_test() {
730 let config = build_config()
731 .register_kv("k1")
732 .set("key[0]", "xx")
733 .finish()
734 .unwrap()
735 .register_kv("k2")
736 .set("key[5]", "xx")
737 .finish()
738 .unwrap()
739 .register_kv("k3")
740 .set("key[3]", "xx")
741 .finish()
742 .unwrap();
743 assert_eq!(1, config.get_or("a1", 1).unwrap());
744 let v = config.get::<Vec<Option<String>>>("key").unwrap();
745 assert_eq!(
746 vec![
747 Some("xx".to_string()),
748 None,
749 None,
750 Some("xx".to_string()),
751 None,
752 Some("xx".to_string())
753 ],
754 v
755 );
756 }
757
758 #[test]
759 fn predefined_test() {
760 let _config = Configuration::with_predefined().unwrap();
761 let _conf2 = Configuration::with_predefined_builder().init().unwrap();
762 println!("Total count = {}", _conf2.source.value.len());
763 for v in _config.source_names() {
764 println!("{}", v);
765 }
766 assert_eq!(_conf2.source.value.len(), _config.source.value.len());
767
768 init_cargo_env!();
769 let _conf3 = Configuration::with_predefined_builder()
770 .set("key", "value")
771 .set_prefix_env("XXX")
772 .set_cargo_env(init_cargo_env())
773 .init()
774 .unwrap();
775
776 let _conf3 = Configuration::with_predefined_builder()
777 .set_dir("")
778 .set_name("app")
779 .set_profile("dev")
780 .set_prefix_env("XXX")
781 .set_cargo_env(init_cargo_env())
782 .init()
783 .unwrap();
784
785 match _config.register_file("/conf/no_extension", false) {
786 Err(ConfigError::ConfigFileNotSupported(_)) => {}
787 _ => assert_eq!(true, false),
788 }
789 match _conf2.register_file("/conf/app.not_exist", false) {
790 Err(ConfigError::ConfigFileNotSupported(_)) => {}
791 _ => assert_eq!(true, false),
792 }
793 }
794
795 #[test]
796 fn configuration_refresh_tests() {
797 let mut cfg = Configuration::new();
798 assert_eq!(cfg.refresh_ref().unwrap(), false);
800 assert_eq!(cfg.refresh().unwrap(), false);
801 }
802
803 #[test]
804 fn manual_source_chain_finish() {
805 let cfg = Configuration::new();
806 let manual = cfg.register_kv("ms").set("k", "v");
807 let cfg = manual.finish().unwrap();
808 let got = cfg.get::<String>("k").unwrap();
809 assert_eq!(got, "v".to_string());
810 }
811
812 use std::sync::atomic::{AtomicBool, Ordering};
813 use std::sync::Arc;
814
815 #[test]
816 fn set_init_should_be_called() {
817 let called = Arc::new(AtomicBool::new(false));
818 let flag = called.clone();
819
820 let builder =
821 Configuration::with_predefined_builder().set_init(move |_cfg: &Configuration| {
822 flag.store(true, Ordering::SeqCst);
824 Ok(())
825 });
826
827 let _ = builder.init().unwrap();
829 assert!(called.load(Ordering::SeqCst));
830 }
831
832 #[test]
833 fn set_init_error_propagates() {
834 let builder = Configuration::with_predefined_builder().set_init(|_cfg: &Configuration| {
835 Err(ConfigError::ConfigParseError(
836 "init".to_string(),
837 "err".to_string(),
838 ))
839 });
840
841 assert!(builder.init().is_err());
843 }
844
845 #[test]
846 fn app_config_default_and_parse() {
847 let src = HashSource::new("test")
849 .set("app.name", "myapp")
850 .set("app.dir", "/tmp")
851 .set("app.profile", "dev");
852
853 let app_cfg = ConfigContext {
854 key: CacheString::new().new_key(),
855 source: &src,
856 ref_value_flag: false,
857 }
858 .parse_config::<AppConfig>("app", None)
859 .unwrap();
860
861 assert_eq!(app_cfg.name, "myapp");
862 assert_eq!(app_cfg.dir.as_deref(), Some("/tmp"));
863 assert_eq!(app_cfg.profile.as_deref(), Some("dev"));
864
865 let src2 = HashSource::new("test");
867 let app_cfg = ConfigContext {
868 key: CacheString::new().new_key(),
869 source: &src2,
870 ref_value_flag: false,
871 }
872 .parse_config::<AppConfig>("app", None)
873 .unwrap();
874
875 assert_eq!(app_cfg.name, "app");
876 assert_eq!(app_cfg.dir, None);
877 assert_eq!(app_cfg.profile, None);
878 }
879}