1use super::{Plugin, PluginError, PluginMetadata};
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use tracing::{debug, info, warn};
11
12#[derive(Debug, Clone)]
14pub struct PluginRegistryEntry {
15 pub metadata: PluginMetadata,
17 pub source: Option<PathBuf>,
19 pub loaded: bool,
21 pub registered_at: chrono::DateTime<chrono::Utc>,
23 pub tags: Vec<String>,
25}
26
27pub struct PluginRegistry {
33 entries: HashMap<String, PluginRegistryEntry>,
34 search_paths: Vec<PathBuf>,
35}
36
37impl PluginRegistry {
38 pub fn new() -> Self {
40 Self {
41 entries: HashMap::new(),
42 search_paths: Vec::new(),
43 }
44 }
45
46 pub fn add_search_path(&mut self, path: impl Into<PathBuf>) {
48 let path = path.into();
49 if !self.search_paths.contains(&path) {
50 self.search_paths.push(path);
51 }
52 }
53
54 pub fn register(
58 &mut self,
59 metadata: PluginMetadata,
60 source: Option<PathBuf>,
61 ) -> Result<(), PluginError> {
62 let name = metadata.name.clone();
63
64 if self.entries.contains_key(&name) {
65 return Err(PluginError::AlreadyRegistered(name));
66 }
67
68 info!("Registering plugin in registry: {}", name);
69
70 self.entries.insert(
71 name.clone(),
72 PluginRegistryEntry {
73 metadata,
74 source,
75 loaded: false,
76 registered_at: chrono::Utc::now(),
77 tags: Vec::new(),
78 },
79 );
80
81 Ok(())
82 }
83
84 pub fn register_plugin(&mut self, plugin: &Arc<dyn Plugin>) -> Result<(), PluginError> {
88 let metadata = plugin.metadata().clone();
89 let name = metadata.name.clone();
90
91 if self.entries.contains_key(&name) {
92 return Err(PluginError::AlreadyRegistered(name));
93 }
94
95 self.entries.insert(
96 name.clone(),
97 PluginRegistryEntry {
98 metadata,
99 source: None,
100 loaded: true,
101 registered_at: chrono::Utc::now(),
102 tags: Vec::new(),
103 },
104 );
105
106 Ok(())
107 }
108
109 pub fn mark_loaded(&mut self, name: &str) -> Result<(), PluginError> {
111 let entry = self
112 .entries
113 .get_mut(name)
114 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
115
116 entry.loaded = true;
117 Ok(())
118 }
119
120 pub fn mark_unloaded(&mut self, name: &str) -> Result<(), PluginError> {
122 let entry = self
123 .entries
124 .get_mut(name)
125 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
126
127 entry.loaded = false;
128 Ok(())
129 }
130
131 pub fn unregister(&mut self, name: &str) -> Result<(), PluginError> {
133 self.entries
134 .remove(name)
135 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
136
137 info!("Unregistered plugin from registry: {}", name);
138 Ok(())
139 }
140
141 pub fn get(&self, name: &str) -> Option<&PluginRegistryEntry> {
143 self.entries.get(name)
144 }
145
146 pub fn contains(&self, name: &str) -> bool {
148 self.entries.contains_key(name)
149 }
150
151 pub fn list_all(&self) -> Vec<&PluginRegistryEntry> {
153 self.entries.values().collect()
154 }
155
156 pub fn list_loaded(&self) -> Vec<&PluginRegistryEntry> {
158 self.entries.values().filter(|entry| entry.loaded).collect()
159 }
160
161 pub fn list_unloaded(&self) -> Vec<&PluginRegistryEntry> {
163 self.entries
164 .values()
165 .filter(|entry| !entry.loaded)
166 .collect()
167 }
168
169 pub fn find_by_capability(&self, capability: &str) -> Vec<&PluginRegistryEntry> {
171 self.entries
172 .values()
173 .filter(|entry| {
174 entry
175 .metadata
176 .capabilities
177 .contains(&capability.to_string())
178 })
179 .collect()
180 }
181
182 pub fn find_by_tag(&self, tag: &str) -> Vec<&PluginRegistryEntry> {
184 self.entries
185 .values()
186 .filter(|entry| entry.tags.contains(&tag.to_string()))
187 .collect()
188 }
189
190 pub fn add_tag(&mut self, name: &str, tag: impl Into<String>) -> Result<(), PluginError> {
192 let entry = self
193 .entries
194 .get_mut(name)
195 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
196
197 let tag = tag.into();
198 if !entry.tags.contains(&tag) {
199 entry.tags.push(tag);
200 }
201
202 Ok(())
203 }
204
205 pub fn remove_tag(&mut self, name: &str, tag: &str) -> Result<(), PluginError> {
207 let entry = self
208 .entries
209 .get_mut(name)
210 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
211
212 entry.tags.retain(|t| t != tag);
213 Ok(())
214 }
215
216 pub fn stats(&self) -> PluginRegistryStats {
218 let total = self.entries.len();
219 let loaded = self.list_loaded().len();
220 let unloaded = self.list_unloaded().len();
221
222 let mut capabilities = HashMap::new();
223 for entry in self.entries.values() {
224 for capability in &entry.metadata.capabilities {
225 *capabilities.entry(capability.clone()).or_insert(0) += 1;
226 }
227 }
228
229 PluginRegistryStats {
230 total_plugins: total,
231 loaded_plugins: loaded,
232 unloaded_plugins: unloaded,
233 capabilities,
234 }
235 }
236
237 pub fn clear(&mut self) {
239 self.entries.clear();
240 }
241}
242
243impl Default for PluginRegistry {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct PluginRegistryStats {
252 pub total_plugins: usize,
254 pub loaded_plugins: usize,
256 pub unloaded_plugins: usize,
258 pub capabilities: HashMap<String, usize>,
260}
261
262pub struct PluginDiscovery {
266 search_paths: Vec<PathBuf>,
267}
268
269impl PluginDiscovery {
270 pub fn new() -> Self {
272 Self {
273 search_paths: Vec::new(),
274 }
275 }
276
277 pub fn add_path(&mut self, path: impl Into<PathBuf>) {
279 self.search_paths.push(path.into());
280 }
281
282 pub fn discover(&self) -> Vec<PluginMetadata> {
287 let mut discovered = Vec::new();
288
289 for path in &self.search_paths {
290 if let Ok(entries) = self.discover_in_path(path) {
291 discovered.extend(entries);
292 }
293 }
294
295 discovered
296 }
297
298 fn discover_in_path(&self, path: &Path) -> Result<Vec<PluginMetadata>, PluginError> {
300 debug!("Searching for plugins in: {}", path.display());
301
302 if !path.exists() {
303 warn!("Plugin search path does not exist: {}", path.display());
304 return Ok(Vec::new());
305 }
306
307 warn!("Plugin discovery not yet implemented");
314
315 Ok(Vec::new())
316 }
317
318 pub fn scan_metadata_files(&self) -> Vec<PathBuf> {
322 let mut metadata_files = Vec::new();
323
324 for path in &self.search_paths {
325 if let Ok(entries) = std::fs::read_dir(path) {
326 for entry in entries.flatten() {
327 let path = entry.path();
328 if path.is_file() {
329 if let Some(filename) = path.file_name() {
330 let filename = filename.to_string_lossy();
331 if filename == "plugin.json" || filename == "plugin.toml" {
332 metadata_files.push(path);
333 }
334 }
335 }
336 }
337 }
338 }
339
340 metadata_files
341 }
342
343 pub fn load_metadata_from_file(&self, _path: &Path) -> Result<PluginMetadata, PluginError> {
347 Err(PluginError::General(
349 "Metadata file loading not yet implemented".to_string(),
350 ))
351 }
352}
353
354impl Default for PluginDiscovery {
355 fn default() -> Self {
356 Self::new()
357 }
358}
359
360pub struct PluginCatalog {
364 entries: Vec<PluginRegistryEntry>,
365}
366
367impl PluginCatalog {
368 pub fn from_registry(registry: &PluginRegistry) -> Self {
370 Self {
371 entries: registry.list_all().into_iter().cloned().collect(),
372 }
373 }
374
375 pub fn entries(&self) -> &[PluginRegistryEntry] {
377 &self.entries
378 }
379
380 pub fn with_capability(self, capability: &str) -> Self {
382 let entries = self
383 .entries
384 .into_iter()
385 .filter(|entry| {
386 entry
387 .metadata
388 .capabilities
389 .contains(&capability.to_string())
390 })
391 .collect();
392
393 Self { entries }
394 }
395
396 pub fn loaded(self, loaded: bool) -> Self {
398 let entries = self
399 .entries
400 .into_iter()
401 .filter(|entry| entry.loaded == loaded)
402 .collect();
403
404 Self { entries }
405 }
406
407 pub fn with_tag(self, tag: &str) -> Self {
409 let entries = self
410 .entries
411 .into_iter()
412 .filter(|entry| entry.tags.contains(&tag.to_string()))
413 .collect();
414
415 Self { entries }
416 }
417
418 pub fn sort_by_name(mut self) -> Self {
420 self.entries
421 .sort_by(|a, b| a.metadata.name.cmp(&b.metadata.name));
422 self
423 }
424
425 pub fn sort_by_time(mut self) -> Self {
427 self.entries
428 .sort_by(|a, b| a.registered_at.cmp(&b.registered_at));
429 self
430 }
431
432 pub fn count(&self) -> usize {
434 self.entries.len()
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use crate::plugin::PluginBuilder;
442
443 #[test]
444 fn test_plugin_registry() {
445 let mut registry = PluginRegistry::new();
446 let metadata = PluginBuilder::new("test_plugin", "1.0.0")
447 .author("Test")
448 .description("Test plugin")
449 .capability("validation")
450 .build();
451
452 assert!(registry.register(metadata.clone(), None).is_ok());
453 assert!(registry.contains("test_plugin"));
454 assert_eq!(registry.list_all().len(), 1);
455 }
456
457 #[test]
458 fn test_plugin_registry_loaded_status() {
459 let mut registry = PluginRegistry::new();
460 let metadata = PluginBuilder::new("test_plugin", "1.0.0")
461 .author("Test")
462 .description("Test plugin")
463 .build();
464
465 registry.register(metadata, None).unwrap();
466 assert_eq!(registry.list_loaded().len(), 0);
467 assert_eq!(registry.list_unloaded().len(), 1);
468
469 registry.mark_loaded("test_plugin").unwrap();
470 assert_eq!(registry.list_loaded().len(), 1);
471 assert_eq!(registry.list_unloaded().len(), 0);
472 }
473
474 #[test]
475 fn test_plugin_registry_capabilities() {
476 let mut registry = PluginRegistry::new();
477 let metadata1 = PluginBuilder::new("plugin1", "1.0.0")
478 .capability("validation")
479 .build();
480 let metadata2 = PluginBuilder::new("plugin2", "1.0.0")
481 .capability("validation")
482 .capability("enrichment")
483 .build();
484
485 registry.register(metadata1, None).unwrap();
486 registry.register(metadata2, None).unwrap();
487
488 let validation_plugins = registry.find_by_capability("validation");
489 assert_eq!(validation_plugins.len(), 2);
490
491 let enrichment_plugins = registry.find_by_capability("enrichment");
492 assert_eq!(enrichment_plugins.len(), 1);
493 }
494
495 #[test]
496 fn test_plugin_catalog() {
497 let mut registry = PluginRegistry::new();
498 let metadata1 = PluginBuilder::new("plugin1", "1.0.0")
499 .capability("validation")
500 .build();
501 let metadata2 = PluginBuilder::new("plugin2", "1.0.0")
502 .capability("enrichment")
503 .build();
504
505 registry.register(metadata1, None).unwrap();
506 registry.register(metadata2, None).unwrap();
507 registry.mark_loaded("plugin1").unwrap();
508
509 let catalog = PluginCatalog::from_registry(®istry);
510 assert_eq!(catalog.count(), 2);
511
512 let loaded_catalog = catalog.loaded(true);
513 assert_eq!(loaded_catalog.count(), 1);
514 }
515}