//! Plugin registry for managing loaded plugins use std::path::PathBuf; use anyhow::{Result, anyhow}; use pinakes_plugin_api::{PluginManifest, PluginMetadata}; use rustc_hash::FxHashMap; 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: FxHashMap, } impl PluginRegistry { /// Create a new empty registry #[must_use] pub fn new() -> Self { Self { plugins: FxHashMap::default(), } } /// Register a new plugin /// /// # Errors /// /// Returns an error if a plugin with the same ID is already registered. 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 /// /// # Errors /// /// Returns an error if the plugin ID is not found. 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 #[must_use] 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 #[must_use] 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. #[must_use] pub fn is_enabled(&self, plugin_id: &str) -> Option { self.plugins.get(plugin_id).map(|p| p.enabled) } /// Enable a plugin /// /// # Errors /// /// Returns an error if the plugin ID is not found. 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 /// /// # Errors /// /// Returns an error if the plugin ID is not found. 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 #[must_use] pub fn list_all(&self) -> Vec<&RegisteredPlugin> { self.plugins.values().collect() } /// List all enabled plugins #[must_use] 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`") #[must_use] pub fn get_by_kind(&self, kind: &str) -> Vec<&RegisteredPlugin> { self .plugins .values() .filter(|p| p.manifest.plugin.kind.iter().any(|k| k == kind)) .collect() } /// Get count of registered plugins #[must_use] pub fn count(&self) -> usize { self.plugins.len() } /// Get count of enabled plugins #[must_use] 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 pinakes_plugin_api::{Capabilities, manifest::ManifestCapabilities}; use rustc_hash::FxHashMap; 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![], priority: 0, }, capabilities: ManifestCapabilities::default(), config: FxHashMap::default(), ui: Default::default(), }; 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).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); } }