cfg_rs/
configuration.rs

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/// Configuration Context.
26///
27/// Configuration context contains current level of config key and configuration instance.
28/// It is designed for parsing partial config by partial key and default value.
29#[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    /// Parse partial config by partial key and default value.
191    #[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    /// Get current key in context.
201    #[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    /// Parse config value error.
231    #[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/// Configuration Instance, See [Examples](https://github.com/leptonyu/cfg-rs/tree/main/examples),
244/// [How to Initialize Configuration](index.html#how-to-initialize-configuration) for details.
245#[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    /// Create an empty [`Configuration`].
256    ///
257    /// If you want to use predefined sources, please try [`Configuration::with_predefined`] or [`Configuration::with_predefined_builder`].
258    ///
259    pub fn new() -> Self {
260        Self {
261            source: HashSource::new("configuration"),
262            max: 64,
263            loaders: vec![],
264        }
265    }
266
267    /// Register key value manually.
268    pub fn register_kv<N: Into<String>>(self, name: N) -> ManualSource {
269        ManualSource(self, HashSource::new(name))
270    }
271
272    /// Register all env variables with prefix, default prefix is `CFG`.
273    ///
274    /// * `prefix` - Env variable prefix.
275    ///
276    /// If prefix is `CFG`, then all env variables with pattern `CFG_*` will be added into configuration.
277    ///
278    /// Examples:
279    /// 1. `CFG_APP_NAME` => `app.name`
280    /// 2. `CFG_APP_0_NAME` => `app[0].name`
281    ///
282    pub fn register_prefix_env(self, prefix: &str) -> Result<Self, ConfigError> {
283        self.register_source(PrefixEnvironment::new(prefix))
284    }
285
286    /// Register file source, this method uses file extension[^ext] to choose how to parsing configuration.
287    ///
288    /// * `path` - Config file path.
289    /// * `required` - Whether config file must exist.
290    ///
291    /// See [Supported File Formats](index.html#supported-file-format) for details.
292    ///
293    /// [^ext]: `cfg-rs` does not **enable** any file format by default, please enable specific features when use this method.
294    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    /// Register random value source, must enable feature **rand**.
303    ///
304    /// Supported integer types:
305    /// * random.u8
306    /// * random.u16
307    /// * random.u32
308    /// * random.u64
309    /// * random.u128
310    /// * random.usize
311    /// * random.i8
312    /// * random.i16
313    /// * random.i32
314    /// * random.i64
315    /// * random.i128
316    /// * random.isize
317    #[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    /// Register customized source, see [How to Initialize Configuration](index.html#how-to-initialize-configuration),
324    /// [ConfigSource](source/trait.ConfigSource.html) for details.
325    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    /// Refresh all [RefValue](struct.RefValue.html)s without change [`Configuration`] itself.
366    pub fn refresh_ref(&self) -> Result<bool, ConfigError> {
367        Ok(self.reload()?.0)
368    }
369
370    /// Refresh all [RefValue](struct.RefValue.html)s and [`Configuration`] itself.
371    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    /// Get config from configuration by key, see [`ConfigKey`] for the key's pattern details.
380    ///
381    /// * `key` - Config Key.
382    ///   Key examples:
383    /// 1. `cfg.v1`
384    /// 2. `cfg.v2[0]`
385    /// 3. `cfg.v3[0][1]`
386    /// 4. `cfg.v4.key`
387    /// 5. `cfg.v5.arr[0]`
388    #[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    /// Get config from configuration by key, otherwise return default. See [`ConfigKey`] for the key's pattern details.
397    ///
398    /// * `key` - Config Key.
399    /// * `def` - If config value is not found, then return def.
400    #[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    /// Get config with predefined key, which is automatically derived by [FromConfig](./derive.FromConfig.html#struct-annotation-attribute).
406    #[inline]
407    pub fn get_predefined<T: FromConfigWithPrefix>(&self) -> Result<T, ConfigError> {
408        self.get(T::prefix())
409    }
410
411    /// Get source names, just for test.
412    pub fn source_names(&self) -> Vec<&str> {
413        self.loaders.iter().map(|l| l.name()).collect()
414    }
415
416    /// Create predefined sources builder, see [init](struct.PredefinedConfigurationBuilder.html#method.init) for details.
417    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    /// Create predefined configuration, see [init](struct.PredefinedConfigurationBuilder.html#method.init) for details.
427    pub fn with_predefined() -> Result<Self, ConfigError> {
428        Self::with_predefined_builder().init()
429    }
430}
431
432/// Predefined Configuration Builder. See [init](struct.PredefinedConfigurationBuilder.html#method.init) for details.
433#[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    /// Set environment prefix, default value if `CFG`.
443    /// By default, following environment variables will be loaded in to configuration.
444    /// # Environment Variable Regex Pattern: `CFG_[0-9a-zA-Z]+`
445    ///
446    /// These are some predefined environment variables:
447    /// * `CFG_ENV_PREFIX=CFG`
448    /// * `CFG_APP_NAME=app`
449    /// * `CFG_APP_DIR=`
450    /// * `CFG_APP_PROFILE=`
451    ///
452    /// You can change `CFG` to other prefix by this method.
453    pub fn set_prefix_env<K: ToString>(mut self, prefix: K) -> Self {
454        self.prefix = Some(prefix.to_string());
455        self
456    }
457
458    /// Set config file directory.
459    pub fn set_dir<V: Into<PathBuf>>(self, path: V) -> Self {
460        self.set("app.dir", path.into().display().to_string())
461    }
462
463    /// Set config file name.
464    pub fn set_name<V: Into<String>>(self, name: V) -> Self {
465        self.set("app.name", name.into())
466    }
467
468    /// Set config file profile.
469    pub fn set_profile<V: Into<String>>(self, profile: V) -> Self {
470        self.set("app.profile", profile.into())
471    }
472
473    /// Set config into configuration by programming, or from command line arguments.
474    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    /// Set source from cargo build env. Macro [init_cargo_env](macro.init_cargo_env.html) will collect
480    /// all `CARGO_PKG_*` env variables, and `CARGO_BIN_NAME` into configuration.
481    ///
482    /// ### Usage
483    /// ```rust, no_run
484    /// use cfg_rs::*;
485    /// // Generate fn init_cargo_env().
486    /// init_cargo_env!();
487    /// let c = Configuration::with_predefined_builder()
488    ///   .set_cargo_env(init_cargo_env())
489    ///   .init()
490    ///   .unwrap();
491    /// ```
492    pub fn set_cargo_env(mut self, cargo: Cargo) -> Self {
493        self.cargo = Some(cargo);
494        self
495    }
496
497    /// Set init func, which will be run after env source loaded.
498    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    /// Initialize configuration by multiple predefined sources.
507    ///
508    /// ## Predefined Sources.
509    ///
510    /// 0. Cargo Package Env Variables (Must be explicitly set by [set_cargo_env](struct.PredefinedConfigurationBuilder.html#method.set_cargo_env)).
511    /// 1. Customized by Programming or Commandline Args.[^f_default]
512    /// 2. Random Value (Auto enabled with feature `rand`).
513    /// 3. Environment Variable with Prefix `CFG`, referto [set_prefix_env](struct.PredefinedConfigurationBuilder.html#method.set_prefix_env) for details.[^f_default]
514    /// 4. Profiled File Source with Path, `${app.dir}/${app.name}-${app.profile}.EXT`. EXT: toml, json, yaml.[^f_file]
515    /// 5. File Source with Path, `${app.dir}/${app.name}.EXT`. EXT: toml, json, yaml.[^f_file]
516    /// 6. Customized Source Can be Registered by [register_source](struct.Configuration.html#method.register_source).
517    ///
518    /// [^f_default]: Always be enabled.
519    ///
520    /// [^f_file]: See [Supported File Formats](index.html#supported-file-format) for details.
521    ///
522    /// ## Crate Feature
523    ///
524    /// * Feature `rand` to enable random value source.
525    /// * Feature `toml` to enable toml supports.
526    /// * Feature `yaml` to enable yaml supports.
527    /// * Feature `json` to enable json supports.
528    pub fn init(self) -> Result<Configuration, ConfigError> {
529        let mut config = Configuration::new();
530
531        // Layer 0, cargo dev envs.
532        if let Some(cargo) = self.cargo {
533            config = config.register_source(cargo)?;
534        }
535
536        // Layer 1, commandlines.
537        config = config.register_source(self.memory)?;
538
539        let option: SourceOption = config.get_predefined()?;
540
541        // Layer 2, random
542        #[cfg(feature = "rand")]
543        if option.random.enabled {
544            config = config.register_random()?;
545        }
546
547        // Layer 3, environment.
548        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        // Layer 4, profile file.
561        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        // Layer 5, file.
573        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/// Manually register key value to [`Configuration`].
594#[allow(missing_debug_implementations)]
595pub struct ManualSource(Configuration, HashSource);
596
597impl ManualSource {
598    /// Set config into configuration by programming, or from command line arguments.
599    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    /// Finish customized kv.
605    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        // No loader registered, refresh should return false
799        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                // Mark as called
823                flag.store(true, Ordering::SeqCst);
824                Ok(())
825            });
826
827        // init should succeed and call the closure
828        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        // init should return the error produced in the closure
842        assert!(builder.init().is_err());
843    }
844
845    #[test]
846    fn app_config_default_and_parse() {
847        // Construct a config with only the name field
848        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        // Test default values
866        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}