use std::collections::HashMap;
use std::sync::Arc;
use dashmap::DashMap;
use crate::error::{Error, Result};
use crate::lifecycle::{LifecycleHooks, LifecycleState};
use crate::plugin::{Plugin, PluginHandle, PluginInfo};
#[derive(Debug, Clone)]
pub struct RegistryConfig {
pub max_plugins: usize,
pub allow_overwrite: bool,
pub auto_unload_stopped: bool,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
max_plugins: 100,
allow_overwrite: false,
auto_unload_stopped: false,
}
}
}
impl RegistryConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_plugins(mut self, max: usize) -> Self {
self.max_plugins = max;
self
}
pub fn with_allow_overwrite(mut self, allow: bool) -> Self {
self.allow_overwrite = allow;
self
}
pub fn with_auto_unload_stopped(mut self, auto: bool) -> Self {
self.auto_unload_stopped = auto;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct RegistryStats {
pub total: usize,
pub running: usize,
pub stopped: usize,
pub error: usize,
pub unloaded: usize,
}
pub struct PluginRegistry {
config: RegistryConfig,
plugins: DashMap<String, PluginHandle>,
hooks: Arc<LifecycleHooks>,
}
impl PluginRegistry {
pub fn new(config: RegistryConfig) -> Self {
Self {
config,
plugins: DashMap::new(),
hooks: Arc::new(LifecycleHooks::new()),
}
}
pub fn default_config() -> Self {
Self::new(RegistryConfig::default())
}
pub fn config(&self) -> &RegistryConfig {
&self.config
}
pub fn register(&self, plugin: PluginHandle) -> Result<()> {
let name = plugin.name();
if self.plugins.len() >= self.config.max_plugins {
return Err(Error::Registry(format!(
"registry full: max {} plugins",
self.config.max_plugins
)));
}
if self.plugins.contains_key(&name) {
if !self.config.allow_overwrite {
return Err(Error::PluginAlreadyLoaded(name));
}
if let Some((_, existing)) = self.plugins.remove(&name) {
let _ = existing.inner().unload();
}
}
self.plugins.insert(name.clone(), plugin);
self.hooks.emit_created(&name);
Ok(())
}
pub fn unregister(&self, name: &str) -> Result<PluginHandle> {
let (_, plugin) = self
.plugins
.remove(name)
.ok_or_else(|| Error::plugin_not_found(name))?;
let _ = plugin.inner().unload();
self.hooks.emit_unloaded(name);
Ok(plugin)
}
pub fn get(&self, name: &str) -> Option<PluginHandle> {
self.plugins.get(name).map(|r| r.clone())
}
pub fn contains(&self, name: &str) -> bool {
self.plugins.contains_key(name)
}
pub fn names(&self) -> Vec<String> {
self.plugins.iter().map(|r| r.key().clone()).collect()
}
pub fn all(&self) -> Vec<PluginHandle> {
self.plugins.iter().map(|r| r.value().clone()).collect()
}
pub fn by_state(&self, state: LifecycleState) -> Vec<PluginHandle> {
self.plugins
.iter()
.filter(|r| r.state() == state)
.map(|r| r.value().clone())
.collect()
}
pub fn running(&self) -> Vec<PluginHandle> {
self.by_state(LifecycleState::Running)
}
pub fn len(&self) -> usize {
self.plugins.len()
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
pub fn stats(&self) -> RegistryStats {
let mut stats = RegistryStats::default();
stats.total = self.plugins.len();
for entry in self.plugins.iter() {
match entry.state() {
LifecycleState::Running => stats.running += 1,
LifecycleState::Stopped => stats.stopped += 1,
LifecycleState::Error => stats.error += 1,
LifecycleState::Unloaded => stats.unloaded += 1,
_ => {}
}
}
stats
}
pub fn info(&self) -> Vec<PluginInfo> {
self.plugins.iter().map(|r| r.info()).collect()
}
pub fn start_all(&self) -> Vec<Result<()>> {
self.plugins
.iter()
.filter(|r| r.state() == LifecycleState::Initialized)
.map(|r| {
let plugin = r.value();
plugin.inner().start()
})
.collect()
}
pub fn stop_all(&self) -> Vec<Result<()>> {
self.plugins
.iter()
.filter(|r| r.state() == LifecycleState::Running)
.map(|r| {
let plugin = r.value();
plugin.inner().stop()
})
.collect()
}
pub fn unload_all(&self) {
for entry in self.plugins.iter() {
let _ = entry.value().inner().unload();
}
self.plugins.clear();
}
pub fn reload(&self, name: &str) -> Result<()> {
let plugin = self
.get(name)
.ok_or_else(|| Error::plugin_not_found(name))?;
plugin.inner().reload()?;
let info = plugin.info();
self.hooks.emit_reloaded(name, info.reload_count);
Ok(())
}
pub fn reload_all(&self) -> Vec<Result<()>> {
self.plugins
.iter()
.map(|r| {
let name = r.key().clone();
self.reload(&name)
})
.collect()
}
pub fn find_by_tag(&self, tag: &str) -> Vec<PluginHandle> {
self.plugins
.iter()
.filter(|r| {
r.value()
.inner()
.manifest()
.tags
.contains(&tag.to_string())
})
.map(|r| r.value().clone())
.collect()
}
pub fn find_by_capability(&self, cap: &str) -> Vec<PluginHandle> {
self.plugins
.iter()
.filter(|r| r.value().inner().requires_capability(cap))
.map(|r| r.value().clone())
.collect()
}
pub fn cleanup(&self) -> usize {
let to_remove: Vec<String> = self
.plugins
.iter()
.filter(|r| {
let state = r.state();
state == LifecycleState::Unloaded
|| (self.config.auto_unload_stopped && state == LifecycleState::Stopped)
})
.map(|r| r.key().clone())
.collect();
let count = to_remove.len();
for name in to_remove {
self.plugins.remove(&name);
}
count
}
}
impl std::fmt::Debug for PluginRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginRegistry")
.field("config", &self.config)
.field("plugin_count", &self.plugins.len())
.finish()
}
}
impl Drop for PluginRegistry {
fn drop(&mut self) {
for entry in self.plugins.iter() {
let _ = entry.value().inner().unload();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::ManifestBuilder;
fn create_test_plugin(name: &str) -> PluginHandle {
let manifest = ManifestBuilder::new(name, "1.0.0")
.source("test.fsx")
.build_unchecked();
PluginHandle::new(Plugin::new(manifest))
}
#[test]
fn test_registry_creation() {
let registry = PluginRegistry::default_config();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
}
#[test]
fn test_register_plugin() {
let registry = PluginRegistry::default_config();
let plugin = create_test_plugin("test-plugin");
registry.register(plugin).unwrap();
assert!(registry.contains("test-plugin"));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_register_duplicate() {
let registry = PluginRegistry::default_config();
let plugin1 = create_test_plugin("test-plugin");
let plugin2 = create_test_plugin("test-plugin");
registry.register(plugin1).unwrap();
let result = registry.register(plugin2);
assert!(matches!(result, Err(Error::PluginAlreadyLoaded(_))));
}
#[test]
fn test_register_duplicate_with_overwrite() {
let config = RegistryConfig::new().with_allow_overwrite(true);
let registry = PluginRegistry::new(config);
let plugin1 = create_test_plugin("test-plugin");
let id1 = plugin1.id();
let plugin2 = create_test_plugin("test-plugin");
let id2 = plugin2.id();
registry.register(plugin1).unwrap();
registry.register(plugin2).unwrap();
let plugin = registry.get("test-plugin").unwrap();
assert_eq!(plugin.id(), id2);
assert_ne!(plugin.id(), id1);
}
#[test]
fn test_unregister_plugin() {
let registry = PluginRegistry::default_config();
let plugin = create_test_plugin("test-plugin");
registry.register(plugin).unwrap();
assert!(registry.contains("test-plugin"));
registry.unregister("test-plugin").unwrap();
assert!(!registry.contains("test-plugin"));
}
#[test]
fn test_unregister_nonexistent() {
let registry = PluginRegistry::default_config();
let result = registry.unregister("nonexistent");
assert!(matches!(result, Err(Error::PluginNotFound(_))));
}
#[test]
fn test_get_all_plugins() {
let registry = PluginRegistry::default_config();
registry.register(create_test_plugin("plugin-1")).unwrap();
registry.register(create_test_plugin("plugin-2")).unwrap();
registry.register(create_test_plugin("plugin-3")).unwrap();
let all = registry.all();
assert_eq!(all.len(), 3);
let names = registry.names();
assert!(names.contains(&"plugin-1".to_string()));
assert!(names.contains(&"plugin-2".to_string()));
assert!(names.contains(&"plugin-3".to_string()));
}
#[test]
fn test_registry_stats() {
let registry = PluginRegistry::default_config();
registry.register(create_test_plugin("plugin-1")).unwrap();
registry.register(create_test_plugin("plugin-2")).unwrap();
let stats = registry.stats();
assert_eq!(stats.total, 2);
}
#[test]
fn test_max_plugins() {
let config = RegistryConfig::new().with_max_plugins(2);
let registry = PluginRegistry::new(config);
registry.register(create_test_plugin("plugin-1")).unwrap();
registry.register(create_test_plugin("plugin-2")).unwrap();
let result = registry.register(create_test_plugin("plugin-3"));
assert!(matches!(result, Err(Error::Registry(_))));
}
}