Skip to main content

oxihuman_core/
option_cache.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5use std::collections::HashMap;
6
7/// Cache that stores optional values (present or absent).
8#[allow(dead_code)]
9pub struct OptionCache {
10    present: HashMap<String, String>,
11    absent: std::collections::HashSet<String>,
12    hits: u64,
13    misses: u64,
14}
15
16#[allow(dead_code)]
17impl OptionCache {
18    pub fn new() -> Self {
19        Self {
20            present: HashMap::new(),
21            absent: std::collections::HashSet::new(),
22            hits: 0,
23            misses: 0,
24        }
25    }
26    pub fn set_some(&mut self, key: &str, value: &str) {
27        self.absent.remove(key);
28        self.present.insert(key.to_string(), value.to_string());
29    }
30    pub fn set_none(&mut self, key: &str) {
31        self.present.remove(key);
32        self.absent.insert(key.to_string());
33    }
34    pub fn get(&mut self, key: &str) -> Option<Option<&str>> {
35        if let Some(v) = self.present.get(key) {
36            self.hits += 1;
37            Some(Some(v.as_str()))
38        } else if self.absent.contains(key) {
39            self.hits += 1;
40            Some(None)
41        } else {
42            self.misses += 1;
43            None
44        }
45    }
46    pub fn has_key(&self, key: &str) -> bool {
47        self.present.contains_key(key) || self.absent.contains(key)
48    }
49    pub fn remove(&mut self, key: &str) -> bool {
50        self.present.remove(key).is_some() || self.absent.remove(key)
51    }
52    pub fn len(&self) -> usize {
53        self.present.len() + self.absent.len()
54    }
55    pub fn is_empty(&self) -> bool {
56        self.present.is_empty() && self.absent.is_empty()
57    }
58    pub fn hits(&self) -> u64 {
59        self.hits
60    }
61    pub fn misses(&self) -> u64 {
62        self.misses
63    }
64    pub fn hit_rate(&self) -> f32 {
65        let total = self.hits + self.misses;
66        if total == 0 {
67            0.0
68        } else {
69            self.hits as f32 / total as f32
70        }
71    }
72    pub fn clear(&mut self) {
73        self.present.clear();
74        self.absent.clear();
75    }
76    pub fn present_count(&self) -> usize {
77        self.present.len()
78    }
79    pub fn absent_count(&self) -> usize {
80        self.absent.len()
81    }
82}
83
84impl Default for OptionCache {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90#[allow(dead_code)]
91pub fn new_option_cache() -> OptionCache {
92    OptionCache::new()
93}
94#[allow(dead_code)]
95pub fn oc_set_some(c: &mut OptionCache, key: &str, value: &str) {
96    c.set_some(key, value);
97}
98#[allow(dead_code)]
99pub fn oc_set_none(c: &mut OptionCache, key: &str) {
100    c.set_none(key);
101}
102#[allow(dead_code)]
103pub fn oc_get<'a>(c: &'a mut OptionCache, key: &str) -> Option<Option<&'a str>> {
104    c.get(key)
105}
106#[allow(dead_code)]
107pub fn oc_has_key(c: &OptionCache, key: &str) -> bool {
108    c.has_key(key)
109}
110#[allow(dead_code)]
111pub fn oc_remove(c: &mut OptionCache, key: &str) -> bool {
112    c.remove(key)
113}
114#[allow(dead_code)]
115pub fn oc_len(c: &OptionCache) -> usize {
116    c.len()
117}
118#[allow(dead_code)]
119pub fn oc_is_empty(c: &OptionCache) -> bool {
120    c.is_empty()
121}
122#[allow(dead_code)]
123pub fn oc_hit_rate(c: &OptionCache) -> f32 {
124    c.hit_rate()
125}
126#[allow(dead_code)]
127pub fn oc_clear(c: &mut OptionCache) {
128    c.clear();
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    #[test]
135    fn test_set_some_get() {
136        let mut c = new_option_cache();
137        oc_set_some(&mut c, "k", "v");
138        assert_eq!(oc_get(&mut c, "k"), Some(Some("v")));
139    }
140    #[test]
141    fn test_set_none_get() {
142        let mut c = new_option_cache();
143        oc_set_none(&mut c, "k");
144        assert_eq!(oc_get(&mut c, "k"), Some(None));
145    }
146    #[test]
147    fn test_miss() {
148        let mut c = new_option_cache();
149        assert_eq!(oc_get(&mut c, "missing"), None);
150    }
151    #[test]
152    fn test_has_key() {
153        let mut c = new_option_cache();
154        oc_set_some(&mut c, "a", "1");
155        assert!(oc_has_key(&c, "a"));
156        assert!(!oc_has_key(&c, "b"));
157    }
158    #[test]
159    fn test_remove() {
160        let mut c = new_option_cache();
161        oc_set_some(&mut c, "x", "y");
162        assert!(oc_remove(&mut c, "x"));
163        assert!(!oc_has_key(&c, "x"));
164    }
165    #[test]
166    fn test_hit_rate() {
167        let mut c = new_option_cache();
168        oc_set_some(&mut c, "k", "v");
169        oc_get(&mut c, "k");
170        oc_get(&mut c, "missing");
171        let r = oc_hit_rate(&c);
172        assert!((0.0..=1.0).contains(&r));
173    }
174    #[test]
175    fn test_len() {
176        let mut c = new_option_cache();
177        oc_set_some(&mut c, "a", "1");
178        oc_set_none(&mut c, "b");
179        assert_eq!(oc_len(&c), 2);
180    }
181    #[test]
182    fn test_clear() {
183        let mut c = new_option_cache();
184        oc_set_some(&mut c, "a", "1");
185        oc_clear(&mut c);
186        assert!(oc_is_empty(&c));
187    }
188    #[test]
189    fn test_present_absent_counts() {
190        let mut c = new_option_cache();
191        oc_set_some(&mut c, "a", "1");
192        oc_set_none(&mut c, "b");
193        assert_eq!(c.present_count(), 1);
194        assert_eq!(c.absent_count(), 1);
195    }
196    #[test]
197    fn test_overwrite_some_to_none() {
198        let mut c = new_option_cache();
199        oc_set_some(&mut c, "k", "v");
200        oc_set_none(&mut c, "k");
201        assert_eq!(c.present_count(), 0);
202        assert_eq!(c.absent_count(), 1);
203    }
204}