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}