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")]
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(test)]
611mod test {
612
613    use crate::{init_cargo_env, test::TestConfigExt};
614
615    use super::*;
616
617    macro_rules! should_eq {
618        ($context:ident: $val:literal as $t:ty = $x:expr  ) => {
619            println!("{} key: {}", type_name::<$t>(), $val);
620            assert_eq!($x, &format!("{:?}", $context.get::<$t>($val)));
621        };
622    }
623
624    fn build_config() -> Configuration {
625        HashSource::new("test")
626            .set("a", "0")
627            .set("b", "${b}")
628            .set("c", "${a}")
629            .set("d", "${z}")
630            .set("e", "${z:}")
631            .set("f", "${z:${a}}")
632            .set("g", "a")
633            .set("h", "${${g}}")
634            .set("i", "\\$\\{a\\}")
635            .set("j", "${${g}:a}")
636            .set("k", "${a} ${a}")
637            .set("l", "${c}")
638            .set("m", "${no_found:${no_found_2:hello}}")
639            .set("n", "$")
640            .set("o", "\\")
641            .set("p", "}")
642            .set("q", "${")
643            .set("r", "${a}suffix")
644            .new_config()
645            .register_kv("test")
646            .set("a0", "0")
647            .set("a", "1")
648            .set("b", "1")
649            .set("c", "1")
650            .finish()
651            .unwrap()
652    }
653
654    #[test]
655    fn parse_string_test() {
656        let config = build_config();
657        should_eq!(config: "a0" as String = "Ok(\"0\")");
658
659        should_eq!(config: "a" as String = "Ok(\"0\")");
660        should_eq!(config: "b" as String = "Err(ConfigRecursiveError(\"b\"))");
661        should_eq!(config: "c" as String = "Ok(\"0\")");
662        should_eq!(config: "d" as String = "Err(ConfigRecursiveNotFound(\"z\"))");
663        should_eq!(config: "e" as String = "Ok(\"\")");
664        should_eq!(config: "f" as String = "Ok(\"0\")");
665        should_eq!(config: "g" as String = "Ok(\"a\")");
666        should_eq!(config: "h" as String = "Ok(\"0\")");
667        should_eq!(config: "i" as String = "Ok(\"${a}\")");
668        should_eq!(config: "j" as String = "Ok(\"0\")");
669        should_eq!(config: "k" as String = "Ok(\"0 0\")");
670        should_eq!(config: "l" as String = "Ok(\"0\")");
671        should_eq!(config: "m" as String = "Ok(\"hello\")");
672        should_eq!(config: "n" as String = "Err(ConfigParseError(\"n\", \"$\"))");
673        should_eq!(config: "o" as String = "Err(ConfigParseError(\"o\", \"\\\\\"))");
674        should_eq!(config: "p" as String = "Err(ConfigParseError(\"p\", \"}\"))");
675        should_eq!(config: "q" as String = "Err(ConfigParseError(\"q\", \"${\"))");
676        should_eq!(config: "r" as String = "Ok(\"0suffix\")");
677    }
678
679    #[test]
680    fn parse_bool_test() {
681        let config = build_config();
682        should_eq!(config: "a0" as bool = "Err(ConfigParseError(\"a0\", \"0\"))");
683
684        should_eq!(config: "a" as bool = "Err(ConfigParseError(\"a\", \"0\"))");
685        should_eq!(config: "b" as bool = "Err(ConfigRecursiveError(\"b\"))");
686        should_eq!(config: "c" as bool = "Err(ConfigParseError(\"c\", \"0\"))");
687        should_eq!(config: "d" as bool = "Err(ConfigRecursiveNotFound(\"z\"))");
688        should_eq!(config: "e" as bool = "Err(ConfigNotFound(\"e\"))");
689        should_eq!(config: "f" as bool = "Err(ConfigParseError(\"f\", \"0\"))");
690        should_eq!(config: "g" as bool = "Err(ConfigParseError(\"g\", \"a\"))");
691        should_eq!(config: "h" as bool = "Err(ConfigParseError(\"h\", \"0\"))");
692        should_eq!(config: "i" as bool = "Err(ConfigParseError(\"i\", \"${a}\"))");
693        should_eq!(config: "j" as bool = "Err(ConfigParseError(\"j\", \"0\"))");
694        should_eq!(config: "k" as bool = "Err(ConfigParseError(\"k\", \"0 0\"))");
695        should_eq!(config: "l" as bool = "Err(ConfigParseError(\"l\", \"0\"))");
696        should_eq!(config: "m" as bool = "Err(ConfigParseError(\"m\", \"hello\"))");
697        should_eq!(config: "n" as bool = "Err(ConfigParseError(\"n\", \"$\"))");
698        should_eq!(config: "o" as bool = "Err(ConfigParseError(\"o\", \"\\\\\"))");
699        should_eq!(config: "p" as bool = "Err(ConfigParseError(\"p\", \"}\"))");
700        should_eq!(config: "q" as bool = "Err(ConfigParseError(\"q\", \"${\"))");
701    }
702
703    #[test]
704    fn parse_u8_test() {
705        let config = build_config();
706        should_eq!(config: "a0" as u8 = "Ok(0)");
707
708        should_eq!(config: "a" as u8 = "Ok(0)");
709        should_eq!(config: "b" as u8 = "Err(ConfigRecursiveError(\"b\"))");
710        should_eq!(config: "c" as u8 = "Ok(0)");
711        should_eq!(config: "d" as u8 = "Err(ConfigRecursiveNotFound(\"z\"))");
712        should_eq!(config: "e" as u8 = "Err(ConfigNotFound(\"e\"))");
713        should_eq!(config: "f" as u8 = "Ok(0)");
714        should_eq!(config: "g" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
715        should_eq!(config: "h" as u8 = "Ok(0)");
716        should_eq!(config: "i" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
717        should_eq!(config: "j" as u8 = "Ok(0)");
718        should_eq!(config: "k" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
719        should_eq!(config: "l" as u8 = "Ok(0)");
720        should_eq!(config: "m" as u8 = "Err(ConfigCause(ParseIntError { kind: InvalidDigit }))");
721        should_eq!(config: "n" as u8 = "Err(ConfigParseError(\"n\", \"$\"))");
722        should_eq!(config: "o" as u8 = "Err(ConfigParseError(\"o\", \"\\\\\"))");
723        should_eq!(config: "p" as u8 = "Err(ConfigParseError(\"p\", \"}\"))");
724        should_eq!(config: "q" as u8 = "Err(ConfigParseError(\"q\", \"${\"))");
725    }
726
727    #[test]
728    fn get_test() {
729        let config = build_config()
730            .register_kv("k1")
731            .set("key[0]", "xx")
732            .finish()
733            .unwrap()
734            .register_kv("k2")
735            .set("key[5]", "xx")
736            .finish()
737            .unwrap()
738            .register_kv("k3")
739            .set("key[3]", "xx")
740            .finish()
741            .unwrap();
742        assert_eq!(1, config.get_or("a1", 1).unwrap());
743        let v = config.get::<Vec<Option<String>>>("key").unwrap();
744        assert_eq!(
745            vec![
746                Some("xx".to_string()),
747                None,
748                None,
749                Some("xx".to_string()),
750                None,
751                Some("xx".to_string())
752            ],
753            v
754        );
755    }
756
757    #[test]
758    fn predefined_test() {
759        let _config = Configuration::with_predefined().unwrap();
760        let _conf2 = Configuration::with_predefined_builder().init().unwrap();
761        println!("Total count = {}", _conf2.source.value.len());
762        for v in _config.source_names() {
763            println!("{}", v);
764        }
765        assert_eq!(_conf2.source.value.len(), _config.source.value.len());
766
767        init_cargo_env!();
768        let _conf3 = Configuration::with_predefined_builder()
769            .set("key", "value")
770            .set_prefix_env("XXX")
771            .set_cargo_env(init_cargo_env())
772            .init()
773            .unwrap();
774
775        let _conf3 = Configuration::with_predefined_builder()
776            .set_dir("")
777            .set_name("app")
778            .set_profile("dev")
779            .set_prefix_env("XXX")
780            .set_cargo_env(init_cargo_env())
781            .init()
782            .unwrap();
783
784        match _config.register_file("/conf/no_extension", false) {
785            Err(ConfigError::ConfigFileNotSupported(_)) => {}
786            _ => assert_eq!(true, false),
787        }
788        match _conf2.register_file("/conf/app.not_exist", false) {
789            Err(ConfigError::ConfigFileNotSupported(_)) => {}
790            _ => assert_eq!(true, false),
791        }
792    }
793
794    #[test]
795    fn configuration_refresh_tests() {
796        let mut cfg = Configuration::new();
797        // No loader registered, refresh should return false
798        assert_eq!(cfg.refresh_ref().unwrap(), false);
799        assert_eq!(cfg.refresh().unwrap(), false);
800    }
801
802    #[test]
803    fn manual_source_chain_finish() {
804        let cfg = Configuration::new();
805        let manual = cfg.register_kv("ms").set("k", "v");
806        let cfg = manual.finish().unwrap();
807        let got = cfg.get::<String>("k").unwrap();
808        assert_eq!(got, "v".to_string());
809    }
810
811    use std::sync::atomic::{AtomicBool, Ordering};
812    use std::sync::Arc;
813
814    #[test]
815    fn set_init_should_be_called() {
816        let called = Arc::new(AtomicBool::new(false));
817        let flag = called.clone();
818
819        let builder =
820            Configuration::with_predefined_builder().set_init(move |_cfg: &Configuration| {
821                // Mark as called
822                flag.store(true, Ordering::SeqCst);
823                Ok(())
824            });
825
826        // init should succeed and call the closure
827        let _ = builder.init().unwrap();
828        assert!(called.load(Ordering::SeqCst));
829    }
830
831    #[test]
832    fn set_init_error_propagates() {
833        let builder = Configuration::with_predefined_builder().set_init(|_cfg: &Configuration| {
834            Err(ConfigError::ConfigParseError(
835                "init".to_string(),
836                "err".to_string(),
837            ))
838        });
839
840        // init should return the error produced in the closure
841        assert!(builder.init().is_err());
842    }
843
844    #[test]
845    fn app_config_default_and_parse() {
846        // Construct a config with only the name field
847        let src = HashSource::new("test")
848            .set("app.name", "myapp")
849            .set("app.dir", "/tmp")
850            .set("app.profile", "dev");
851
852        let app_cfg = ConfigContext {
853            key: CacheString::new().new_key(),
854            source: &src,
855            ref_value_flag: false,
856        }
857        .parse_config::<AppConfig>("app", None)
858        .unwrap();
859
860        assert_eq!(app_cfg.name, "myapp");
861        assert_eq!(app_cfg.dir.as_deref(), Some("/tmp"));
862        assert_eq!(app_cfg.profile.as_deref(), Some("dev"));
863
864        // Test default values
865        let src2 = HashSource::new("test");
866        let app_cfg = ConfigContext {
867            key: CacheString::new().new_key(),
868            source: &src2,
869            ref_value_flag: false,
870        }
871        .parse_config::<AppConfig>("app", None)
872        .unwrap();
873
874        assert_eq!(app_cfg.name, "app");
875        assert_eq!(app_cfg.dir, None);
876        assert_eq!(app_cfg.profile, None);
877    }
878}