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