Skip to main content

oxihuman_core/
path_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 for resolved filesystem-style paths.
8#[allow(dead_code)]
9pub struct PathCache {
10    cache: HashMap<String, String>,
11    hits: u64,
12    misses: u64,
13    max_size: usize,
14}
15
16#[allow(dead_code)]
17impl PathCache {
18    pub fn new(max_size: usize) -> Self {
19        Self {
20            cache: HashMap::new(),
21            hits: 0,
22            misses: 0,
23            max_size: max_size.max(1),
24        }
25    }
26    pub fn get(&mut self, key: &str) -> Option<&str> {
27        if self.cache.contains_key(key) {
28            self.hits += 1;
29            self.cache.get(key).map(|s| s.as_str())
30        } else {
31            self.misses += 1;
32            None
33        }
34    }
35    pub fn insert(&mut self, key: &str, resolved: &str) {
36        if self.cache.len() >= self.max_size && !self.cache.contains_key(key) {
37            if let Some(k) = self.cache.keys().next().cloned() {
38                self.cache.remove(&k);
39            }
40        }
41        self.cache.insert(key.to_string(), resolved.to_string());
42    }
43    pub fn invalidate(&mut self, key: &str) -> bool {
44        self.cache.remove(key).is_some()
45    }
46    pub fn contains(&self, key: &str) -> bool {
47        self.cache.contains_key(key)
48    }
49    pub fn len(&self) -> usize {
50        self.cache.len()
51    }
52    pub fn is_empty(&self) -> bool {
53        self.cache.is_empty()
54    }
55    pub fn hits(&self) -> u64 {
56        self.hits
57    }
58    pub fn misses(&self) -> u64 {
59        self.misses
60    }
61    pub fn hit_rate(&self) -> f32 {
62        let t = self.hits + self.misses;
63        if t == 0 {
64            0.0
65        } else {
66            self.hits as f32 / t as f32
67        }
68    }
69    pub fn clear(&mut self) {
70        self.cache.clear();
71    }
72    pub fn max_size(&self) -> usize {
73        self.max_size
74    }
75}
76
77#[allow(dead_code)]
78pub fn new_path_cache(max_size: usize) -> PathCache {
79    PathCache::new(max_size)
80}
81#[allow(dead_code)]
82pub fn pc_get<'a>(c: &'a mut PathCache, key: &str) -> Option<&'a str> {
83    c.get(key)
84}
85#[allow(dead_code)]
86pub fn pc_insert(c: &mut PathCache, key: &str, resolved: &str) {
87    c.insert(key, resolved);
88}
89#[allow(dead_code)]
90pub fn pc_invalidate(c: &mut PathCache, key: &str) -> bool {
91    c.invalidate(key)
92}
93#[allow(dead_code)]
94pub fn pc_contains(c: &PathCache, key: &str) -> bool {
95    c.contains(key)
96}
97#[allow(dead_code)]
98pub fn pc_len(c: &PathCache) -> usize {
99    c.len()
100}
101#[allow(dead_code)]
102pub fn pc_is_empty(c: &PathCache) -> bool {
103    c.is_empty()
104}
105#[allow(dead_code)]
106pub fn pc_hit_rate(c: &PathCache) -> f32 {
107    c.hit_rate()
108}
109#[allow(dead_code)]
110pub fn pc_clear(c: &mut PathCache) {
111    c.clear();
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    #[test]
118    fn test_insert_get() {
119        let mut c = new_path_cache(16);
120        pc_insert(&mut c, "/a", "/resolved/a");
121        assert_eq!(pc_get(&mut c, "/a"), Some("/resolved/a"));
122    }
123    #[test]
124    fn test_miss() {
125        let mut c = new_path_cache(16);
126        assert_eq!(pc_get(&mut c, "/nope"), None);
127    }
128    #[test]
129    fn test_invalidate() {
130        let mut c = new_path_cache(16);
131        pc_insert(&mut c, "/k", "/v");
132        assert!(pc_invalidate(&mut c, "/k"));
133        assert!(!pc_contains(&c, "/k"));
134    }
135    #[test]
136    fn test_hit_rate() {
137        let mut c = new_path_cache(16);
138        pc_insert(&mut c, "/k", "/v");
139        pc_get(&mut c, "/k");
140        pc_get(&mut c, "/missing");
141        let r = pc_hit_rate(&c);
142        assert!((0.0..=1.0).contains(&r));
143    }
144    #[test]
145    fn test_len() {
146        let mut c = new_path_cache(16);
147        pc_insert(&mut c, "/a", "/ra");
148        pc_insert(&mut c, "/b", "/rb");
149        assert_eq!(pc_len(&c), 2);
150    }
151    #[test]
152    fn test_max_size_respected() {
153        let mut c = new_path_cache(2);
154        pc_insert(&mut c, "/a", "/ra");
155        pc_insert(&mut c, "/b", "/rb");
156        pc_insert(&mut c, "/c", "/rc");
157        assert!(pc_len(&c) <= 2);
158    }
159    #[test]
160    fn test_clear() {
161        let mut c = new_path_cache(16);
162        pc_insert(&mut c, "/a", "/ra");
163        pc_clear(&mut c);
164        assert!(pc_is_empty(&c));
165    }
166    #[test]
167    fn test_contains() {
168        let mut c = new_path_cache(16);
169        pc_insert(&mut c, "/x", "/rx");
170        assert!(pc_contains(&c, "/x"));
171        assert!(!pc_contains(&c, "/y"));
172    }
173    #[test]
174    fn test_is_empty() {
175        let c = new_path_cache(8);
176        assert!(pc_is_empty(&c));
177    }
178    #[test]
179    fn test_max_size_getter() {
180        let c = new_path_cache(32);
181        assert_eq!(c.max_size(), 32);
182    }
183}