Skip to main content

use_config_layer/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::collections::BTreeMap;
5
6use use_config_key::ConfigPath;
7use use_config_source::ConfigSource;
8use use_config_value::ConfigValue;
9
10/// A deterministic map of configuration values paired with source metadata.
11#[derive(Clone, Debug, PartialEq)]
12pub struct ConfigLayer {
13    /// Source metadata for this layer.
14    pub source: ConfigSource,
15    values: BTreeMap<ConfigPath, ConfigValue>,
16}
17
18impl ConfigLayer {
19    /// Creates an empty layer for a source.
20    #[must_use]
21    pub const fn new(source: ConfigSource) -> Self {
22        Self {
23            source,
24            values: BTreeMap::new(),
25        }
26    }
27
28    /// Creates a layer from source metadata and values.
29    #[must_use]
30    pub const fn from_values(
31        source: ConfigSource,
32        values: BTreeMap<ConfigPath, ConfigValue>,
33    ) -> Self {
34        Self { source, values }
35    }
36
37    /// Returns the layer source.
38    #[must_use]
39    pub const fn source(&self) -> &ConfigSource {
40        &self.source
41    }
42
43    /// Returns the deterministic value map.
44    #[must_use]
45    pub const fn values(&self) -> &BTreeMap<ConfigPath, ConfigValue> {
46        &self.values
47    }
48
49    /// Returns the number of values in the layer.
50    #[must_use]
51    pub fn len(&self) -> usize {
52        self.values.len()
53    }
54
55    /// Returns `true` when the layer has no values.
56    #[must_use]
57    pub fn is_empty(&self) -> bool {
58        self.values.is_empty()
59    }
60
61    /// Inserts a path value into the layer.
62    pub fn insert(&mut self, path: ConfigPath, value: ConfigValue) -> Option<ConfigValue> {
63        self.values.insert(path, value)
64    }
65
66    /// Returns a value for a path.
67    #[must_use]
68    pub fn get(&self, path: &ConfigPath) -> Option<&ConfigValue> {
69        self.values.get(path)
70    }
71
72    /// Iterates over values in deterministic path order.
73    pub fn iter(&self) -> impl Iterator<Item = (&ConfigPath, &ConfigValue)> {
74        self.values.iter()
75    }
76}
77
78/// Merges two layers using shallow layer semantics.
79///
80/// Layers are applied in ascending priority order. If priorities match, the
81/// second layer wins for duplicate paths.
82#[must_use]
83pub fn merge_two_layers(
84    first: &ConfigLayer,
85    second: &ConfigLayer,
86) -> BTreeMap<ConfigPath, ConfigValue> {
87    merge_layers([first, second])
88}
89
90/// Merges many layers using shallow layer semantics.
91///
92/// Layers are applied in ascending priority order. Caller order is preserved
93/// for equal priorities, so later equal-priority layers replace earlier values.
94/// Map values are replaced as whole values; nested map merging is intentionally
95/// not part of this first version.
96#[must_use]
97pub fn merge_layers<'a, I>(layers: I) -> BTreeMap<ConfigPath, ConfigValue>
98where
99    I: IntoIterator<Item = &'a ConfigLayer>,
100{
101    let mut ordered_layers: Vec<_> = layers.into_iter().enumerate().collect();
102
103    ordered_layers.sort_by(|(left_index, left), (right_index, right)| {
104        left.source
105            .priority()
106            .cmp(&right.source.priority())
107            .then_with(|| left_index.cmp(right_index))
108    });
109
110    let mut merged = BTreeMap::new();
111
112    for (_, layer) in ordered_layers {
113        for (path, value) in layer.iter() {
114            merged.insert(path.clone(), value.clone());
115        }
116    }
117
118    merged
119}
120
121#[cfg(test)]
122mod tests {
123    use super::{ConfigLayer, merge_layers, merge_two_layers};
124    use use_config_key::ConfigPath;
125    use use_config_source::{ConfigSource, ConfigSourceKind};
126    use use_config_value::ConfigValue;
127
128    #[test]
129    fn lower_priority_default_is_overridden_by_higher_priority_override() {
130        let path = ConfigPath::parse("server.port").expect("path should parse");
131        let mut defaults = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Default, 0));
132        let mut overrides = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Override, 10));
133
134        defaults.insert(path.clone(), ConfigValue::from(8080_i64));
135        overrides.insert(path.clone(), ConfigValue::from(9090_i64));
136
137        let merged = merge_two_layers(&defaults, &overrides);
138
139        assert_eq!(merged.get(&path).and_then(ConfigValue::as_i64), Some(9090));
140    }
141
142    #[test]
143    fn later_layer_wins_when_priority_is_equal() {
144        let path = ConfigPath::parse("mode").expect("path should parse");
145        let source = ConfigSource::unnamed(ConfigSourceKind::Runtime, 5);
146        let mut first = ConfigLayer::new(source.clone());
147        let mut second = ConfigLayer::new(source);
148
149        first.insert(path.clone(), ConfigValue::from("first"));
150        second.insert(path.clone(), ConfigValue::from("second"));
151
152        let merged = merge_layers([&first, &second]);
153
154        assert_eq!(
155            merged.get(&path).and_then(ConfigValue::as_str),
156            Some("second")
157        );
158    }
159
160    #[test]
161    fn unrelated_keys_are_preserved() {
162        let host = ConfigPath::parse("server.host").expect("path should parse");
163        let port = ConfigPath::parse("server.port").expect("path should parse");
164        let mut defaults = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Default, 0));
165        let mut overrides = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Override, 10));
166
167        defaults.insert(host.clone(), ConfigValue::from("localhost"));
168        overrides.insert(port.clone(), ConfigValue::from(9090_i64));
169
170        let merged = merge_layers([&defaults, &overrides]);
171
172        assert_eq!(
173            merged.get(&host).and_then(ConfigValue::as_str),
174            Some("localhost")
175        );
176        assert_eq!(merged.get(&port).and_then(ConfigValue::as_i64), Some(9090));
177    }
178
179    #[test]
180    fn merge_order_is_deterministic() {
181        let path = ConfigPath::parse("answer").expect("path should parse");
182        let mut low = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Default, 0));
183        let mut middle = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Runtime, 5));
184        let mut high = ConfigLayer::new(ConfigSource::unnamed(ConfigSourceKind::Override, 10));
185
186        high.insert(path.clone(), ConfigValue::from(3_i64));
187        low.insert(path.clone(), ConfigValue::from(1_i64));
188        middle.insert(path.clone(), ConfigValue::from(2_i64));
189
190        let merged = merge_layers([&high, &low, &middle]);
191
192        assert_eq!(merged.get(&path).and_then(ConfigValue::as_i64), Some(3));
193    }
194}