use serde_json::Value; use std::collections::HashMap; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Mutex; pub trait StoreOps: Send + Sync { fn get(&self, key: &str) -> Option; fn set(&self, key: &str, value: Value); fn delete(&self, key: &str); fn save(&self) -> Result<(), String>; } pub struct JsonFileStore { path: PathBuf, data: Mutex>, } impl JsonFileStore { pub fn new(path: PathBuf) -> Result { let data = if path.exists() { let content = fs::read_to_string(&path).map_err(|e| format!("Failed to read store: {e}"))?; if content.trim().is_empty() { HashMap::new() } else { serde_json::from_str::>(&content) .map_err(|e| format!("Failed to parse store: {e}"))? } } else { HashMap::new() }; Ok(Self { path, data: Mutex::new(data), }) } pub fn from_path>(path: P) -> Result { Self::new(path.as_ref().to_path_buf()) } #[allow(dead_code)] pub fn path(&self) -> &Path { &self.path } fn ensure_parent_dir(&self) -> Result<(), String> { if let Some(parent) = self.path.parent() { fs::create_dir_all(parent) .map_err(|e| format!("Failed to create store directory: {e}"))?; } Ok(()) } } impl StoreOps for JsonFileStore { fn get(&self, key: &str) -> Option { self.data.lock().ok().and_then(|map| map.get(key).cloned()) } fn set(&self, key: &str, value: Value) { if let Ok(mut map) = self.data.lock() { map.insert(key.to_string(), value); } } fn delete(&self, key: &str) { if let Ok(mut map) = self.data.lock() { map.remove(key); } } fn save(&self) -> Result<(), String> { self.ensure_parent_dir()?; let map = self.data.lock().map_err(|e| e.to_string())?; let content = serde_json::to_string_pretty(&*map).map_err(|e| format!("Serialize failed: {e}"))?; fs::write(&self.path, content).map_err(|e| format!("Failed to write store: {e}")) } }