Skip to main content

qubit_config/
config.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! # Configuration Manager
11//!
12//! Provides storage, retrieval, and management of configurations.
13//!
14
15#![allow(private_bounds)]
16
17use serde::de::DeserializeOwned;
18use serde::{
19    Deserialize,
20    Serialize,
21};
22use serde_json::{
23    Map,
24    Value as JsonValue,
25};
26use std::collections::HashMap;
27use std::path::Path;
28
29use crate::ConfigPropertyMut;
30use crate::config_prefix_view::ConfigPrefixView;
31use crate::config_reader::ConfigReader;
32use crate::config_value_deserializer::ConfigValueDeserializer;
33use crate::constants::DEFAULT_MAX_SUBSTITUTION_DEPTH;
34use crate::field::ConfigField;
35use crate::from::{
36    FromConfig,
37    IntoConfigDefault,
38};
39use crate::options::ConfigReadOptions;
40#[cfg(feature = "source-env-file")]
41use crate::source::EnvFileConfigSource;
42#[cfg(feature = "source-toml")]
43use crate::source::TomlConfigSource;
44#[cfg(feature = "source-yaml")]
45use crate::source::YamlConfigSource;
46use crate::source::{
47    ConfigSource,
48    EnvConfigSource,
49    PropertiesConfigSource,
50};
51use crate::utils;
52use crate::{
53    ConfigError,
54    ConfigName,
55    ConfigNames,
56    ConfigResult,
57    Property,
58};
59use qubit_datatype::{
60    DataConvertTo,
61    DataConverter,
62    DataType,
63};
64use qubit_value::{
65    MultiValues,
66    Value as QubitValue,
67    ValueError,
68};
69
70pub(crate) fn convert_deserialize_number<T>(key: &str, options: &ConfigReadOptions, value: String) -> ConfigResult<T>
71where
72    for<'a> DataConverter<'a>: DataConvertTo<T>,
73{
74    match QubitValue::String(value).to_with::<T>(options.conversion_options()) {
75        Ok(value) => Ok(value),
76        Err(error) => Err(ConfigError::from((key, error))),
77    }
78}
79
80/// Returns `true` when `key` is strictly below `prefix`.
81fn is_child_key(key: &str, prefix: &str) -> bool {
82    key.len() > prefix.len() && key.starts_with(prefix) && key.as_bytes().get(prefix.len()) == Some(&b'.')
83}
84
85/// Returns whether a scalar string property is missing under deserialization options.
86fn scalar_string_is_missing_for_deserialize(
87    primary: &impl ConfigReader,
88    fallback: &impl ConfigReader,
89    key: &str,
90    property: &Property,
91    options: &ConfigReadOptions,
92) -> ConfigResult<bool> {
93    let MultiValues::String(values) = property.value() else {
94        return Ok(false);
95    };
96    let [value] = values.as_slice() else {
97        return Ok(false);
98    };
99    let value = if primary.is_enable_variable_substitution() {
100        utils::substitute_variables_with_fallback(value, primary, fallback, primary.max_substitution_depth())?
101    } else {
102        value.to_string()
103    };
104    match options.conversion_options().string.normalize(&value) {
105        Ok(_) => Ok(false),
106        Err(qubit_datatype::DataConversionError::NoValue) => Ok(true),
107        Err(error) => Err(ConfigError::from_data_conversion_error(key, error)),
108    }
109}
110
111/// Configuration Manager
112///
113/// Manages a set of configuration properties with type-safe read/write
114/// interfaces.
115///
116/// # Features
117///
118/// - Supports multiple data types
119/// - Supports variable substitution (`${var_name}` format)
120/// - Supports configuration merging
121/// - Supports final value protection
122/// - Thread-safe (when wrapped in `Arc<RwLock<Config>>`)
123///
124/// # Examples
125///
126/// ```rust
127/// use qubit_config::Config;
128///
129/// let mut config = Config::new();
130///
131/// // Set configuration values (type inference)
132/// config.set("port", 8080).unwrap();                    // inferred as i32
133/// config.set("host", "localhost").unwrap();
134/// // &str is converted to String
135/// config.set("debug", true).unwrap();                   // inferred as bool
136/// config.set("timeout", 30.5).unwrap();                 // inferred as f64
137/// config.set("code", 42u8).unwrap();                    // inferred as u8
138///
139/// // Set multiple values (type inference)
140/// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // inferred as i32
141/// config.set("hosts", vec!["host1", "host2"]).unwrap();
142/// // &str elements are converted
143///
144/// // Read configuration values (type inference)
145/// let port: i32 = config.get("port").unwrap();
146/// let host: String = config.get("host").unwrap();
147/// let debug: bool = config.get("debug").unwrap();
148/// let code: u8 = config.get("code").unwrap();
149///
150/// // Read configuration values (turbofish)
151/// let port = config.get::<i32>("port").unwrap();
152///
153/// // Read configuration value or use default
154/// let timeout: f64 = config.get_or("timeout", 30.0).unwrap();
155/// ```
156///
157///
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct Config {
160    /// Configuration description
161    description: Option<String>,
162    /// Configuration property mapping
163    pub(crate) properties: HashMap<String, Property>,
164    /// Whether variable substitution is enabled
165    enable_variable_substitution: bool,
166    /// Maximum depth for variable substitution
167    max_substitution_depth: usize,
168    /// Runtime read parsing options
169    #[serde(default)]
170    read_options: ConfigReadOptions,
171}
172
173impl Config {
174    /// Creates a new empty configuration
175    ///
176    /// # Returns
177    ///
178    /// Returns a new configuration instance
179    ///
180    /// # Examples
181    ///
182    /// ```rust
183    /// use qubit_config::Config;
184    ///
185    /// let mut config = Config::new();
186    /// assert!(config.is_empty());
187    /// ```
188    #[inline]
189    pub fn new() -> Self {
190        Self {
191            description: None,
192            properties: HashMap::new(),
193            enable_variable_substitution: true,
194            max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
195            read_options: ConfigReadOptions::default(),
196        }
197    }
198
199    /// Creates a configuration with description
200    ///
201    /// # Parameters
202    ///
203    /// * `description` - Configuration description
204    ///
205    /// # Returns
206    ///
207    /// Returns a new configuration instance
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use qubit_config::Config;
213    ///
214    /// let config = Config::with_description("Server Configuration");
215    /// assert_eq!(config.description(), Some("Server Configuration"));
216    /// ```
217    #[inline]
218    pub fn with_description(description: &str) -> Self {
219        Self {
220            description: Some(description.to_string()),
221            properties: HashMap::new(),
222            enable_variable_substitution: true,
223            max_substitution_depth: DEFAULT_MAX_SUBSTITUTION_DEPTH,
224            read_options: ConfigReadOptions::default(),
225        }
226    }
227
228    // ========================================================================
229    // Basic Property Access
230    // ========================================================================
231
232    /// Gets the configuration description
233    ///
234    /// # Returns
235    ///
236    /// Returns the configuration description as Option
237    #[inline]
238    pub fn description(&self) -> Option<&str> {
239        self.description.as_deref()
240    }
241
242    /// Sets the configuration description
243    ///
244    /// # Parameters
245    ///
246    /// * `description` - Configuration description
247    ///
248    /// # Returns
249    ///
250    /// Nothing.
251    #[inline]
252    pub fn set_description(&mut self, description: Option<String>) {
253        self.description = description;
254    }
255
256    /// Checks if variable substitution is enabled
257    ///
258    /// # Returns
259    ///
260    /// Returns `true` if variable substitution is enabled
261    #[inline]
262    pub fn is_enable_variable_substitution(&self) -> bool {
263        self.enable_variable_substitution
264    }
265
266    /// Sets whether to enable variable substitution
267    ///
268    /// # Parameters
269    ///
270    /// * `enable` - Whether to enable
271    ///
272    /// # Returns
273    ///
274    /// Nothing.
275    #[inline]
276    pub fn set_enable_variable_substitution(&mut self, enable: bool) {
277        self.enable_variable_substitution = enable;
278    }
279
280    /// Gets the maximum depth for variable substitution
281    ///
282    /// # Returns
283    ///
284    /// Returns the maximum depth value
285    #[inline]
286    pub fn max_substitution_depth(&self) -> usize {
287        self.max_substitution_depth
288    }
289
290    /// Gets the global read parsing options.
291    ///
292    /// # Returns
293    ///
294    /// The options used by `get`, `get_any`, and field reads when no
295    /// field-level override is provided.
296    #[inline]
297    pub fn read_options(&self) -> &ConfigReadOptions {
298        &self.read_options
299    }
300
301    /// Sets the global read parsing options.
302    ///
303    /// # Parameters
304    ///
305    /// * `read_options` - New read parsing options.
306    ///
307    /// # Returns
308    ///
309    /// Mutable reference to this configuration for chaining.
310    #[inline]
311    pub fn set_read_options(&mut self, read_options: ConfigReadOptions) -> &mut Self {
312        self.read_options = read_options;
313        self
314    }
315
316    /// Returns a cloned configuration with different read parsing options.
317    ///
318    /// # Parameters
319    ///
320    /// * `read_options` - Read options for the returned configuration.
321    ///
322    /// # Returns
323    ///
324    /// A cloned [`Config`] using `read_options`.
325    #[must_use]
326    pub fn with_read_options(&self, read_options: ConfigReadOptions) -> Self {
327        let mut config = self.clone();
328        config.read_options = read_options;
329        config
330    }
331
332    /// Creates a read-only prefix view using [`ConfigPrefixView`].
333    ///
334    /// # Parameters
335    ///
336    /// * `prefix` - Prefix
337    ///
338    /// # Returns
339    ///
340    /// Returns a read-only prefix view
341    ///
342    /// # Examples
343    ///
344    /// ```rust
345    /// use qubit_config::{Config, ConfigReader};
346    ///
347    /// let mut config = Config::new();
348    /// config.set("server.port", 8080).unwrap();
349    /// config.set("server.host", "localhost").unwrap();
350    ///
351    /// let server = config.prefix_view("server");
352    /// assert_eq!(server.get::<i32>("port").unwrap(), 8080);
353    /// assert_eq!(server.get::<String>("host").unwrap(), "localhost");
354    /// ```
355    #[inline]
356    pub fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
357        ConfigPrefixView::new(self, prefix)
358    }
359
360    /// Sets the maximum depth for variable substitution
361    ///
362    /// # Parameters
363    ///
364    /// * `depth` - Maximum depth
365    ///
366    /// # Returns
367    ///
368    /// Nothing.
369    #[inline]
370    pub fn set_max_substitution_depth(&mut self, depth: usize) {
371        self.max_substitution_depth = depth;
372    }
373
374    // ========================================================================
375    // Configuration Item Management
376    // ========================================================================
377
378    /// Checks if the configuration contains an item with the specified name
379    ///
380    /// # Parameters
381    ///
382    /// * `name` - Configuration item name
383    ///
384    /// # Returns
385    ///
386    /// Returns `true` if the configuration item exists
387    ///
388    /// # Examples
389    ///
390    /// ```rust
391    /// use qubit_config::Config;
392    ///
393    /// let mut config = Config::new();
394    /// config.set("port", 8080).unwrap();
395    ///
396    /// assert!(config.contains("port"));
397    /// assert!(!config.contains("host"));
398    /// ```
399    #[inline]
400    pub fn contains(&self, name: impl ConfigName) -> bool {
401        name.with_config_name(|name| self.properties.contains_key(name))
402    }
403
404    /// Gets a reference to a configuration item
405    ///
406    /// # Parameters
407    ///
408    /// * `name` - Configuration item name
409    ///
410    /// # Returns
411    ///
412    /// Returns Option containing the configuration item
413    #[inline]
414    pub fn get_property(&self, name: impl ConfigName) -> Option<&Property> {
415        name.with_config_name(|name| self.properties.get(name))
416    }
417
418    /// Gets guarded mutable access to a non-final configuration item.
419    ///
420    /// # Parameters
421    ///
422    /// * `name` - Configuration item name
423    ///
424    /// # Returns
425    ///
426    /// Returns `Ok(Some(_))` for an existing non-final property, `Ok(None)`
427    /// for a missing property, or [`ConfigError::PropertyIsFinal`] for an
428    /// existing final property. The returned guard re-checks final state before
429    /// each value-changing operation.
430    #[inline]
431    pub fn get_property_mut(&mut self, name: impl ConfigName) -> ConfigResult<Option<ConfigPropertyMut<'_>>> {
432        name.with_config_name(|name| {
433            self.ensure_property_not_final(name)?;
434            Ok(self.properties.get_mut(name).map(ConfigPropertyMut::new))
435        })
436    }
437
438    /// Sets the final flag of an existing configuration item.
439    ///
440    /// A non-final property can be marked final. A property that is already
441    /// final may be marked final again, but cannot be unset through this API.
442    ///
443    /// # Parameters
444    ///
445    /// * `name` - Configuration item name.
446    /// * `is_final` - Whether the property should be final.
447    ///
448    /// # Returns
449    ///
450    /// `Ok(())` on success.
451    ///
452    /// # Errors
453    ///
454    /// - [`ConfigError::PropertyNotFound`] if the key does not exist.
455    /// - [`ConfigError::PropertyIsFinal`] when trying to unset a final
456    ///   property.
457    pub fn set_final(&mut self, name: impl ConfigName, is_final: bool) -> ConfigResult<()> {
458        name.with_config_name(|name| {
459            let property = self
460                .properties
461                .get_mut(name)
462                .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))?;
463            if property.is_final() && !is_final {
464                return Err(ConfigError::PropertyIsFinal(name.to_string()));
465            }
466            property.set_final(is_final);
467            Ok(())
468        })
469    }
470
471    /// Removes a non-final configuration item.
472    ///
473    /// # Parameters
474    ///
475    /// * `name` - Configuration item name
476    ///
477    /// # Returns
478    ///
479    /// Returns the removed configuration item, or None if it doesn't exist
480    ///
481    /// # Examples
482    ///
483    /// ```rust
484    /// use qubit_config::Config;
485    ///
486    /// let mut config = Config::new();
487    /// config.set("port", 8080).unwrap();
488    ///
489    /// let removed = config.remove("port").unwrap();
490    /// assert!(removed.is_some());
491    /// assert!(!config.contains("port"));
492    /// ```
493    #[inline]
494    pub fn remove(&mut self, name: impl ConfigName) -> ConfigResult<Option<Property>> {
495        name.with_config_name(|name| {
496            self.ensure_property_not_final(name)?;
497            Ok(self.properties.remove(name))
498        })
499    }
500
501    /// Clears all configuration items if none of them are final.
502    ///
503    /// # Examples
504    ///
505    /// ```rust
506    /// use qubit_config::Config;
507    ///
508    /// let mut config = Config::new();
509    /// config.set("port", 8080).unwrap();
510    /// config.set("host", "localhost").unwrap();
511    ///
512    /// config.clear().unwrap();
513    /// assert!(config.is_empty());
514    /// ```
515    ///
516    /// # Returns
517    ///
518    /// `Ok(())` when all properties were removed.
519    #[inline]
520    pub fn clear(&mut self) -> ConfigResult<()> {
521        self.ensure_no_final_properties()?;
522        self.properties.clear();
523        Ok(())
524    }
525
526    /// Gets the number of configuration items
527    ///
528    /// # Returns
529    ///
530    /// Returns the number of configuration items
531    #[inline]
532    pub fn len(&self) -> usize {
533        self.properties.len()
534    }
535
536    /// Checks if the configuration is empty
537    ///
538    /// # Returns
539    ///
540    /// Returns `true` if the configuration contains no items
541    #[inline]
542    pub fn is_empty(&self) -> bool {
543        self.properties.is_empty()
544    }
545
546    /// Gets all configuration item names
547    ///
548    /// # Returns
549    ///
550    /// Returns a Vec of configuration item names
551    ///
552    /// # Examples
553    ///
554    /// ```rust
555    /// use qubit_config::Config;
556    ///
557    /// let mut config = Config::new();
558    /// config.set("port", 8080).unwrap();
559    /// config.set("host", "localhost").unwrap();
560    ///
561    /// let keys = config.keys();
562    /// assert_eq!(keys.len(), 2);
563    /// assert!(keys.contains(&"port".to_string()));
564    /// assert!(keys.contains(&"host".to_string()));
565    /// ```
566    pub fn keys(&self) -> Vec<String> {
567        self.properties.keys().cloned().collect()
568    }
569
570    /// Looks up a property by key for internal read paths.
571    ///
572    /// # Parameters
573    ///
574    /// * `name` - Configuration key
575    ///
576    /// # Returns
577    ///
578    /// `Ok(&Property)` if the key exists, or [`ConfigError::PropertyNotFound`]
579    /// otherwise.
580    #[inline]
581    fn get_property_by_name(&self, name: &str) -> ConfigResult<&Property> {
582        self.properties
583            .get(name)
584            .ok_or_else(|| ConfigError::PropertyNotFound(name.to_string()))
585    }
586
587    /// Ensures the entry for `name` is not marked final before a write.
588    ///
589    /// Missing keys are allowed (writes may create them).
590    ///
591    /// # Parameters
592    ///
593    /// * `name` - Configuration key
594    ///
595    /// # Returns
596    ///
597    /// `Ok(())` if the key is absent or not final, or
598    /// [`ConfigError::PropertyIsFinal`] if an existing property is final.
599    #[inline]
600    fn ensure_property_not_final(&self, name: &str) -> ConfigResult<()> {
601        if let Some(prop) = self.properties.get(name)
602            && prop.is_final()
603        {
604            return Err(ConfigError::PropertyIsFinal(name.to_string()));
605        }
606        Ok(())
607    }
608
609    /// Ensures no property is final before a bulk destructive operation.
610    #[inline]
611    fn ensure_no_final_properties(&self) -> ConfigResult<()> {
612        if let Some((name, _)) = self.properties.iter().find(|(_, prop)| prop.is_final()) {
613            return Err(ConfigError::PropertyIsFinal(name.clone()));
614        }
615        Ok(())
616    }
617
618    // ========================================================================
619    // Core Generic Methods
620    // ========================================================================
621
622    /// Gets a configuration value, converting the stored first value to `T`.
623    ///
624    /// Core read API with type inference.
625    ///
626    /// This method does not perform `${...}` variable substitution. Use
627    /// [`Self::get_string`], [`Self::get_string_list`], or
628    /// [`Self::deserialize`] when placeholders should be resolved while reading.
629    ///
630    /// # Type Parameters
631    ///
632    /// * `T` - Target type supported by [`FromConfig`]
633    ///
634    /// # Parameters
635    ///
636    /// * `name` - Configuration item name
637    ///
638    /// # Returns
639    ///
640    /// The value of the specified type on success, or a [`ConfigError`] on
641    /// failure.
642    ///
643    /// # Errors
644    ///
645    /// - [`ConfigError::PropertyNotFound`] if the key does not exist
646    /// - [`ConfigError::PropertyHasNoValue`] if the property has no value
647    /// - [`ConfigError::ConversionError`] if the stored value cannot be
648    ///   converted to `T`
649    ///
650    /// # Examples
651    ///
652    /// ```rust
653    /// use qubit_config::Config;
654    ///
655    /// let mut config = Config::new();
656    /// config.set("port", 8080).unwrap();
657    /// config.set("host", "localhost").unwrap();
658    ///
659    /// // Method 1: Type inference
660    /// let port: i32 = config.get("port").unwrap();
661    /// let host: String = config.get("host").unwrap();
662    ///
663    /// // Method 2: Turbofish
664    /// let port = config.get::<i32>("port").unwrap();
665    /// let host = config.get::<String>("host").unwrap();
666    ///
667    /// // Method 3: Inference from usage
668    /// fn start_server(port: i32, host: String) { }
669    /// start_server(config.get("port").unwrap(), config.get("host").unwrap());
670    /// ```
671    pub fn get<T>(&self, name: impl ConfigName) -> ConfigResult<T>
672    where
673        T: FromConfig,
674    {
675        <Self as ConfigReader>::get(self, name)
676    }
677
678    /// Gets a configuration value only when the stored value already has the
679    /// exact requested type.
680    ///
681    /// Unlike [`Self::get`], this method preserves the pre-conversion read
682    /// semantics. For example, a stored string `"1"` can be read as `bool` by
683    /// [`Self::get`], but [`Self::get_strict`] returns
684    /// [`ConfigError::TypeMismatch`].
685    ///
686    /// # Type Parameters
687    ///
688    /// * `T` - Exact target type supported by `TryFrom<&MultiValues>`.
689    ///
690    /// # Parameters
691    ///
692    /// * `name` - Configuration item name
693    ///
694    /// # Returns
695    ///
696    /// The exact typed value on success, or a [`ConfigError`] on failure.
697    pub fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
698    where
699        for<'a> T: TryFrom<&'a MultiValues, Error = ValueError>,
700    {
701        name.with_config_name(|name| {
702            let property = self.get_property_by_name(name)?;
703
704            property.get_first::<T>().map_err(|e| utils::map_value_error(name, e))
705        })
706    }
707
708    /// Gets a configuration value or returns a default value.
709    ///
710    /// Returns `default` only if the key is missing or explicitly empty.
711    /// Conversion and substitution errors are returned.
712    ///
713    /// # Type Parameters
714    ///
715    /// * `T` - Target type supported by [`FromConfig`]
716    ///
717    /// # Parameters
718    ///
719    /// * `name` - Configuration item name
720    /// * `default` - Default value
721    ///
722    /// # Returns
723    ///
724    /// Returns the configuration value or default value. Conversion and
725    /// substitution errors are returned instead of being hidden by the default.
726    ///
727    /// # Examples
728    ///
729    /// ```rust
730    /// use qubit_config::Config;
731    ///
732    /// let config = Config::new();
733    ///
734    /// let port: i32 = config.get_or("port", 8080).unwrap();
735    /// let host: String = config.get_or("host", "localhost").unwrap();
736    ///
737    /// assert_eq!(port, 8080);
738    /// assert_eq!(host, "localhost");
739    /// ```
740    pub fn get_or<T>(&self, name: impl ConfigName, default: impl IntoConfigDefault<T>) -> ConfigResult<T>
741    where
742        T: FromConfig,
743    {
744        <Self as ConfigReader>::get_or(self, name, default)
745    }
746
747    /// Gets the first configured value from `names`.
748    ///
749    /// # Parameters
750    ///
751    /// * `names` - Candidate keys checked in priority order.
752    ///
753    /// # Returns
754    ///
755    /// Parsed value from the first present and non-empty key.
756    pub fn get_any<T>(&self, names: impl ConfigNames) -> ConfigResult<T>
757    where
758        T: FromConfig,
759    {
760        <Self as ConfigReader>::get_any(self, names)
761    }
762
763    /// Gets an optional value from the first configured key.
764    ///
765    /// # Parameters
766    ///
767    /// * `names` - Candidate keys checked in priority order.
768    ///
769    /// # Returns
770    ///
771    /// `Ok(None)` when all keys are missing or empty.
772    pub fn get_optional_any<T>(&self, names: impl ConfigNames) -> ConfigResult<Option<T>>
773    where
774        T: FromConfig,
775    {
776        <Self as ConfigReader>::get_optional_any(self, names)
777    }
778
779    /// Gets the first configured value from `names`, or `default` when absent.
780    ///
781    /// # Parameters
782    ///
783    /// * `names` - Candidate keys checked in priority order.
784    /// * `default` - Fallback used only when all keys are missing or empty.
785    ///
786    /// # Returns
787    ///
788    /// Parsed value or `default`; conversion errors are returned.
789    pub fn get_any_or<T>(&self, names: impl ConfigNames, default: impl IntoConfigDefault<T>) -> ConfigResult<T>
790    where
791        T: FromConfig,
792    {
793        <Self as ConfigReader>::get_any_or(self, names, default)
794    }
795
796    /// Gets the first configured value from `names` with explicit read options,
797    /// or `default` when absent.
798    ///
799    /// # Parameters
800    ///
801    /// * `names` - Candidate keys checked in priority order.
802    /// * `default` - Fallback used only when all keys are missing or empty.
803    /// * `read_options` - Parsing options for this read.
804    ///
805    /// # Returns
806    ///
807    /// Parsed value or `default`; conversion errors are returned.
808    pub fn get_any_or_with<T>(
809        &self,
810        names: impl ConfigNames,
811        default: impl IntoConfigDefault<T>,
812        read_options: &ConfigReadOptions,
813    ) -> ConfigResult<T>
814    where
815        T: FromConfig,
816    {
817        <Self as ConfigReader>::get_any_or_with(self, names, default, read_options)
818    }
819
820    /// Reads a declared configuration field.
821    ///
822    /// # Parameters
823    ///
824    /// * `field` - Field declaration with name, aliases, defaults, and optional
825    ///   read options.
826    ///
827    /// # Returns
828    ///
829    /// Parsed field value or default.
830    pub fn read<T>(&self, field: ConfigField<T>) -> ConfigResult<T>
831    where
832        T: FromConfig,
833    {
834        <Self as ConfigReader>::read(self, field)
835    }
836
837    /// Reads an optional declared configuration field.
838    ///
839    /// # Parameters
840    ///
841    /// * `field` - Field declaration.
842    ///
843    /// # Returns
844    ///
845    /// Parsed field value, default, or `None`.
846    pub fn read_optional<T>(&self, field: ConfigField<T>) -> ConfigResult<Option<T>>
847    where
848        T: FromConfig,
849    {
850        <Self as ConfigReader>::read_optional(self, field)
851    }
852
853    /// Gets a list of configuration values, converting each stored element to
854    /// `T`.
855    ///
856    /// Gets all values of a configuration item (multi-value configuration).
857    ///
858    /// # Type Parameters
859    ///
860    /// * `T` - Target type supported by [`FromConfig`]
861    ///
862    /// # Parameters
863    ///
864    /// * `name` - Configuration item name
865    ///
866    /// # Returns
867    ///
868    /// Returns a list of values on success, or an error on failure
869    ///
870    /// # Examples
871    ///
872    /// ```rust
873    /// use qubit_config::Config;
874    ///
875    /// let mut config = Config::new();
876    /// config.set("ports", vec![8080, 8081, 8082]).unwrap();
877    ///
878    /// let ports: Vec<i32> = config.get_list("ports").unwrap();
879    /// assert_eq!(ports, vec![8080, 8081, 8082]);
880    /// ```
881    pub fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
882    where
883        T: FromConfig,
884    {
885        <Self as ConfigReader>::get(self, name)
886    }
887
888    /// Gets all configuration values only when the stored values already have
889    /// the exact requested element type.
890    ///
891    /// Unlike [`Self::get_list`], this method preserves the pre-conversion
892    /// list read semantics. It returns an empty vector for empty properties and
893    /// [`ConfigError::TypeMismatch`] for non-empty values of another stored
894    /// type.
895    ///
896    /// # Type Parameters
897    ///
898    /// * `T` - Exact element type supported by `TryFrom<&MultiValues>`.
899    ///
900    /// # Parameters
901    ///
902    /// * `name` - Configuration item name
903    ///
904    /// # Returns
905    ///
906    /// A vector of exact typed values on success, or a [`ConfigError`] on
907    /// failure.
908    pub fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
909    where
910        for<'a> Vec<T>: TryFrom<&'a MultiValues, Error = ValueError>,
911    {
912        name.with_config_name(|name| {
913            let property = self.get_property_by_name(name)?;
914            if property.is_empty() {
915                return Ok(Vec::new());
916            }
917
918            property.get::<T>().map_err(|e| utils::map_value_error(name, e))
919        })
920    }
921
922    /// Sets a configuration value
923    ///
924    /// This is the core method for setting configuration values, supporting
925    /// type inference.
926    ///
927    /// # Type Parameters
928    ///
929    /// * `T` - Element type, automatically inferred from the `values` parameter
930    ///
931    /// # Parameters
932    ///
933    /// * `name` - Configuration item name
934    /// * `values` - Value to store; supports `T`, `Vec<T>`, `&[T]`, and related
935    ///   forms accepted by [`MultiValues`] setters
936    ///
937    /// # Returns
938    ///
939    /// Returns Ok(()) on success, or an error on failure
940    ///
941    /// # Errors
942    ///
943    /// - [`ConfigError::PropertyIsFinal`] if the property is marked final
944    ///
945    /// # Examples
946    ///
947    /// ```rust
948    /// use qubit_config::Config;
949    ///
950    /// let mut config = Config::new();
951    ///
952    /// // Set single values (type auto-inference)
953    /// config.set("port", 8080).unwrap();                    // T inferred as i32
954    /// config.set("host", "localhost").unwrap();
955    /// // T inferred as String; &str is converted
956    /// config.set("debug", true).unwrap();                   // T inferred as bool
957    /// config.set("timeout", 30.5).unwrap();                 // T inferred as f64
958    ///
959    /// // Set multiple values (type auto-inference)
960    /// config.set("ports", vec![8080, 8081, 8082]).unwrap(); // T inferred as i32
961    /// config.set("hosts", vec!["host1", "host2"]).unwrap();
962    /// // T inferred as &str (then converted)
963    /// ```
964    pub fn set<S>(&mut self, name: impl ConfigName, values: S) -> ConfigResult<()>
965    where
966        S: Into<MultiValues>,
967    {
968        name.with_config_name(|name| {
969            self.ensure_property_not_final(name)?;
970            let property = self
971                .properties
972                .entry(name.to_string())
973                .or_insert_with(|| Property::new(name));
974
975            property.set(values).map_err(ConfigError::from)
976        })
977    }
978
979    /// Adds configuration values
980    ///
981    /// Adds values to an existing configuration item (multi-value properties).
982    ///
983    /// # Type Parameters
984    ///
985    /// * `T` - Element type, automatically inferred from the `values` parameter
986    ///
987    /// # Parameters
988    ///
989    /// * `name` - Configuration item name
990    /// * `values` - Values to append; supports the same forms as [`Self::set`]
991    ///
992    /// # Returns
993    ///
994    /// Returns Ok(()) on success, or an error on failure
995    ///
996    /// # Examples
997    ///
998    /// ```rust
999    /// use qubit_config::Config;
1000    ///
1001    /// let mut config = Config::new();
1002    /// config.set("port", 8080).unwrap();                    // Set initial value
1003    /// config.add("port", 8081).unwrap();                    // Add single value
1004    /// config.add("port", vec![8082, 8083]).unwrap();        // Add multiple values
1005    /// config.add("port", vec![8084, 8085]).unwrap();       // Add slice
1006    ///
1007    /// let ports: Vec<i32> = config.get_list("port").unwrap();
1008    /// assert_eq!(ports, vec![8080, 8081, 8082, 8083, 8084, 8085]);
1009    /// ```
1010    pub fn add<S>(&mut self, name: impl ConfigName, values: S) -> ConfigResult<()>
1011    where
1012        S: Into<MultiValues>,
1013    {
1014        name.with_config_name(|name| {
1015            self.ensure_property_not_final(name)?;
1016
1017            if let Some(property) = self.properties.get_mut(name) {
1018                property.add(values).map_err(ConfigError::from)
1019            } else {
1020                let mut property = Property::new(name);
1021                property.set(values).map_err(ConfigError::from)?;
1022                self.properties.insert(name.to_string(), property);
1023                Ok(())
1024            }
1025        })
1026    }
1027
1028    // ========================================================================
1029    // String Special Handling (Variable Substitution)
1030    // ========================================================================
1031
1032    /// Gets a string configuration value (with variable substitution)
1033    ///
1034    /// If variable substitution is enabled, replaces `${var_name}` placeholders
1035    /// in the stored string.
1036    ///
1037    /// # Parameters
1038    ///
1039    /// * `name` - Configuration item name
1040    ///
1041    /// # Returns
1042    ///
1043    /// Returns the string value on success, or an error on failure
1044    ///
1045    /// # Examples
1046    ///
1047    /// ```rust
1048    /// use qubit_config::Config;
1049    ///
1050    /// let mut config = Config::new();
1051    /// config.set("base_url", "http://localhost").unwrap();
1052    /// config.set("api_url", "${base_url}/api").unwrap();
1053    ///
1054    /// let api_url = config.get_string("api_url").unwrap();
1055    /// assert_eq!(api_url, "http://localhost/api");
1056    /// ```
1057    pub fn get_string(&self, name: impl ConfigName) -> ConfigResult<String> {
1058        <Self as ConfigReader>::get_string(self, name)
1059    }
1060
1061    /// Gets a string value from the first present and non-empty key in `names`.
1062    ///
1063    /// # Parameters
1064    ///
1065    /// * `names` - Candidate keys checked in priority order.
1066    ///
1067    /// # Returns
1068    ///
1069    /// Returns the string value on success, or an error on failure.
1070    pub fn get_string_any(&self, names: impl ConfigNames) -> ConfigResult<String> {
1071        <Self as ConfigReader>::get_string_any(self, names)
1072    }
1073
1074    /// Gets an optional string value from the first present and non-empty key.
1075    ///
1076    /// # Parameters
1077    ///
1078    /// * `names` - Candidate keys checked in priority order.
1079    ///
1080    /// # Returns
1081    ///
1082    /// `Ok(None)` when all keys are missing or empty.
1083    pub fn get_optional_string_any(&self, names: impl ConfigNames) -> ConfigResult<Option<String>> {
1084        <Self as ConfigReader>::get_optional_string_any(self, names)
1085    }
1086
1087    /// Gets a string from any key, or `default` when all keys are missing or
1088    /// empty.
1089    ///
1090    /// # Parameters
1091    ///
1092    /// * `names` - Candidate keys checked in priority order.
1093    /// * `default` - Fallback used only when all keys are missing or empty.
1094    ///
1095    /// # Returns
1096    ///
1097    /// Returns the string value or default value. Substitution errors are
1098    /// returned instead of being hidden by the default.
1099    pub fn get_string_any_or(&self, names: impl ConfigNames, default: &str) -> ConfigResult<String> {
1100        <Self as ConfigReader>::get_string_any_or(self, names, default)
1101    }
1102
1103    /// Gets a string with substitution, or `default` if the key is absent or
1104    /// empty.
1105    ///
1106    /// # Parameters
1107    ///
1108    /// * `name` - Configuration item name
1109    /// * `default` - Default value
1110    ///
1111    /// # Returns
1112    ///
1113    /// Returns the string value or default value. Substitution errors are
1114    /// returned instead of being hidden by the default.
1115    ///
1116    pub fn get_string_or(&self, name: impl ConfigName, default: &str) -> ConfigResult<String> {
1117        <Self as ConfigReader>::get_string_or(self, name, default)
1118    }
1119
1120    /// Gets a list of string configuration values (with variable substitution)
1121    ///
1122    /// If variable substitution is enabled, runs it on each list element
1123    /// (same `${var_name}` rules as [`Self::get_string`]).
1124    ///
1125    /// # Parameters
1126    ///
1127    /// * `name` - Configuration item name
1128    ///
1129    /// # Returns
1130    ///
1131    /// Returns a list of strings on success, or an error on failure
1132    ///
1133    /// # Examples
1134    ///
1135    /// ```rust
1136    /// use qubit_config::Config;
1137    ///
1138    /// let mut config = Config::new();
1139    /// config.set("base_path", "/opt/app").unwrap();
1140    /// config.set("paths", vec!["${base_path}/bin", "${base_path}/lib"]).unwrap();
1141    ///
1142    /// let paths = config.get_string_list("paths").unwrap();
1143    /// assert_eq!(paths, vec!["/opt/app/bin", "/opt/app/lib"]);
1144    /// ```
1145    pub fn get_string_list(&self, name: impl ConfigName) -> ConfigResult<Vec<String>> {
1146        <Self as ConfigReader>::get_string_list(self, name)
1147    }
1148
1149    /// Gets a list of string configuration values or returns a default value
1150    /// (with variable substitution)
1151    ///
1152    /// # Parameters
1153    ///
1154    /// * `name` - Configuration item name
1155    /// * `default` - Default value (can be array slice or vec)
1156    ///
1157    /// # Returns
1158    ///
1159    /// Returns the list of strings or default value. Substitution and parsing
1160    /// errors are returned instead of being hidden by the default.
1161    ///
1162    /// # Examples
1163    ///
1164    /// ```rust
1165    /// use qubit_config::Config;
1166    ///
1167    /// let config = Config::new();
1168    ///
1169    /// // Using array slice
1170    /// let paths = config.get_string_list_or("paths", &["/default/path"]).unwrap();
1171    /// assert_eq!(paths, vec!["/default/path"]);
1172    ///
1173    /// // Using vec
1174    /// let paths = config.get_string_list_or("paths", &vec!["path1", "path2"]).unwrap();
1175    /// assert_eq!(paths, vec!["path1", "path2"]);
1176    /// ```
1177    pub fn get_string_list_or(&self, name: impl ConfigName, default: &[&str]) -> ConfigResult<Vec<String>> {
1178        <Self as ConfigReader>::get_string_list_or(self, name, default)
1179    }
1180
1181    // ========================================================================
1182    // Configuration Source Integration
1183    // ========================================================================
1184
1185    /// Creates a new configuration by loading a [`ConfigSource`].
1186    ///
1187    /// The returned configuration starts empty and is populated by the given
1188    /// source. This is a convenience constructor for callers that do not need
1189    /// to customize the target [`Config`] before loading.
1190    ///
1191    /// # Parameters
1192    ///
1193    /// * `source` - The configuration source to load from.
1194    ///
1195    /// # Returns
1196    ///
1197    /// A populated configuration.
1198    ///
1199    /// # Errors
1200    ///
1201    /// Returns any [`ConfigError`] produced by the source while loading or by
1202    /// the underlying config mutation methods.
1203    #[inline]
1204    pub fn from_source(source: &dyn ConfigSource) -> ConfigResult<Self> {
1205        let mut config = Self::new();
1206        source.load(&mut config)?;
1207        Ok(config)
1208    }
1209
1210    /// Creates a configuration from all current process environment variables.
1211    ///
1212    /// Environment variable names are loaded as-is. Use
1213    /// [`Self::from_env_prefix`] when the application uses a dedicated prefix
1214    /// and wants normalized dot-separated keys.
1215    ///
1216    /// # Returns
1217    ///
1218    /// A configuration populated from the process environment.
1219    ///
1220    /// # Errors
1221    ///
1222    /// Returns [`ConfigError`] if a matching environment key or value is not
1223    /// valid Unicode, or if setting a loaded property fails.
1224    #[inline]
1225    pub fn from_env() -> ConfigResult<Self> {
1226        let source = EnvConfigSource::new();
1227        Self::from_source(&source)
1228    }
1229
1230    /// Creates a configuration from environment variables with a prefix.
1231    ///
1232    /// Only variables starting with `prefix` are loaded. The prefix is stripped,
1233    /// the remaining key is lowercased, and underscores are converted to dots.
1234    ///
1235    /// # Parameters
1236    ///
1237    /// * `prefix` - Prefix used to select environment variables.
1238    ///
1239    /// # Returns
1240    ///
1241    /// A configuration populated from matching environment variables.
1242    ///
1243    /// # Errors
1244    ///
1245    /// Returns [`ConfigError`] if a matching environment key or value is not
1246    /// valid Unicode, or if setting a loaded property fails.
1247    #[inline]
1248    pub fn from_env_prefix(prefix: &str) -> ConfigResult<Self> {
1249        let source = EnvConfigSource::with_prefix(prefix);
1250        Self::from_source(&source)
1251    }
1252
1253    /// Creates a configuration from environment variables with explicit key
1254    /// transformation options.
1255    ///
1256    /// # Parameters
1257    ///
1258    /// * `prefix` - Prefix used to select environment variables.
1259    /// * `strip_prefix` - Whether to strip the prefix from loaded keys.
1260    /// * `convert_underscores` - Whether to convert underscores to dots.
1261    /// * `lowercase_keys` - Whether to lowercase loaded keys.
1262    ///
1263    /// # Returns
1264    ///
1265    /// A configuration populated from matching environment variables.
1266    ///
1267    /// # Errors
1268    ///
1269    /// Returns [`ConfigError`] if a matching environment key or value is not
1270    /// valid Unicode, or if setting a loaded property fails.
1271    #[inline]
1272    pub fn from_env_options(
1273        prefix: &str,
1274        strip_prefix: bool,
1275        convert_underscores: bool,
1276        lowercase_keys: bool,
1277    ) -> ConfigResult<Self> {
1278        let source = EnvConfigSource::with_options(prefix, strip_prefix, convert_underscores, lowercase_keys);
1279        Self::from_source(&source)
1280    }
1281
1282    /// Creates a configuration from a TOML file.
1283    ///
1284    /// # Parameters
1285    ///
1286    /// * `path` - Path to the TOML file.
1287    ///
1288    /// # Returns
1289    ///
1290    /// A configuration populated from the TOML file.
1291    ///
1292    /// # Errors
1293    ///
1294    /// Returns [`ConfigError::IoError`] if the file cannot be read,
1295    /// [`ConfigError::ParseError`] if the TOML cannot be parsed, or another
1296    /// [`ConfigError`] if setting a loaded property fails.
1297    #[cfg(feature = "source-toml")]
1298    #[inline]
1299    pub fn from_toml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1300        let source = TomlConfigSource::from_file(path);
1301        Self::from_source(&source)
1302    }
1303
1304    /// Creates a configuration from a YAML file.
1305    ///
1306    /// # Parameters
1307    ///
1308    /// * `path` - Path to the YAML file.
1309    ///
1310    /// # Returns
1311    ///
1312    /// A configuration populated from the YAML file.
1313    ///
1314    /// # Errors
1315    ///
1316    /// Returns [`ConfigError::IoError`] if the file cannot be read,
1317    /// [`ConfigError::ParseError`] if the YAML cannot be parsed, or another
1318    /// [`ConfigError`] if setting a loaded property fails.
1319    #[cfg(feature = "source-yaml")]
1320    #[inline]
1321    pub fn from_yaml_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1322        let source = YamlConfigSource::from_file(path);
1323        Self::from_source(&source)
1324    }
1325
1326    /// Creates a configuration from a Java `.properties` file.
1327    ///
1328    /// # Parameters
1329    ///
1330    /// * `path` - Path to the `.properties` file.
1331    ///
1332    /// # Returns
1333    ///
1334    /// A configuration populated from the `.properties` file.
1335    ///
1336    /// # Errors
1337    ///
1338    /// Returns [`ConfigError::IoError`] if the file cannot be read, or another
1339    /// [`ConfigError`] if setting a loaded property fails.
1340    #[inline]
1341    pub fn from_properties_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1342        let source = PropertiesConfigSource::from_file(path);
1343        Self::from_source(&source)
1344    }
1345
1346    /// Creates a configuration from a `.env` file.
1347    ///
1348    /// # Parameters
1349    ///
1350    /// * `path` - Path to the `.env` file.
1351    ///
1352    /// # Returns
1353    ///
1354    /// A configuration populated from the `.env` file.
1355    ///
1356    /// # Errors
1357    ///
1358    /// Returns [`ConfigError::IoError`] if the file cannot be read,
1359    /// [`ConfigError::ParseError`] if dotenv parsing fails, or another
1360    /// [`ConfigError`] if setting a loaded property fails.
1361    #[cfg(feature = "source-env-file")]
1362    #[inline]
1363    pub fn from_env_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
1364        let source = EnvFileConfigSource::from_file(path);
1365        Self::from_source(&source)
1366    }
1367
1368    /// Merges configuration from a `ConfigSource`
1369    ///
1370    /// Loads all key-value pairs from the given source and merges them into
1371    /// this configuration. Existing non-final properties are overwritten;
1372    /// final properties are preserved and cause an error if the source tries
1373    /// to overwrite them.
1374    ///
1375    /// # Parameters
1376    ///
1377    /// * `source` - The configuration source to load from
1378    ///
1379    /// # Returns
1380    ///
1381    /// Returns `Ok(())` on success, or a `ConfigError` on failure
1382    ///
1383    /// # Examples
1384    ///
1385    /// ```rust
1386    /// # #[cfg(feature = "source-toml")]
1387    /// # {
1388    /// use qubit_config::Config;
1389    /// use qubit_config::source::{
1390    ///     CompositeConfigSource, ConfigSource,
1391    ///     EnvConfigSource, TomlConfigSource,
1392    /// };
1393    ///
1394    /// let mut composite = CompositeConfigSource::new();
1395    /// let path = std::env::temp_dir().join(format!(
1396    ///     "qubit-config-doc-{}.toml",
1397    ///     std::process::id()
1398    /// ));
1399    /// std::fs::write(&path, "app.name = \"demo\"").unwrap();
1400    /// composite.add(TomlConfigSource::from_file(&path));
1401    /// composite.add(EnvConfigSource::with_prefix("APP_"));
1402    ///
1403    /// let mut config = Config::new();
1404    /// config.merge_from_source(&composite).unwrap();
1405    /// std::fs::remove_file(&path).unwrap();
1406    /// # }
1407    /// ```
1408    #[inline]
1409    pub fn merge_from_source(&mut self, source: &dyn ConfigSource) -> ConfigResult<()> {
1410        let mut staged = self.clone();
1411        source.load_into(&mut staged)?;
1412        *self = staged;
1413        Ok(())
1414    }
1415
1416    // ========================================================================
1417    // Prefix Traversal and Sub-tree Extraction (v0.4.0)
1418    // ========================================================================
1419
1420    /// Iterates over all configuration entries as `(key, &Property)` pairs.
1421    ///
1422    /// # Returns
1423    ///
1424    /// An iterator yielding `(&str, &Property)` tuples.
1425    ///
1426    /// # Examples
1427    ///
1428    /// ```rust
1429    /// use qubit_config::Config;
1430    ///
1431    /// let mut config = Config::new();
1432    /// config.set("host", "localhost").unwrap();
1433    /// config.set("port", 8080).unwrap();
1434    ///
1435    /// for (key, prop) in config.iter() {
1436    ///     println!("{} = {:?}", key, prop);
1437    /// }
1438    /// ```
1439    #[inline]
1440    pub fn iter(&self) -> impl Iterator<Item = (&str, &Property)> {
1441        self.properties.iter().map(|(k, v)| (k.as_str(), v))
1442    }
1443
1444    /// Iterates over all configuration entries whose key starts with `prefix`.
1445    ///
1446    /// # Parameters
1447    ///
1448    /// * `prefix` - The key prefix to filter by (e.g., `"http."`)
1449    ///
1450    /// # Returns
1451    ///
1452    /// An iterator of `(&str, &Property)` whose keys start with `prefix`.
1453    ///
1454    /// # Examples
1455    ///
1456    /// ```rust
1457    /// use qubit_config::Config;
1458    ///
1459    /// let mut config = Config::new();
1460    /// config.set("http.host", "localhost").unwrap();
1461    /// config.set("http.port", 8080).unwrap();
1462    /// config.set("db.host", "dbhost").unwrap();
1463    ///
1464    /// let http_entries: Vec<_> = config.iter_prefix("http.").collect();
1465    /// assert_eq!(http_entries.len(), 2);
1466    /// ```
1467    #[inline]
1468    pub fn iter_prefix<'a>(&'a self, prefix: &'a str) -> impl Iterator<Item = (&'a str, &'a Property)> {
1469        self.properties
1470            .iter()
1471            .filter(move |(k, _)| k.starts_with(prefix))
1472            .map(|(k, v)| (k.as_str(), v))
1473    }
1474
1475    /// Returns `true` if any configuration key starts with `prefix`.
1476    ///
1477    /// # Parameters
1478    ///
1479    /// * `prefix` - The key prefix to check
1480    ///
1481    /// # Returns
1482    ///
1483    /// `true` if at least one key starts with `prefix`, `false` otherwise.
1484    ///
1485    /// # Examples
1486    ///
1487    /// ```rust
1488    /// use qubit_config::Config;
1489    ///
1490    /// let mut config = Config::new();
1491    /// config.set("http.host", "localhost").unwrap();
1492    ///
1493    /// assert!(config.contains_prefix("http."));
1494    /// assert!(!config.contains_prefix("db."));
1495    /// ```
1496    #[inline]
1497    pub fn contains_prefix(&self, prefix: &str) -> bool {
1498        self.properties.keys().any(|k| k.starts_with(prefix))
1499    }
1500
1501    /// Extracts a sub-configuration for child keys below `prefix`.
1502    ///
1503    /// An exact key equal to `prefix` is treated as a value, not as part of the
1504    /// extracted subtree. For example, `subconfig("http", true)` includes
1505    /// `http.host` as `host`, but does not include an exact `http` property.
1506    ///
1507    /// # Parameters
1508    ///
1509    /// * `prefix` - The key prefix to extract (e.g., `"http"`)
1510    /// * `strip_prefix` - When `true`, removes `prefix` and the following dot
1511    ///   from keys in the result; when `false`, keys are copied unchanged.
1512    ///
1513    /// # Returns
1514    ///
1515    /// A new `Config` containing only child entries below `prefix`.
1516    ///
1517    /// # Examples
1518    ///
1519    /// ```rust
1520    /// use qubit_config::Config;
1521    ///
1522    /// let mut config = Config::new();
1523    /// config.set("http.host", "localhost").unwrap();
1524    /// config.set("http.port", 8080).unwrap();
1525    /// config.set("db.host", "dbhost").unwrap();
1526    ///
1527    /// let http_config = config.subconfig("http", true).unwrap();
1528    /// assert!(http_config.contains("host"));
1529    /// assert!(http_config.contains("port"));
1530    /// assert!(!http_config.contains("db.host"));
1531    /// ```
1532    pub fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
1533        let mut sub = Config::new();
1534        sub.description = self.description.clone();
1535        sub.enable_variable_substitution = self.enable_variable_substitution;
1536        sub.max_substitution_depth = self.max_substitution_depth;
1537        sub.read_options = self.read_options.clone();
1538
1539        // Empty prefix means "all keys"
1540        if prefix.is_empty() {
1541            for (k, v) in &self.properties {
1542                sub.properties.insert(k.clone(), v.clone());
1543            }
1544            return Ok(sub);
1545        }
1546
1547        let full_prefix = format!("{prefix}.");
1548
1549        for (k, v) in &self.properties {
1550            if k.starts_with(&full_prefix) {
1551                let new_key = if strip_prefix {
1552                    k[full_prefix.len()..].to_string()
1553                } else {
1554                    k.clone()
1555                };
1556                sub.properties.insert(new_key, v.clone());
1557            }
1558        }
1559
1560        Ok(sub)
1561    }
1562
1563    // ========================================================================
1564    // Optional and Null Semantics (v0.4.0)
1565    // ========================================================================
1566
1567    /// Returns `true` if the property exists but has no value (empty / null).
1568    ///
1569    /// This distinguishes between:
1570    /// - Key does not exist → `contains()` returns `false`
1571    /// - Key exists but is empty/null → `is_null()` returns `true`
1572    ///
1573    /// # Parameters
1574    ///
1575    /// * `name` - Configuration item name
1576    ///
1577    /// # Returns
1578    ///
1579    /// `true` if the property exists and has no values (is empty).
1580    ///
1581    /// # Examples
1582    ///
1583    /// ```rust
1584    /// use qubit_config::Config;
1585    /// use qubit_datatype::DataType;
1586    ///
1587    /// let mut config = Config::new();
1588    /// config.set_null("nullable", DataType::String).unwrap();
1589    ///
1590    /// assert!(config.is_null("nullable"));
1591    /// assert!(!config.is_null("missing"));
1592    /// ```
1593    pub fn is_null(&self, name: impl ConfigName) -> bool {
1594        name.with_config_name(|name| self.properties.get(name).map(|p| p.is_empty()).unwrap_or(false))
1595    }
1596
1597    /// Gets an optional configuration value.
1598    ///
1599    /// Distinguishes between three states:
1600    /// - `Ok(Some(value))` – key exists and has a value
1601    /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1602    /// - `Err(e)` – key exists and has a value, but conversion failed
1603    ///
1604    /// # Type Parameters
1605    ///
1606    /// * `T` - Target type
1607    ///
1608    /// # Parameters
1609    ///
1610    /// * `name` - Configuration item name
1611    ///
1612    /// # Returns
1613    ///
1614    /// `Ok(Some(value))`, `Ok(None)`, or `Err` as described above.
1615    ///
1616    /// # Examples
1617    ///
1618    /// ```rust
1619    /// use qubit_config::Config;
1620    ///
1621    /// let mut config = Config::new();
1622    /// config.set("port", 8080).unwrap();
1623    ///
1624    /// let port: Option<i32> = config.get_optional("port").unwrap();
1625    /// assert_eq!(port, Some(8080));
1626    ///
1627    /// let missing: Option<i32> = config.get_optional("missing").unwrap();
1628    /// assert_eq!(missing, None);
1629    /// ```
1630    pub fn get_optional<T>(&self, name: impl ConfigName) -> ConfigResult<Option<T>>
1631    where
1632        T: FromConfig,
1633    {
1634        <Self as ConfigReader>::get_optional(self, name)
1635    }
1636
1637    /// Gets an optional list of configuration values.
1638    ///
1639    /// See also [`Self::get_optional_string_list`] for optional string lists
1640    /// with variable substitution.
1641    ///
1642    /// Distinguishes between three states:
1643    /// - `Ok(Some(vec))` – key exists and has values
1644    /// - `Ok(None)` – key does not exist, **or** exists but is null/empty
1645    /// - `Err(e)` – key exists and has values, but conversion failed
1646    ///
1647    /// # Type Parameters
1648    ///
1649    /// * `T` - Target element type supported by [`FromConfig`]
1650    ///
1651    /// # Parameters
1652    ///
1653    /// * `name` - Configuration item name
1654    ///
1655    /// # Returns
1656    ///
1657    /// `Ok(Some(vec))`, `Ok(None)`, or `Err` as described above.
1658    ///
1659    /// # Examples
1660    ///
1661    /// ```rust
1662    /// use qubit_config::Config;
1663    ///
1664    /// let mut config = Config::new();
1665    /// config.set("ports", vec![8080, 8081]).unwrap();
1666    ///
1667    /// let ports: Option<Vec<i32>> = config.get_optional_list("ports").unwrap();
1668    /// assert_eq!(ports, Some(vec![8080, 8081]));
1669    ///
1670    /// let missing: Option<Vec<i32>> = config.get_optional_list("missing").unwrap();
1671    /// assert_eq!(missing, None);
1672    /// ```
1673    pub fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
1674    where
1675        T: FromConfig,
1676    {
1677        <Self as ConfigReader>::get_optional(self, name)
1678    }
1679
1680    /// Gets an optional string (with variable substitution when enabled).
1681    ///
1682    /// Same semantics as [`Self::get_optional`], but values are read via
1683    /// [`Self::get_string`], so `${...}` substitution applies when enabled.
1684    ///
1685    /// # Parameters
1686    ///
1687    /// * `name` - Configuration item name
1688    ///
1689    /// # Returns
1690    ///
1691    /// `Ok(Some(s))`, `Ok(None)`, or `Err` as for [`Self::get_optional`].
1692    ///
1693    /// # Examples
1694    ///
1695    /// ```rust
1696    /// use qubit_config::Config;
1697    ///
1698    /// let mut config = Config::new();
1699    /// config.set("base", "http://localhost").unwrap();
1700    /// config.set("api", "${base}/api").unwrap();
1701    ///
1702    /// let api = config.get_optional_string("api").unwrap();
1703    /// assert_eq!(api.as_deref(), Some("http://localhost/api"));
1704    ///
1705    /// let missing = config.get_optional_string("missing").unwrap();
1706    /// assert_eq!(missing, None);
1707    /// ```
1708    pub fn get_optional_string(&self, name: impl ConfigName) -> ConfigResult<Option<String>> {
1709        <Self as ConfigReader>::get_optional_string(self, name)
1710    }
1711
1712    /// Gets an optional string list (substitution per element when enabled).
1713    ///
1714    /// Same semantics as [`Self::get_optional_list`], but elements use
1715    /// [`Self::get_string_list`] (same `${...}` rules as [`Self::get_string`]).
1716    ///
1717    /// # Parameters
1718    ///
1719    /// * `name` - Configuration item name
1720    ///
1721    /// # Returns
1722    ///
1723    /// `Ok(Some(vec))`, `Ok(None)`, or `Err` like [`Self::get_optional_list`].
1724    ///
1725    /// # Examples
1726    ///
1727    /// ```rust
1728    /// use qubit_config::Config;
1729    ///
1730    /// let mut config = Config::new();
1731    /// config.set("root", "/opt/app").unwrap();
1732    /// config.set("paths", vec!["${root}/bin", "${root}/lib"]).unwrap();
1733    ///
1734    /// let paths = config.get_optional_string_list("paths").unwrap();
1735    /// assert_eq!(
1736    ///     paths,
1737    ///     Some(vec![
1738    ///         "/opt/app/bin".to_string(),
1739    ///         "/opt/app/lib".to_string(),
1740    ///     ]),
1741    /// );
1742    /// ```
1743    pub fn get_optional_string_list(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<String>>> {
1744        <Self as ConfigReader>::get_optional_string_list(self, name)
1745    }
1746
1747    // ========================================================================
1748    // Structured Config Deserialization (v0.4.0)
1749    // ========================================================================
1750
1751    /// Deserializes a config value or subtree at `prefix` into `T` using `serde`.
1752    /// String values inside the generated serde value apply the same
1753    /// `${...}` substitution rules as [`Self::get_string`] and
1754    /// [`Self::get_string_list`] when substitution is enabled. Scalar strings
1755    /// are then parsed with this config's [`ConfigReadOptions`], so
1756    /// environment-style booleans, numeric strings, and scalar string lists
1757    /// behave consistently with typed `get` reads.
1758    ///
1759    /// When `prefix` is non-empty, an exact property named `prefix` is
1760    /// deserialized as the root value. If no exact property exists, child keys
1761    /// under `prefix` (prefix and trailing dot removed) form an object for
1762    /// `serde`, for example:
1763    ///
1764    /// ```rust
1765    /// #[derive(serde::Deserialize)]
1766    /// struct HttpOptions {
1767    ///     host: String,
1768    ///     port: u16,
1769    /// }
1770    /// ```
1771    ///
1772    /// can be populated from config keys `http.host` and `http.port` by calling
1773    /// `config.deserialize::<HttpOptions>("http")`. Defining both `http` and
1774    /// `http.*` is a [`ConfigError::KeyConflict`], as are ambiguous dotted paths
1775    /// such as `a` and `a.b` inside the same deserialized object.
1776    ///
1777    /// # Type Parameters
1778    ///
1779    /// * `T` - Target type, must implement `serde::de::DeserializeOwned`
1780    ///
1781    /// # Parameters
1782    ///
1783    /// * `prefix` - Key prefix for the struct fields (`""` means the root map)
1784    ///
1785    /// # Returns
1786    ///
1787    /// The deserialized `T`.
1788    ///
1789    /// # Errors
1790    ///
1791    /// Returns [`ConfigError::KeyConflict`] for ambiguous key shapes,
1792    /// substitution/conversion errors while preparing string values, or
1793    /// [`ConfigError::DeserializeError`] when serde cannot deserialize the
1794    /// prepared value into `T`.
1795    ///
1796    /// # Examples
1797    ///
1798    /// ```rust
1799    /// use qubit_config::Config;
1800    /// use serde::Deserialize;
1801    ///
1802    /// #[derive(Deserialize, Debug, PartialEq)]
1803    /// struct Server {
1804    ///     host: String,
1805    ///     port: i32,
1806    /// }
1807    ///
1808    /// let mut config = Config::new();
1809    /// config.set("server.host", "localhost").unwrap();
1810    /// config.set("server.port", 8080).unwrap();
1811    ///
1812    /// let server: Server = config.deserialize("server").unwrap();
1813    /// assert_eq!(server.host, "localhost");
1814    /// assert_eq!(server.port, 8080);
1815    /// ```
1816    pub fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
1817    where
1818        T: DeserializeOwned,
1819    {
1820        let value = self.deserialize_root_value(prefix)?;
1821
1822        match T::deserialize(ConfigValueDeserializer::new(
1823            value,
1824            prefix.to_string(),
1825            self.read_options(),
1826        )) {
1827            Ok(value) => Ok(value),
1828            Err(error) => Err(error.into_config_error(prefix)),
1829        }
1830    }
1831
1832    /// Builds the JSON root consumed by structured serde deserialization.
1833    ///
1834    /// # Errors
1835    ///
1836    /// Returns [`ConfigError::KeyConflict`] when `prefix` has both an exact value
1837    /// and child keys, or when dotted child keys cannot form an unambiguous object
1838    /// tree. Returns substitution/conversion errors if configured string handling
1839    /// fails before deserialization starts.
1840    fn deserialize_root_value(&self, prefix: &str) -> ConfigResult<JsonValue> {
1841        if prefix.is_empty() {
1842            return self.deserialize_subtree_value(prefix);
1843        }
1844
1845        let exact = self.properties.get(prefix);
1846        let has_children = self.properties.keys().any(|key| is_child_key(key, prefix));
1847        match (exact, has_children) {
1848            (Some(_), true) => Err(ConfigError::KeyConflict {
1849                path: prefix.to_string(),
1850                existing: "exact value".to_string(),
1851                incoming: "nested child keys".to_string(),
1852            }),
1853            (Some(property), false) => self.deserialize_exact_value(prefix, property),
1854            (None, _) => self.deserialize_subtree_value(prefix),
1855        }
1856    }
1857
1858    /// Builds a JSON value from a single exact property for deserialization.
1859    ///
1860    /// # Errors
1861    ///
1862    /// Returns substitution errors when string leaves contain unresolved
1863    /// placeholders, or `JsonValue::Null` when the exact property is effectively
1864    /// missing under the active read options.
1865    fn deserialize_exact_value(&self, key: &str, property: &Property) -> ConfigResult<JsonValue> {
1866        if scalar_string_is_missing_for_deserialize(self, self, key, property, self.read_options())? {
1867            return Ok(JsonValue::Null);
1868        }
1869
1870        let mut value = utils::property_to_json_value(property);
1871        utils::substitute_json_strings_with_fallback(&mut value, self, self)?;
1872        Ok(value)
1873    }
1874
1875    /// Builds a JSON object from keys under `prefix` for deserialization.
1876    ///
1877    /// # Errors
1878    ///
1879    /// Returns key-conflict errors for ambiguous dotted paths, and propagates
1880    /// substitution/conversion errors from active read options.
1881    fn deserialize_subtree_value(&self, prefix: &str) -> ConfigResult<JsonValue> {
1882        let sub = self.subconfig(prefix, true)?;
1883
1884        let mut properties = sub.properties.iter().collect::<Vec<_>>();
1885        properties.sort_by_key(|(left_key, _)| *left_key);
1886
1887        let mut map = Map::new();
1888        for (key, prop) in properties {
1889            if scalar_string_is_missing_for_deserialize(&sub, self, key, prop, self.read_options())? {
1890                continue;
1891            }
1892
1893            let mut json_val = utils::property_to_json_value(prop);
1894            utils::substitute_json_strings_with_fallback(&mut json_val, &sub, self)?;
1895            utils::insert_deserialize_value(&mut map, key, json_val)?;
1896        }
1897        Ok(JsonValue::Object(map))
1898    }
1899
1900    /// Inserts or replaces a property using an explicit [`Property`] object.
1901    ///
1902    /// This method enforces two invariants:
1903    ///
1904    /// - `name` must exactly match `property.name()`
1905    /// - existing final properties cannot be overridden
1906    ///
1907    /// # Parameters
1908    ///
1909    /// * `name` - Target key in this config.
1910    /// * `property` - Property to store under `name`.
1911    ///
1912    /// # Returns
1913    ///
1914    /// `Ok(())` on success.
1915    ///
1916    /// # Errors
1917    ///
1918    /// - [`ConfigError::MergeError`] when `name` and `property.name()` differ.
1919    /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1920    ///   property.
1921    pub fn insert_property(&mut self, name: impl ConfigName, property: Property) -> ConfigResult<()> {
1922        name.with_config_name(|name| {
1923            if property.name() != name {
1924                return Err(ConfigError::MergeError(format!(
1925                    "Property name mismatch: key '{name}' != property '{}'",
1926                    property.name()
1927                )));
1928            }
1929            self.ensure_property_not_final(name)?;
1930            self.properties.insert(name.to_string(), property);
1931            Ok(())
1932        })
1933    }
1934
1935    /// Sets a key to a typed null/empty value.
1936    ///
1937    /// This is the preferred public API for representing null/empty values
1938    /// without exposing raw mutable access to the internal map.
1939    ///
1940    /// # Parameters
1941    ///
1942    /// * `name` - Configuration item name.
1943    /// * `data_type` - Data type metadata for the empty value.
1944    ///
1945    /// # Returns
1946    ///
1947    /// `Ok(())` on success.
1948    ///
1949    /// # Errors
1950    ///
1951    /// - [`ConfigError::PropertyIsFinal`] when trying to override a final
1952    ///   property.
1953    #[inline]
1954    pub fn set_null(&mut self, name: impl ConfigName, data_type: DataType) -> ConfigResult<()> {
1955        name.with_config_name(|name| {
1956            self.insert_property(name, Property::with_value(name, MultiValues::Empty(data_type)))
1957        })
1958    }
1959}
1960
1961impl ConfigReader for Config {
1962    #[inline]
1963    fn is_enable_variable_substitution(&self) -> bool {
1964        Config::is_enable_variable_substitution(self)
1965    }
1966
1967    #[inline]
1968    fn max_substitution_depth(&self) -> usize {
1969        Config::max_substitution_depth(self)
1970    }
1971
1972    #[inline]
1973    fn read_options(&self) -> &ConfigReadOptions {
1974        Config::read_options(self)
1975    }
1976
1977    #[inline]
1978    fn description(&self) -> Option<&str> {
1979        Config::description(self)
1980    }
1981
1982    #[inline]
1983    fn get_property(&self, name: impl ConfigName) -> Option<&Property> {
1984        Config::get_property(self, name)
1985    }
1986
1987    #[inline]
1988    fn len(&self) -> usize {
1989        Config::len(self)
1990    }
1991
1992    #[inline]
1993    fn is_empty(&self) -> bool {
1994        Config::is_empty(self)
1995    }
1996
1997    #[inline]
1998    fn keys(&self) -> Vec<String> {
1999        Config::keys(self)
2000    }
2001
2002    #[inline]
2003    fn contains(&self, name: impl ConfigName) -> bool {
2004        Config::contains(self, name)
2005    }
2006
2007    #[inline]
2008    fn get_strict<T>(&self, name: impl ConfigName) -> ConfigResult<T>
2009    where
2010        for<'a> T: TryFrom<&'a MultiValues, Error = ValueError>,
2011    {
2012        Config::get_strict(self, name)
2013    }
2014
2015    #[inline]
2016    fn get_list<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
2017    where
2018        T: FromConfig,
2019    {
2020        Config::get_list(self, name)
2021    }
2022
2023    #[inline]
2024    fn get_list_strict<T>(&self, name: impl ConfigName) -> ConfigResult<Vec<T>>
2025    where
2026        for<'a> Vec<T>: TryFrom<&'a MultiValues, Error = ValueError>,
2027    {
2028        Config::get_list_strict(self, name)
2029    }
2030
2031    #[inline]
2032    fn get_optional_list<T>(&self, name: impl ConfigName) -> ConfigResult<Option<Vec<T>>>
2033    where
2034        T: FromConfig,
2035    {
2036        Config::get_optional_list(self, name)
2037    }
2038
2039    #[inline]
2040    fn contains_prefix(&self, prefix: &str) -> bool {
2041        Config::contains_prefix(self, prefix)
2042    }
2043
2044    #[inline]
2045    fn iter_prefix<'a>(&'a self, prefix: &'a str) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
2046        Box::new(Config::iter_prefix(self, prefix))
2047    }
2048
2049    #[inline]
2050    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'a str, &'a Property)> + 'a> {
2051        Box::new(Config::iter(self))
2052    }
2053
2054    #[inline]
2055    fn is_null(&self, name: impl ConfigName) -> bool {
2056        Config::is_null(self, name)
2057    }
2058
2059    #[inline]
2060    fn subconfig(&self, prefix: &str, strip_prefix: bool) -> ConfigResult<Config> {
2061        Config::subconfig(self, prefix, strip_prefix)
2062    }
2063
2064    #[inline]
2065    fn deserialize<T>(&self, prefix: &str) -> ConfigResult<T>
2066    where
2067        T: DeserializeOwned,
2068    {
2069        Config::deserialize(self, prefix)
2070    }
2071
2072    #[inline]
2073    fn prefix_view(&self, prefix: &str) -> ConfigPrefixView<'_> {
2074        Config::prefix_view(self, prefix)
2075    }
2076}
2077
2078impl Default for Config {
2079    /// Creates a new default configuration
2080    ///
2081    /// # Returns
2082    ///
2083    /// Returns a new configuration instance
2084    ///
2085    /// # Examples
2086    ///
2087    /// ```rust
2088    /// use qubit_config::Config;
2089    ///
2090    /// let config = Config::default();
2091    /// assert!(config.is_empty());
2092    /// ```
2093    #[inline]
2094    fn default() -> Self {
2095        Self::new()
2096    }
2097}