Skip to main content

oxihuman_core/
config_layer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Layered configuration: base layer overridden by user layer.
6
7use std::collections::HashMap;
8
9/// A single configuration layer mapping string keys to string values.
10#[allow(dead_code)]
11#[derive(Debug, Clone, Default)]
12pub struct ConfigLayer {
13    entries: HashMap<String, String>,
14    name: String,
15}
16
17#[allow(dead_code)]
18impl ConfigLayer {
19    pub fn new(name: &str) -> Self {
20        Self {
21            entries: HashMap::new(),
22            name: name.to_string(),
23        }
24    }
25
26    pub fn name(&self) -> &str {
27        &self.name
28    }
29
30    pub fn set(&mut self, key: &str, value: &str) {
31        self.entries.insert(key.to_string(), value.to_string());
32    }
33
34    pub fn get(&self, key: &str) -> Option<&str> {
35        self.entries.get(key).map(|v| v.as_str())
36    }
37
38    pub fn remove(&mut self, key: &str) -> bool {
39        self.entries.remove(key).is_some()
40    }
41
42    pub fn contains(&self, key: &str) -> bool {
43        self.entries.contains_key(key)
44    }
45
46    pub fn count(&self) -> usize {
47        self.entries.len()
48    }
49
50    pub fn is_empty(&self) -> bool {
51        self.entries.is_empty()
52    }
53
54    pub fn keys(&self) -> Vec<&str> {
55        self.entries.keys().map(|k| k.as_str()).collect()
56    }
57
58    pub fn clear(&mut self) {
59        self.entries.clear();
60    }
61}
62
63/// Stack of config layers resolved top-down (last layer wins).
64#[allow(dead_code)]
65#[derive(Debug, Clone, Default)]
66pub struct LayeredConfig {
67    layers: Vec<ConfigLayer>,
68}
69
70#[allow(dead_code)]
71impl LayeredConfig {
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    pub fn push_layer(&mut self, layer: ConfigLayer) {
77        self.layers.push(layer);
78    }
79
80    pub fn layer_count(&self) -> usize {
81        self.layers.len()
82    }
83
84    /// Resolve key: last layer that has it wins.
85    pub fn get(&self, key: &str) -> Option<&str> {
86        self.layers.iter().rev().find_map(|l| l.get(key))
87    }
88
89    pub fn get_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
90        self.get(key).unwrap_or(default)
91    }
92
93    pub fn has_key(&self, key: &str) -> bool {
94        self.get(key).is_some()
95    }
96
97    /// All unique keys across all layers.
98    pub fn all_keys(&self) -> Vec<String> {
99        let mut keys: Vec<String> = self
100            .layers
101            .iter()
102            .flat_map(|l| l.keys().into_iter().map(|s| s.to_string()))
103            .collect();
104        keys.sort();
105        keys.dedup();
106        keys
107    }
108
109    pub fn pop_layer(&mut self) -> Option<ConfigLayer> {
110        self.layers.pop()
111    }
112
113    pub fn clear(&mut self) {
114        self.layers.clear();
115    }
116
117    /// Flatten all layers into a single resolved map.
118    pub fn flatten(&self) -> HashMap<String, String> {
119        let mut out = HashMap::new();
120        for layer in &self.layers {
121            for key in layer.keys() {
122                if let Some(v) = layer.get(key) {
123                    out.insert(key.to_string(), v.to_string());
124                }
125            }
126        }
127        out
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn layer_set_get() {
137        let mut l = ConfigLayer::new("base");
138        l.set("color", "red");
139        assert_eq!(l.get("color"), Some("red"));
140    }
141
142    #[test]
143    fn layer_name() {
144        let l = ConfigLayer::new("user");
145        assert_eq!(l.name(), "user");
146    }
147
148    #[test]
149    fn layer_remove() {
150        let mut l = ConfigLayer::new("test");
151        l.set("k", "v");
152        assert!(l.remove("k"));
153        assert!(!l.contains("k"));
154    }
155
156    #[test]
157    fn layered_resolution_last_wins() {
158        let mut base = ConfigLayer::new("base");
159        base.set("size", "10");
160        let mut user = ConfigLayer::new("user");
161        user.set("size", "20");
162
163        let mut lc = LayeredConfig::new();
164        lc.push_layer(base);
165        lc.push_layer(user);
166        assert_eq!(lc.get("size"), Some("20"));
167    }
168
169    #[test]
170    fn layered_fallback_to_base() {
171        let mut base = ConfigLayer::new("base");
172        base.set("mode", "auto");
173        let user = ConfigLayer::new("user");
174
175        let mut lc = LayeredConfig::new();
176        lc.push_layer(base);
177        lc.push_layer(user);
178        assert_eq!(lc.get("mode"), Some("auto"));
179    }
180
181    #[test]
182    fn get_or_default() {
183        let lc = LayeredConfig::new();
184        assert_eq!(lc.get_or("missing", "default"), "default");
185    }
186
187    #[test]
188    fn all_keys_deduped() {
189        let mut base = ConfigLayer::new("base");
190        base.set("a", "1");
191        base.set("b", "2");
192        let mut user = ConfigLayer::new("user");
193        user.set("b", "3");
194        user.set("c", "4");
195
196        let mut lc = LayeredConfig::new();
197        lc.push_layer(base);
198        lc.push_layer(user);
199        let keys = lc.all_keys();
200        assert_eq!(keys, vec!["a", "b", "c"]);
201    }
202
203    #[test]
204    fn flatten_resolves_correctly() {
205        let mut base = ConfigLayer::new("base");
206        base.set("x", "1");
207        let mut user = ConfigLayer::new("user");
208        user.set("x", "2");
209        user.set("y", "3");
210
211        let mut lc = LayeredConfig::new();
212        lc.push_layer(base);
213        lc.push_layer(user);
214        let flat = lc.flatten();
215        assert_eq!(flat.get("x").expect("should succeed"), "2");
216        assert_eq!(flat.get("y").expect("should succeed"), "3");
217    }
218
219    #[test]
220    fn pop_layer() {
221        let mut lc = LayeredConfig::new();
222        lc.push_layer(ConfigLayer::new("a"));
223        lc.push_layer(ConfigLayer::new("b"));
224        let popped = lc.pop_layer().expect("should succeed");
225        assert_eq!(popped.name(), "b");
226        assert_eq!(lc.layer_count(), 1);
227    }
228
229    #[test]
230    fn clear_all_layers() {
231        let mut lc = LayeredConfig::new();
232        lc.push_layer(ConfigLayer::new("x"));
233        lc.clear();
234        assert_eq!(lc.layer_count(), 0);
235    }
236}