Skip to main content

oxihuman_core/
context_map.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! A string-keyed context map for passing heterogeneous values through pipelines.
6
7use std::collections::HashMap;
8
9/// Supported value types in the context map.
10#[allow(dead_code)]
11#[derive(Debug, Clone, PartialEq)]
12pub enum CtxValue {
13    Bool(bool),
14    Int(i64),
15    Float(f32),
16    Str(String),
17    Bytes(Vec<u8>),
18}
19
20/// String-keyed map of typed context values.
21#[allow(dead_code)]
22#[derive(Debug, Clone, Default)]
23pub struct ContextMap {
24    data: HashMap<String, CtxValue>,
25}
26
27#[allow(dead_code)]
28impl ContextMap {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn set_bool(&mut self, key: &str, val: bool) {
34        self.data.insert(key.to_string(), CtxValue::Bool(val));
35    }
36
37    pub fn set_int(&mut self, key: &str, val: i64) {
38        self.data.insert(key.to_string(), CtxValue::Int(val));
39    }
40
41    pub fn set_float(&mut self, key: &str, val: f32) {
42        self.data.insert(key.to_string(), CtxValue::Float(val));
43    }
44
45    pub fn set_str(&mut self, key: &str, val: &str) {
46        self.data
47            .insert(key.to_string(), CtxValue::Str(val.to_string()));
48    }
49
50    pub fn set_bytes(&mut self, key: &str, val: Vec<u8>) {
51        self.data.insert(key.to_string(), CtxValue::Bytes(val));
52    }
53
54    pub fn get(&self, key: &str) -> Option<&CtxValue> {
55        self.data.get(key)
56    }
57
58    pub fn get_bool(&self, key: &str) -> Option<bool> {
59        match self.data.get(key) {
60            Some(CtxValue::Bool(v)) => Some(*v),
61            _ => None,
62        }
63    }
64
65    pub fn get_int(&self, key: &str) -> Option<i64> {
66        match self.data.get(key) {
67            Some(CtxValue::Int(v)) => Some(*v),
68            _ => None,
69        }
70    }
71
72    pub fn get_float(&self, key: &str) -> Option<f32> {
73        match self.data.get(key) {
74            Some(CtxValue::Float(v)) => Some(*v),
75            _ => None,
76        }
77    }
78
79    pub fn get_str(&self, key: &str) -> Option<&str> {
80        match self.data.get(key) {
81            Some(CtxValue::Str(v)) => Some(v.as_str()),
82            _ => None,
83        }
84    }
85
86    pub fn contains(&self, key: &str) -> bool {
87        self.data.contains_key(key)
88    }
89
90    pub fn remove(&mut self, key: &str) -> bool {
91        self.data.remove(key).is_some()
92    }
93
94    pub fn len(&self) -> usize {
95        self.data.len()
96    }
97
98    pub fn is_empty(&self) -> bool {
99        self.data.is_empty()
100    }
101
102    pub fn keys(&self) -> Vec<&str> {
103        self.data.keys().map(|k| k.as_str()).collect()
104    }
105
106    pub fn clear(&mut self) {
107        self.data.clear();
108    }
109
110    /// Merge another map into self; other's values win on conflict.
111    pub fn merge(&mut self, other: &ContextMap) {
112        for (k, v) in &other.data {
113            self.data.insert(k.clone(), v.clone());
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn new_is_empty() {
124        assert!(ContextMap::new().is_empty());
125    }
126
127    #[test]
128    fn set_get_bool() {
129        let mut m = ContextMap::new();
130        m.set_bool("flag", true);
131        assert_eq!(m.get_bool("flag"), Some(true));
132    }
133
134    #[test]
135    fn set_get_int() {
136        let mut m = ContextMap::new();
137        m.set_int("count", 42);
138        assert_eq!(m.get_int("count"), Some(42));
139    }
140
141    #[test]
142    fn set_get_float() {
143        let mut m = ContextMap::new();
144        m.set_float("ratio", std::f32::consts::PI);
145        assert!(
146            (m.get_float("ratio").expect("should succeed") - std::f32::consts::PI).abs() < 1e-6
147        );
148    }
149
150    #[test]
151    fn set_get_str() {
152        let mut m = ContextMap::new();
153        m.set_str("label", "hello");
154        assert_eq!(m.get_str("label"), Some("hello"));
155    }
156
157    #[test]
158    fn type_mismatch_returns_none() {
159        let mut m = ContextMap::new();
160        m.set_bool("x", false);
161        assert!(m.get_int("x").is_none());
162    }
163
164    #[test]
165    fn remove_entry() {
166        let mut m = ContextMap::new();
167        m.set_str("a", "v");
168        assert!(m.remove("a"));
169        assert!(!m.contains("a"));
170        assert!(!m.remove("a"));
171    }
172
173    #[test]
174    fn merge_overwrites() {
175        let mut a = ContextMap::new();
176        a.set_int("n", 1);
177        let mut b = ContextMap::new();
178        b.set_int("n", 2);
179        b.set_str("extra", "hi");
180        a.merge(&b);
181        assert_eq!(a.get_int("n"), Some(2));
182        assert_eq!(a.get_str("extra"), Some("hi"));
183    }
184
185    #[test]
186    fn set_bytes_and_get() {
187        let mut m = ContextMap::new();
188        m.set_bytes("data", vec![1, 2, 3]);
189        assert!(m
190            .get("data")
191            .is_some_and(|v| matches!(v, CtxValue::Bytes(_))));
192    }
193
194    #[test]
195    fn clear_empties() {
196        let mut m = ContextMap::new();
197        m.set_int("a", 1);
198        m.clear();
199        assert!(m.is_empty());
200    }
201}