Skip to main content

qubit_config/source/
composite_config_source.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//! # Composite Configuration Source
11//!
12//! Merges configuration from multiple sources in order.
13//!
14//! Sources are applied in the order they are added. Later sources override
15//! earlier sources for the same key (unless the property is marked as final).
16//!
17//! # Examples
18//!
19//! ```rust
20//! # #[cfg(feature = "source-toml")]
21//! # {
22//! use qubit_config::source::{
23//!     CompositeConfigSource, ConfigSource, TomlConfigSource,
24//! };
25//! use qubit_config::Config;
26//!
27//! let mut composite = CompositeConfigSource::new();
28//! let temp_dir = tempfile::tempdir().unwrap();
29//! let defaults = temp_dir.path().join("defaults.toml");
30//! let override_file = temp_dir.path().join("config.toml");
31//! std::fs::write(&defaults, "port = 80\n").unwrap();
32//! std::fs::write(&override_file, "port = 8080\n").unwrap();
33//! composite.add(TomlConfigSource::from_file(defaults));
34//! composite.add(TomlConfigSource::from_file(override_file));
35//!
36//! let mut config = Config::new();
37//! composite.load(&mut config).unwrap();
38//! assert_eq!(config.get::<i64>("port").unwrap(), 8080);
39//! # }
40//! ```
41//!
42
43use crate::{
44    Config,
45    ConfigResult,
46};
47
48use super::{
49    ConfigSource,
50    config_source::load_transactionally,
51};
52
53/// Configuration source that merges multiple sources in order
54///
55pub struct CompositeConfigSource {
56    sources: Vec<Box<dyn ConfigSource>>,
57}
58
59impl CompositeConfigSource {
60    /// Creates a new empty `CompositeConfigSource`.
61    ///
62    /// # Returns
63    ///
64    /// An empty composite with no inner sources.
65    #[inline]
66    pub fn new() -> Self {
67        Self { sources: Vec::new() }
68    }
69
70    /// Adds a configuration source
71    ///
72    /// Sources are applied in the order they are added. Later sources override
73    /// earlier sources for the same key.
74    ///
75    /// # Parameters
76    ///
77    /// * `source` - The configuration source to add
78    ///
79    /// # Returns
80    ///
81    /// `self` for method chaining.
82    #[inline]
83    pub fn add<S: ConfigSource + 'static>(&mut self, source: S) -> &mut Self {
84        self.sources.push(Box::new(source));
85        self
86    }
87
88    /// Returns the number of sources in this composite.
89    ///
90    /// # Returns
91    ///
92    /// The length of the internal source list.
93    #[inline]
94    pub fn len(&self) -> usize {
95        self.sources.len()
96    }
97
98    /// Returns `true` if this composite has no sources.
99    ///
100    /// # Returns
101    ///
102    /// `true` when [`Self::len`] is zero.
103    #[inline]
104    pub fn is_empty(&self) -> bool {
105        self.sources.is_empty()
106    }
107}
108
109impl Default for CompositeConfigSource {
110    #[inline]
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl ConfigSource for CompositeConfigSource {
117    fn load(&self, config: &mut Config) -> ConfigResult<()> {
118        load_transactionally(self, config)
119    }
120
121    fn load_into(&self, config: &mut Config) -> ConfigResult<()> {
122        for source in &self.sources {
123            source.load_into(config)?;
124        }
125        Ok(())
126    }
127}