ricecoder_storage/
config_cache.rs1use crate::cache::{CacheInvalidationStrategy, CacheManager};
7use crate::error::{StorageError, StorageResult};
8use serde_json::Value;
9use std::path::Path;
10use std::sync::Arc;
11use tracing::{debug, info};
12
13pub struct ConfigCache {
18 cache: Arc<CacheManager>,
19 ttl_seconds: u64,
20}
21
22impl ConfigCache {
23 pub fn new(cache_dir: impl AsRef<Path>, ttl_seconds: u64) -> StorageResult<Self> {
34 let cache = CacheManager::new(cache_dir)?;
35
36 Ok(Self {
37 cache: Arc::new(cache),
38 ttl_seconds,
39 })
40 }
41
42 pub fn get(&self, config_path: &Path) -> StorageResult<Option<Value>> {
52 let cache_key = self.make_cache_key(config_path);
53
54 match self.cache.get(&cache_key) {
55 Ok(Some(cached_json)) => {
56 match serde_json::from_str::<Value>(&cached_json) {
57 Ok(config) => {
58 debug!("Cache hit for config: {}", config_path.display());
59 Ok(Some(config))
60 }
61 Err(e) => {
62 debug!("Failed to deserialize cached config: {}", e);
63 let _ = self.cache.invalidate(&cache_key);
65 Ok(None)
66 }
67 }
68 }
69 Ok(None) => {
70 debug!("Cache miss for config: {}", config_path.display());
71 Ok(None)
72 }
73 Err(e) => {
74 debug!("Cache lookup error: {}", e);
75 Ok(None)
76 }
77 }
78 }
79
80 pub fn set(&self, config_path: &Path, config: &Value) -> StorageResult<()> {
91 let cache_key = self.make_cache_key(config_path);
92
93 let config_json = serde_json::to_string(config)
94 .map_err(|e| StorageError::internal(format!("Failed to serialize config: {}", e)))?;
95
96 let json_len = config_json.len();
97
98 self.cache.set(
99 &cache_key,
100 config_json,
101 CacheInvalidationStrategy::Ttl(self.ttl_seconds),
102 )?;
103
104 debug!(
105 "Cached config: {} ({} bytes)",
106 config_path.display(),
107 json_len
108 );
109
110 Ok(())
111 }
112
113 pub fn invalidate(&self, config_path: &Path) -> StorageResult<bool> {
123 let cache_key = self.make_cache_key(config_path);
124 self.cache.invalidate(&cache_key)
125 }
126
127 pub fn clear(&self) -> StorageResult<()> {
133 self.cache.clear()
134 }
135
136 pub fn cleanup_expired(&self) -> StorageResult<usize> {
142 let cleaned = self.cache.cleanup_expired()?;
143
144 if cleaned > 0 {
145 info!("Cleaned up {} expired config cache entries", cleaned);
146 }
147
148 Ok(cleaned)
149 }
150
151 fn make_cache_key(&self, config_path: &Path) -> String {
153 let path_str = config_path.to_string_lossy();
154 let sanitized = path_str
155 .chars()
156 .map(|c| {
157 if c.is_alphanumeric() || c == '_' || c == '-' || c == '.' {
158 c
159 } else {
160 '_'
161 }
162 })
163 .collect::<String>();
164
165 format!("config_{}", sanitized)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use tempfile::TempDir;
173
174 #[test]
175 fn test_cache_set_and_get() -> StorageResult<()> {
176 let temp_dir = TempDir::new().unwrap();
177 let cache = ConfigCache::new(temp_dir.path(), 3600)?;
178
179 let config_path = std::path::PathBuf::from("config.yaml");
180 let config = serde_json::json!({
181 "key": "value",
182 "nested": {
183 "setting": 42
184 }
185 });
186
187 cache.set(&config_path, &config)?;
189
190 let cached = cache.get(&config_path)?;
192 assert!(cached.is_some());
193 assert_eq!(cached.unwrap()["key"], "value");
194
195 Ok(())
196 }
197
198 #[test]
199 fn test_cache_miss() -> StorageResult<()> {
200 let temp_dir = TempDir::new().unwrap();
201 let cache = ConfigCache::new(temp_dir.path(), 3600)?;
202
203 let config_path = std::path::PathBuf::from("nonexistent.yaml");
204
205 let cached = cache.get(&config_path)?;
207 assert!(cached.is_none());
208
209 Ok(())
210 }
211
212 #[test]
213 fn test_cache_invalidate() -> StorageResult<()> {
214 let temp_dir = TempDir::new().unwrap();
215 let cache = ConfigCache::new(temp_dir.path(), 3600)?;
216
217 let config_path = std::path::PathBuf::from("config.yaml");
218 let config = serde_json::json!({"key": "value"});
219
220 cache.set(&config_path, &config)?;
222
223 let invalidated = cache.invalidate(&config_path)?;
225 assert!(invalidated);
226
227 let cached = cache.get(&config_path)?;
229 assert!(cached.is_none());
230
231 Ok(())
232 }
233
234 #[test]
235 fn test_cache_clear() -> StorageResult<()> {
236 let temp_dir = TempDir::new().unwrap();
237 let cache = ConfigCache::new(temp_dir.path(), 3600)?;
238
239 let config_path1 = std::path::PathBuf::from("config1.yaml");
240 let config_path2 = std::path::PathBuf::from("config2.yaml");
241 let config = serde_json::json!({"key": "value"});
242
243 cache.set(&config_path1, &config)?;
245 cache.set(&config_path2, &config)?;
246
247 cache.clear()?;
249
250 assert!(cache.get(&config_path1)?.is_none());
252 assert!(cache.get(&config_path2)?.is_none());
253
254 Ok(())
255 }
256}