//! Plugin registry for managing loaded plugins use std::{collections::HashMap, path::PathBuf}; use anyhow::{Result, anyhow}; use pinakes_plugin_api::{PluginManifest, PluginMetadata}; use super::runtime::WasmPlugin; /// A registered plugin with its metadata and runtime state #[derive(Clone)] pub struct RegisteredPlugin { pub id: String, pub metadata: PluginMetadata, pub wasm_plugin: WasmPlugin, pub manifest: PluginManifest, pub manifest_path: Option, pub enabled: bool, } /// Plugin registry maintains the state of all loaded plugins pub struct PluginRegistry { /// Map of plugin ID to registered plugin plugins: HashMap, } impl PluginRegistry { /// Create a new empty registry pub fn new() -> Self { Self { plugins: HashMap::new(), } } /// Register a new plugin pub fn register(&mut self, plugin: RegisteredPlugin) -> Result<()> { if self.plugins.contains_key(&plugin.id) { return Err(anyhow!("Plugin already registered: {}", plugin.id)); } self.plugins.insert(plugin.id.clone(), plugin); Ok(()) } /// Unregister a plugin by ID pub fn unregister(&mut self, plugin_id: &str) -> Result<()> { self .plugins .remove(plugin_id) .ok_or_else(|| anyhow!("Plugin not found: {}", plugin_id))?; Ok(()) } /// Get a plugin by ID pub fn get(&self, plugin_id: &str) -> Option<&RegisteredPlugin> { self.plugins.get(plugin_id) } /// Get a mutable reference to a plugin by ID pub fn get_mut(&mut self, plugin_id: &str) -> Option<&mut RegisteredPlugin> { self.plugins.get_mut(plugin_id) } /// Check if a plugin is loaded pub fn is_loaded(&self, plugin_id: &str) -> bool { self.plugins.contains_key(plugin_id) } /// Check if a plugin is enabled. Returns `None` if the plugin is not found. pub fn is_enabled(&self, plugin_id: &str) -> Option { self.plugins.get(plugin_id).map(|p| p.enabled) } /// Enable a plugin pub fn enable(&mut self, plugin_id: &str) -> Result<()> { let plugin = self .plugins .get_mut(plugin_id) .ok_or_else(|| anyhow!("Plugin not found: {}", plugin_id))?; plugin.enabled = true; Ok(()) } /// Disable a plugin pub fn disable(&mut self, plugin_id: &str) -> Result<()> { let plugin = self .plugins .get_mut(plugin_id) .ok_or_else(|| anyhow!("Plugin not found: {}", plugin_id))?; plugin.enabled = false; Ok(()) } /// List all registered plugins pub fn list_all(&self) -> Vec<&RegisteredPlugin> { self.plugins.values().collect() } /// List all enabled plugins pub fn list_enabled(&self) -> Vec<&RegisteredPlugin> { self.plugins.values().filter(|p| p.enabled).collect() } /// Get plugins by kind (e.g., "media_type", "metadata_extractor") pub fn get_by_kind(&self, kind: &str) -> Vec<&RegisteredPlugin> { self .plugins .values() .filter(|p| p.manifest.plugin.kind.contains(&kind.to_string())) .collect() } /// Get count of registered plugins pub fn count(&self) -> usize { self.plugins.len() } /// Get count of enabled plugins pub fn count_enabled(&self) -> usize { self.plugins.values().filter(|p| p.enabled).count() } } impl Default for PluginRegistry { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use std::collections::HashMap; use pinakes_plugin_api::Capabilities; use super::*; fn create_test_plugin(id: &str, kind: Vec) -> RegisteredPlugin { let manifest = PluginManifest { plugin: pinakes_plugin_api::manifest::PluginInfo { name: id.to_string(), version: "1.0.0".to_string(), api_version: "1.0".to_string(), author: Some("Test".to_string()), description: Some("Test plugin".to_string()), homepage: None, license: None, kind, binary: pinakes_plugin_api::manifest::PluginBinary { wasm: "test.wasm".to_string(), entrypoint: None, }, dependencies: vec![], }, capabilities: Default::default(), config: HashMap::new(), }; RegisteredPlugin { id: id.to_string(), metadata: PluginMetadata { id: id.to_string(), name: id.to_string(), version: "1.0.0".to_string(), author: "Test".to_string(), description: "Test plugin".to_string(), api_version: "1.0".to_string(), capabilities_required: Capabilities::default(), }, wasm_plugin: WasmPlugin::default(), manifest, manifest_path: None, enabled: true, } } #[test] fn test_registry_register_and_get() { let mut registry = PluginRegistry::new(); let plugin = create_test_plugin("test-plugin", vec!["media_type".to_string()]); registry.register(plugin.clone()).unwrap(); assert!(registry.is_loaded("test-plugin")); assert!(registry.get("test-plugin").is_some()); } #[test] fn test_registry_duplicate_register() { let mut registry = PluginRegistry::new(); let plugin = create_test_plugin("test-plugin", vec!["media_type".to_string()]); registry.register(plugin.clone()).unwrap(); let result = registry.register(plugin); assert!(result.is_err()); } #[test] fn test_registry_unregister() { let mut registry = PluginRegistry::new(); let plugin = create_test_plugin("test-plugin", vec!["media_type".to_string()]); registry.register(plugin).unwrap(); registry.unregister("test-plugin").unwrap(); assert!(!registry.is_loaded("test-plugin")); } #[test] fn test_registry_enable_disable() { let mut registry = PluginRegistry::new(); let plugin = create_test_plugin("test-plugin", vec!["media_type".to_string()]); registry.register(plugin).unwrap(); assert_eq!(registry.is_enabled("test-plugin"), Some(true)); registry.disable("test-plugin").unwrap(); assert_eq!(registry.is_enabled("test-plugin"), Some(false)); registry.enable("test-plugin").unwrap(); assert_eq!(registry.is_enabled("test-plugin"), Some(true)); assert_eq!(registry.is_enabled("nonexistent"), None); } #[test] fn test_registry_get_by_kind() { let mut registry = PluginRegistry::new(); registry .register(create_test_plugin("plugin1", vec![ "media_type".to_string(), ])) .unwrap(); registry .register(create_test_plugin("plugin2", vec![ "metadata_extractor".to_string(), ])) .unwrap(); registry .register(create_test_plugin("plugin3", vec![ "media_type".to_string(), ])) .unwrap(); let media_type_plugins = registry.get_by_kind("media_type"); assert_eq!(media_type_plugins.len(), 2); let extractor_plugins = registry.get_by_kind("metadata_extractor"); assert_eq!(extractor_plugins.len(), 1); } #[test] fn test_registry_counts() { let mut registry = PluginRegistry::new(); registry .register(create_test_plugin("plugin1", vec![ "media_type".to_string(), ])) .unwrap(); registry .register(create_test_plugin("plugin2", vec![ "media_type".to_string(), ])) .unwrap(); assert_eq!(registry.count(), 2); assert_eq!(registry.count_enabled(), 2); registry.disable("plugin1").unwrap(); assert_eq!(registry.count(), 2); assert_eq!(registry.count_enabled(), 1); } }