1use crate::error::Result;
7use crate::metadata::ToolMetadata;
8use crate::registry::ToolRegistry;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12
13pub trait ToolRegistryStorage: Send + Sync {
17 fn save_registry(&self, registry: &ToolRegistry, path: &Path) -> Result<()>;
28
29 fn load_registry(&self, path: &Path) -> Result<ToolRegistry>;
39
40 fn save_tool(&self, tool: &ToolMetadata, path: &Path) -> Result<()>;
51
52 fn load_tool(&self, path: &Path) -> Result<ToolMetadata>;
62
63 fn list_tools(&self, path: &Path) -> Result<Vec<ToolMetadata>>;
73}
74
75pub struct JsonToolRegistryStorage;
79
80impl JsonToolRegistryStorage {
81 pub fn new() -> Self {
83 Self
84 }
85}
86
87impl Default for JsonToolRegistryStorage {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl ToolRegistryStorage for JsonToolRegistryStorage {
94 fn save_registry(&self, registry: &ToolRegistry, path: &Path) -> Result<()> {
95 let tools: Vec<ToolMetadata> = registry
96 .list_tools()
97 .into_iter()
98 .cloned()
99 .collect();
100
101 let json = serde_json::to_string_pretty(&tools)
102 .map_err(|e| crate::error::Error::SerializationError(e))?;
103
104 std::fs::write(path, json)
105 .map_err(|e| crate::error::Error::IoError(e))?;
106
107 Ok(())
108 }
109
110 fn load_registry(&self, path: &Path) -> Result<ToolRegistry> {
111 let content = std::fs::read_to_string(path)
112 .map_err(|e| crate::error::Error::IoError(e))?;
113
114 let tools: Vec<ToolMetadata> = serde_json::from_str(&content)
115 .map_err(|e| crate::error::Error::SerializationError(e))?;
116
117 let mut registry = ToolRegistry::new();
118 for tool in tools {
119 registry.register_tool(tool)?;
120 }
121
122 Ok(registry)
123 }
124
125 fn save_tool(&self, tool: &ToolMetadata, path: &Path) -> Result<()> {
126 let json = serde_json::to_string_pretty(tool)
127 .map_err(|e| crate::error::Error::SerializationError(e))?;
128
129 std::fs::write(path, json)
130 .map_err(|e| crate::error::Error::IoError(e))?;
131
132 Ok(())
133 }
134
135 fn load_tool(&self, path: &Path) -> Result<ToolMetadata> {
136 let content = std::fs::read_to_string(path)
137 .map_err(|e| crate::error::Error::IoError(e))?;
138
139 let tool: ToolMetadata = serde_json::from_str(&content)
140 .map_err(|e| crate::error::Error::SerializationError(e))?;
141
142 Ok(tool)
143 }
144
145 fn list_tools(&self, path: &Path) -> Result<Vec<ToolMetadata>> {
146 if !path.exists() {
147 return Ok(Vec::new());
148 }
149
150 let mut tools = Vec::new();
151
152 for entry in std::fs::read_dir(path)
153 .map_err(|e| crate::error::Error::IoError(e))?
154 {
155 let entry = entry.map_err(|e| crate::error::Error::IoError(e))?;
156 let file_path = entry.path();
157
158 if file_path.extension().map_or(false, |ext| ext == "json") {
159 match self.load_tool(&file_path) {
160 Ok(tool) => tools.push(tool),
161 Err(e) => {
162 tracing::warn!("Failed to load tool from {:?}: {}", file_path, e);
163 }
164 }
165 }
166 }
167
168 Ok(tools)
169 }
170}
171
172pub struct ToolRegistryPersistence {
176 storage: Arc<dyn ToolRegistryStorage>,
177 registry_path: PathBuf,
178 tools_dir: PathBuf,
179}
180
181impl ToolRegistryPersistence {
182 pub fn new(
190 storage: Arc<dyn ToolRegistryStorage>,
191 registry_path: PathBuf,
192 tools_dir: PathBuf,
193 ) -> Self {
194 Self {
195 storage,
196 registry_path,
197 tools_dir,
198 }
199 }
200
201 pub fn save_registry(&self, registry: &ToolRegistry) -> Result<()> {
203 if let Some(parent) = self.registry_path.parent() {
205 std::fs::create_dir_all(parent)
206 .map_err(|e| crate::error::Error::IoError(e))?;
207 }
208
209 self.storage.save_registry(registry, &self.registry_path)?;
210 tracing::info!("Tool registry saved to {:?}", self.registry_path);
211
212 Ok(())
213 }
214
215 pub fn load_registry(&self) -> Result<ToolRegistry> {
217 if !self.registry_path.exists() {
218 tracing::info!("Registry file not found at {:?}, creating new registry", self.registry_path);
219 return Ok(ToolRegistry::new());
220 }
221
222 let registry = self.storage.load_registry(&self.registry_path)?;
223 tracing::info!("Tool registry loaded from {:?}", self.registry_path);
224
225 Ok(registry)
226 }
227
228 pub fn save_tool(&self, tool: &ToolMetadata) -> Result<()> {
230 std::fs::create_dir_all(&self.tools_dir)
232 .map_err(|e| crate::error::Error::IoError(e))?;
233
234 let tool_path = self.tools_dir.join(format!("{}.json", tool.id));
235 self.storage.save_tool(tool, &tool_path)?;
236 tracing::info!("Tool saved to {:?}", tool_path);
237
238 Ok(())
239 }
240
241 pub fn load_tool(&self, tool_id: &str) -> Result<ToolMetadata> {
243 let tool_path = self.tools_dir.join(format!("{}.json", tool_id));
244 self.storage.load_tool(&tool_path)
245 }
246
247 pub fn list_tools(&self) -> Result<Vec<ToolMetadata>> {
249 self.storage.list_tools(&self.tools_dir)
250 }
251
252 pub fn export_registry(&self, registry: &ToolRegistry, path: &Path, format: &str) -> Result<()> {
254 match format {
255 "json" => {
256 let tools: Vec<ToolMetadata> = registry
257 .list_tools()
258 .into_iter()
259 .cloned()
260 .collect();
261
262 let json = serde_json::to_string_pretty(&tools)
263 .map_err(|e| crate::error::Error::SerializationError(e))?;
264
265 std::fs::write(path, json)
266 .map_err(|e| crate::error::Error::IoError(e))?;
267
268 Ok(())
269 }
270 "yaml" => {
271 let tools: Vec<ToolMetadata> = registry
272 .list_tools()
273 .into_iter()
274 .cloned()
275 .collect();
276
277 let yaml = serde_yaml::to_string(&tools)
278 .map_err(|e| crate::error::Error::ConfigError(e.to_string()))?;
279
280 std::fs::write(path, yaml)
281 .map_err(|e| crate::error::Error::IoError(e))?;
282
283 Ok(())
284 }
285 _ => Err(crate::error::Error::ConfigError(format!(
286 "Unsupported export format: {}",
287 format
288 ))),
289 }
290 }
291
292 pub fn import_registry(&self, path: &Path, format: &str) -> Result<ToolRegistry> {
294 match format {
295 "json" => {
296 let content = std::fs::read_to_string(path)
297 .map_err(|e| crate::error::Error::IoError(e))?;
298
299 let tools: Vec<ToolMetadata> = serde_json::from_str(&content)
300 .map_err(|e| crate::error::Error::SerializationError(e))?;
301
302 let mut registry = ToolRegistry::new();
303 for tool in tools {
304 registry.register_tool(tool)?;
305 }
306
307 Ok(registry)
308 }
309 "yaml" => {
310 let content = std::fs::read_to_string(path)
311 .map_err(|e| crate::error::Error::IoError(e))?;
312
313 let tools: Vec<ToolMetadata> = serde_yaml::from_str(&content)
314 .map_err(|e| crate::error::Error::ConfigError(e.to_string()))?;
315
316 let mut registry = ToolRegistry::new();
317 for tool in tools {
318 registry.register_tool(tool)?;
319 }
320
321 Ok(registry)
322 }
323 _ => Err(crate::error::Error::ConfigError(format!(
324 "Unsupported import format: {}",
325 format
326 ))),
327 }
328 }
329}
330
331pub struct ToolRegistryCache {
336 cache: std::sync::Mutex<HashMap<String, (ToolRegistry, std::time::Instant)>>,
337 ttl_secs: u64,
338}
339
340impl ToolRegistryCache {
341 pub fn new(ttl_secs: u64) -> Self {
347 Self {
348 cache: std::sync::Mutex::new(HashMap::new()),
349 ttl_secs,
350 }
351 }
352
353 pub fn get(&self, key: &str) -> Option<ToolRegistry> {
355 let cache = self.cache.lock().unwrap();
356 if let Some((registry, timestamp)) = cache.get(key) {
357 let elapsed = timestamp.elapsed().as_secs();
358 if elapsed < self.ttl_secs {
359 return Some(registry.clone());
360 }
361 }
362 None
363 }
364
365 pub fn set(&self, key: String, registry: ToolRegistry) {
367 let mut cache = self.cache.lock().unwrap();
368 cache.insert(key, (registry, std::time::Instant::now()));
369 }
370
371 pub fn clear(&self) {
373 let mut cache = self.cache.lock().unwrap();
374 cache.clear();
375 }
376
377 pub fn cleanup_expired(&self) {
379 let mut cache = self.cache.lock().unwrap();
380 let now = std::time::Instant::now();
381 cache.retain(|_, (_, timestamp)| now.duration_since(*timestamp).as_secs() < self.ttl_secs);
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::metadata::ToolSource;
389 use tempfile::TempDir;
390
391 #[test]
392 fn test_json_tool_registry_storage_save_and_load() {
393 let temp_dir = TempDir::new().unwrap();
394 let registry_path = temp_dir.path().join("registry.json");
395
396 let mut registry = ToolRegistry::new();
397 let tool = ToolMetadata::new(
398 "test-tool".to_string(),
399 "Test Tool".to_string(),
400 "A test tool".to_string(),
401 "test".to_string(),
402 "string".to_string(),
403 ToolSource::Custom,
404 );
405 registry.register_tool(tool).unwrap();
406
407 let storage = JsonToolRegistryStorage::new();
408 storage.save_registry(®istry, ®istry_path).unwrap();
409
410 assert!(registry_path.exists());
411
412 let loaded_registry = storage.load_registry(®istry_path).unwrap();
413 assert_eq!(loaded_registry.tool_count(), 1);
414 }
415
416 #[test]
417 fn test_tool_registry_persistence_save_and_load() {
418 let temp_dir = TempDir::new().unwrap();
419 let registry_path = temp_dir.path().join("registry.json");
420 let tools_dir = temp_dir.path().join("tools");
421
422 let storage = Arc::new(JsonToolRegistryStorage::new());
423 let persistence = ToolRegistryPersistence::new(storage, registry_path, tools_dir);
424
425 let mut registry = ToolRegistry::new();
426 let tool = ToolMetadata::new(
427 "test-tool".to_string(),
428 "Test Tool".to_string(),
429 "A test tool".to_string(),
430 "test".to_string(),
431 "string".to_string(),
432 ToolSource::Custom,
433 );
434 registry.register_tool(tool).unwrap();
435
436 persistence.save_registry(®istry).unwrap();
437 let loaded_registry = persistence.load_registry().unwrap();
438
439 assert_eq!(loaded_registry.tool_count(), 1);
440 }
441
442 #[test]
443 fn test_tool_registry_cache() {
444 let cache = ToolRegistryCache::new(60);
445 let registry = ToolRegistry::new();
446
447 cache.set("test".to_string(), registry.clone());
448 assert!(cache.get("test").is_some());
449
450 cache.clear();
451 assert!(cache.get("test").is_none());
452 }
453
454 #[test]
455 fn test_tool_registry_cache_expiration() {
456 let cache = ToolRegistryCache::new(0); let registry = ToolRegistry::new();
458
459 cache.set("test".to_string(), registry);
460 std::thread::sleep(std::time::Duration::from_millis(10));
461
462 assert!(cache.get("test").is_none());
463 }
464
465 #[test]
466 fn test_tool_registry_persistence_export_json() {
467 let temp_dir = TempDir::new().unwrap();
468 let registry_path = temp_dir.path().join("registry.json");
469 let tools_dir = temp_dir.path().join("tools");
470 let export_path = temp_dir.path().join("export.json");
471
472 let storage = Arc::new(JsonToolRegistryStorage::new());
473 let persistence = ToolRegistryPersistence::new(storage, registry_path, tools_dir);
474
475 let mut registry = ToolRegistry::new();
476 let tool = ToolMetadata::new(
477 "test-tool".to_string(),
478 "Test Tool".to_string(),
479 "A test tool".to_string(),
480 "test".to_string(),
481 "string".to_string(),
482 ToolSource::Custom,
483 );
484 registry.register_tool(tool).unwrap();
485
486 persistence.export_registry(®istry, &export_path, "json").unwrap();
487 assert!(export_path.exists());
488
489 let imported_registry = persistence.import_registry(&export_path, "json").unwrap();
490 assert_eq!(imported_registry.tool_count(), 1);
491 }
492}