Skip to main content

oxihuman_core/
config_val.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// A typed configuration value that can hold different data types.
6#[allow(dead_code)]
7#[derive(Debug, Clone, PartialEq)]
8pub enum ConfigVal {
9    Bool(bool),
10    Int(i64),
11    Float(f64),
12    Str(String),
13    List(Vec<ConfigVal>),
14}
15
16#[allow(dead_code)]
17impl ConfigVal {
18    pub fn as_bool(&self) -> Option<bool> {
19        if let ConfigVal::Bool(v) = self {
20            Some(*v)
21        } else {
22            None
23        }
24    }
25
26    pub fn as_int(&self) -> Option<i64> {
27        if let ConfigVal::Int(v) = self {
28            Some(*v)
29        } else {
30            None
31        }
32    }
33
34    pub fn as_float(&self) -> Option<f64> {
35        if let ConfigVal::Float(v) = self {
36            Some(*v)
37        } else {
38            None
39        }
40    }
41
42    pub fn as_str(&self) -> Option<&str> {
43        if let ConfigVal::Str(v) = self {
44            Some(v.as_str())
45        } else {
46            None
47        }
48    }
49
50    pub fn as_list(&self) -> Option<&[ConfigVal]> {
51        if let ConfigVal::List(v) = self {
52            Some(v.as_slice())
53        } else {
54            None
55        }
56    }
57
58    pub fn is_bool(&self) -> bool {
59        matches!(self, ConfigVal::Bool(_))
60    }
61
62    pub fn is_int(&self) -> bool {
63        matches!(self, ConfigVal::Int(_))
64    }
65
66    pub fn is_float(&self) -> bool {
67        matches!(self, ConfigVal::Float(_))
68    }
69
70    pub fn is_str(&self) -> bool {
71        matches!(self, ConfigVal::Str(_))
72    }
73
74    pub fn is_list(&self) -> bool {
75        matches!(self, ConfigVal::List(_))
76    }
77
78    pub fn type_name(&self) -> &'static str {
79        match self {
80            ConfigVal::Bool(_) => "bool",
81            ConfigVal::Int(_) => "int",
82            ConfigVal::Float(_) => "float",
83            ConfigVal::Str(_) => "str",
84            ConfigVal::List(_) => "list",
85        }
86    }
87
88    pub fn to_f64(&self) -> Option<f64> {
89        match self {
90            ConfigVal::Float(v) => Some(*v),
91            ConfigVal::Int(v) => Some(*v as f64),
92            _ => None,
93        }
94    }
95}
96
97/// A configuration store mapping string keys to typed values.
98#[allow(dead_code)]
99#[derive(Debug, Clone)]
100pub struct ConfigStore {
101    entries: Vec<(String, ConfigVal)>,
102}
103
104#[allow(dead_code)]
105impl ConfigStore {
106    pub fn new() -> Self {
107        Self {
108            entries: Vec::new(),
109        }
110    }
111
112    pub fn set(&mut self, key: &str, val: ConfigVal) {
113        if let Some(entry) = self.entries.iter_mut().find(|(k, _)| k == key) {
114            entry.1 = val;
115        } else {
116            self.entries.push((key.to_string(), val));
117        }
118    }
119
120    pub fn get(&self, key: &str) -> Option<&ConfigVal> {
121        self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
122    }
123
124    pub fn remove(&mut self, key: &str) -> bool {
125        let len = self.entries.len();
126        self.entries.retain(|(k, _)| k != key);
127        self.entries.len() < len
128    }
129
130    pub fn len(&self) -> usize {
131        self.entries.len()
132    }
133
134    pub fn is_empty(&self) -> bool {
135        self.entries.is_empty()
136    }
137
138    pub fn keys(&self) -> Vec<&str> {
139        self.entries.iter().map(|(k, _)| k.as_str()).collect()
140    }
141
142    pub fn clear(&mut self) {
143        self.entries.clear();
144    }
145}
146
147impl Default for ConfigStore {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_config_val_bool() {
159        let v = ConfigVal::Bool(true);
160        assert_eq!(v.as_bool(), Some(true));
161        assert!(v.is_bool());
162        assert_eq!(v.type_name(), "bool");
163    }
164
165    #[test]
166    fn test_config_val_int() {
167        let v = ConfigVal::Int(42);
168        assert_eq!(v.as_int(), Some(42));
169        assert!(v.is_int());
170    }
171
172    #[test]
173    fn test_config_val_float() {
174        let v = ConfigVal::Float(2.78);
175        assert_eq!(v.as_float(), Some(2.78));
176        assert!(v.is_float());
177    }
178
179    #[test]
180    fn test_config_val_str() {
181        let v = ConfigVal::Str("hello".to_string());
182        assert_eq!(v.as_str(), Some("hello"));
183        assert!(v.is_str());
184    }
185
186    #[test]
187    fn test_config_val_list() {
188        let v = ConfigVal::List(vec![ConfigVal::Int(1), ConfigVal::Int(2)]);
189        assert!(v.is_list());
190        assert_eq!(v.as_list().expect("should succeed").len(), 2);
191    }
192
193    #[test]
194    fn test_to_f64() {
195        assert_eq!(ConfigVal::Float(1.5).to_f64(), Some(1.5));
196        assert_eq!(ConfigVal::Int(10).to_f64(), Some(10.0));
197        assert_eq!(ConfigVal::Bool(true).to_f64(), None);
198    }
199
200    #[test]
201    fn test_store_set_get() {
202        let mut s = ConfigStore::new();
203        s.set("key", ConfigVal::Int(99));
204        assert_eq!(s.get("key").and_then(|v| v.as_int()), Some(99));
205    }
206
207    #[test]
208    fn test_store_overwrite() {
209        let mut s = ConfigStore::new();
210        s.set("k", ConfigVal::Int(1));
211        s.set("k", ConfigVal::Int(2));
212        assert_eq!(s.get("k").and_then(|v| v.as_int()), Some(2));
213        assert_eq!(s.len(), 1);
214    }
215
216    #[test]
217    fn test_store_remove() {
218        let mut s = ConfigStore::new();
219        s.set("k", ConfigVal::Bool(true));
220        assert!(s.remove("k"));
221        assert!(s.is_empty());
222    }
223
224    #[test]
225    fn test_store_keys() {
226        let mut s = ConfigStore::new();
227        s.set("a", ConfigVal::Int(1));
228        s.set("b", ConfigVal::Int(2));
229        let keys = s.keys();
230        assert!(keys.contains(&"a"));
231        assert!(keys.contains(&"b"));
232    }
233}