llm_config_cache/
manager.rs1use crate::{l1::L1Cache, l2::L2Cache, Result};
4use llm_config_core::ConfigEntry;
5use std::path::Path;
6use std::sync::Arc;
7
8pub struct CacheManager {
10 l1: Arc<L1Cache>,
11 l2: Arc<L2Cache>,
12}
13
14impl CacheManager {
15 pub fn new(l1_size: usize, l2_dir: impl AsRef<Path>) -> Result<Self> {
17 Ok(Self {
18 l1: Arc::new(L1Cache::new(l1_size)),
19 l2: Arc::new(L2Cache::new(l2_dir)?),
20 })
21 }
22
23 pub fn get(&self, namespace: &str, key: &str, env: &str) -> Result<ConfigEntry> {
30 if let Ok(entry) = self.l1.get(namespace, key, env) {
32 return Ok(entry);
33 }
34
35 if let Ok(entry) = self.l2.get(namespace, key, env) {
37 self.l1.put(entry.clone())?;
39 return Ok(entry);
40 }
41
42 Err(crate::CacheError::CacheMiss(format!(
44 "{}:{}:{}",
45 namespace, key, env
46 )))
47 }
48
49 pub fn put(&self, entry: ConfigEntry) -> Result<()> {
51 self.l1.put(entry.clone())?;
53 self.l2.put(&entry)?;
54 Ok(())
55 }
56
57 pub fn invalidate(&self, namespace: &str, key: &str, env: &str) -> Result<()> {
59 self.l1.invalidate(namespace, key, env);
60 self.l2.invalidate(namespace, key, env)?;
61 Ok(())
62 }
63
64 pub fn clear(&self) -> Result<()> {
66 self.l1.clear();
67 self.l2.clear()?;
68 Ok(())
69 }
70
71 pub fn l1_stats(&self) -> crate::l1::CacheStats {
73 self.l1.stats()
74 }
75
76 pub fn l2_size(&self) -> usize {
78 self.l2.size()
79 }
80
81 pub fn clear_l1(&self) {
83 self.l1.clear();
84 }
85
86 pub fn clear_l2(&self) -> Result<()> {
88 self.l2.clear()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use llm_config_core::{ConfigMetadata, ConfigValue, Environment};
96 use tempfile::TempDir;
97 use uuid::Uuid;
98
99 fn create_test_entry(namespace: &str, key: &str, env: Environment) -> ConfigEntry {
100 ConfigEntry {
101 id: Uuid::new_v4(),
102 namespace: namespace.to_string(),
103 key: key.to_string(),
104 value: ConfigValue::String("test-value".to_string()),
105 environment: env,
106 version: 1,
107 metadata: ConfigMetadata {
108 created_at: chrono::Utc::now(),
109 created_by: "test".to_string(),
110 updated_at: chrono::Utc::now(),
111 updated_by: "test".to_string(),
112 tags: vec![],
113 description: None,
114 },
115 }
116 }
117
118 #[test]
119 fn test_manager_creation() {
120 let temp_dir = TempDir::new().unwrap();
121 let manager = CacheManager::new(100, temp_dir.path()).unwrap();
122 assert_eq!(manager.l1_stats().size, 0);
123 assert_eq!(manager.l2_size(), 0);
124 }
125
126 #[test]
127 fn test_put_and_get() {
128 let temp_dir = TempDir::new().unwrap();
129 let manager = CacheManager::new(100, temp_dir.path()).unwrap();
130
131 let entry = create_test_entry("ns", "key1", Environment::Development);
132 manager.put(entry.clone()).unwrap();
133
134 let retrieved = manager.get("ns", "key1", "development").unwrap();
135 assert_eq!(retrieved.id, entry.id);
136 }
137
138 #[test]
139 fn test_l1_to_l2_fallback() {
140 let temp_dir = TempDir::new().unwrap();
141 let manager = CacheManager::new(100, temp_dir.path()).unwrap();
142
143 let entry = create_test_entry("ns", "key1", Environment::Development);
144 manager.put(entry.clone()).unwrap();
145
146 manager.l1.clear();
148
149 let retrieved = manager.get("ns", "key1", "development").unwrap();
151 assert_eq!(retrieved.id, entry.id);
152
153 let stats = manager.l1_stats();
155 assert_eq!(stats.size, 1);
156 }
157
158 #[test]
159 fn test_invalidate() {
160 let temp_dir = TempDir::new().unwrap();
161 let manager = CacheManager::new(100, temp_dir.path()).unwrap();
162
163 let entry = create_test_entry("ns", "key1", Environment::Development);
164 manager.put(entry).unwrap();
165
166 manager.invalidate("ns", "key1", "development").unwrap();
167
168 assert!(manager.get("ns", "key1", "development").is_err());
169 }
170
171 #[test]
172 fn test_clear() {
173 let temp_dir = TempDir::new().unwrap();
174 let manager = CacheManager::new(100, temp_dir.path()).unwrap();
175
176 for i in 0..10 {
177 let entry = create_test_entry("ns", &format!("key{}", i), Environment::Development);
178 manager.put(entry).unwrap();
179 }
180
181 assert_eq!(manager.l1_stats().size, 10);
182 assert_eq!(manager.l2_size(), 10);
183
184 manager.clear().unwrap();
185
186 assert_eq!(manager.l1_stats().size, 0);
187 assert_eq!(manager.l2_size(), 0);
188 }
189
190 #[test]
191 fn test_cache_promotion() {
192 let temp_dir = TempDir::new().unwrap();
193 let manager = CacheManager::new(2, temp_dir.path()).unwrap(); for i in 0..3 {
197 let entry = create_test_entry("ns", &format!("key{}", i), Environment::Development);
198 manager.put(entry).unwrap();
199 }
200
201 assert_eq!(manager.l1_stats().size, 2);
203 assert_eq!(manager.l2_size(), 3);
204
205 manager.get("ns", "key0", "development").unwrap();
207
208 let stats = manager.l1_stats();
210 assert_eq!(stats.size, 2); }
212}