use petgraph::Graph;
use std::collections::HashMap;
use crate::core::discovery::{PluginDiscovery, PluginRegistryTrait};
use crate::core::{Plugin, PluginError, PluginResult};
pub struct PluginRegistry {
plugins: HashMap<String, Box<dyn Plugin>>,
dependency_graph: Graph<String, ()>,
name_to_index: HashMap<String, petgraph::graph::NodeIndex>,
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
dependency_graph: Graph::new(),
name_to_index: HashMap::new(),
}
}
pub fn register<P: Plugin + 'static>(&mut self, plugin: P) -> PluginResult<()> {
let metadata = plugin.metadata();
let plugin_name = metadata.name.clone();
if self.plugins.contains_key(&plugin_name) {
return Err(PluginError::InvalidMetadata(format!(
"Plugin '{plugin_name}' is already registered"
)));
}
let node_index = self.dependency_graph.add_node(plugin_name.clone());
self.name_to_index.insert(plugin_name.clone(), node_index);
self.plugins.insert(plugin_name.clone(), Box::new(plugin));
self.rebuild_dependency_edges();
Ok(())
}
fn rebuild_dependency_edges(&mut self) {
self.dependency_graph.clear_edges();
for (plugin_name, plugin) in &self.plugins {
let plugin_node = self.name_to_index[plugin_name];
let metadata = plugin.metadata();
for dependency in &metadata.dependencies {
if let Some(&dep_node) = self.name_to_index.get(dependency) {
self.dependency_graph.add_edge(dep_node, plugin_node, ());
}
}
}
}
pub fn get_plugin(&self, name: &str) -> Option<&dyn Plugin> {
self.plugins.get(name).map(|p| p.as_ref())
}
pub fn take_plugin(&mut self, name: &str) -> Option<Box<dyn Plugin>> {
self.plugins.remove(name)
}
pub fn put_plugin(&mut self, plugin: Box<dyn Plugin>) -> PluginResult<()> {
let name = plugin.metadata().name.clone();
if self.plugins.contains_key(&name) {
return Err(PluginError::InvalidMetadata(format!(
"Plugin '{name}' already exists"
)));
}
self.plugins.insert(name, plugin);
Ok(())
}
pub fn plugin_names(&self) -> Vec<String> {
self.plugins.keys().cloned().collect()
}
pub fn has_plugin(&self, name: &str) -> bool {
self.plugins.contains_key(name)
}
pub fn validate_dependencies(&self) -> PluginResult<()> {
for (plugin_name, plugin) in &self.plugins {
let metadata = plugin.metadata();
for dependency in &metadata.dependencies {
if !self.has_plugin(dependency) {
return Err(PluginError::DependencyNotSatisfied {
plugin: plugin_name.clone(),
dependency: dependency.clone(),
});
}
}
}
Ok(())
}
pub fn resolve_execution_order(&self) -> PluginResult<Vec<Vec<String>>> {
self.validate_dependencies()?;
if self.plugins.is_empty() {
return Ok(vec![]);
}
let mut batches = Vec::new();
let mut remaining_plugins: std::collections::HashSet<_> =
self.plugins.keys().cloned().collect();
while !remaining_plugins.is_empty() {
let mut current_batch = Vec::new();
for plugin_name in &remaining_plugins {
let plugin = self.plugins.get(plugin_name).unwrap();
let metadata = plugin.metadata();
let dependencies_satisfied = metadata
.dependencies
.iter()
.all(|dep| !remaining_plugins.contains(dep));
if dependencies_satisfied {
current_batch.push(plugin_name.clone());
}
}
if current_batch.is_empty() {
return Err(PluginError::ConfigurationError(
"Unable to resolve execution order - possible circular dependency".to_string(),
));
}
for plugin_name in ¤t_batch {
remaining_plugins.remove(plugin_name);
}
batches.push(current_batch);
}
Ok(batches)
}
pub fn len(&self) -> usize {
self.plugins.len()
}
pub fn is_empty(&self) -> bool {
self.plugins.is_empty()
}
pub fn auto_discover(&mut self) -> PluginResult<usize> {
use crate::core::discovery::PluginDiscovery;
let mut discovery = PluginDiscovery::new();
discovery.auto_register_all(self)
}
pub fn register_from_discovery(
&mut self,
discovery: &PluginDiscovery,
plugin_names: &[String],
) -> PluginResult<usize> {
let mut registered_count = 0;
for name in plugin_names {
match discovery.create_plugin(name) {
Ok(plugin) => {
if let Err(e) = self.register_plugin(plugin) {
eprintln!("Warning: Failed to register plugin '{name}': {e}");
continue;
}
registered_count += 1;
}
Err(e) => {
eprintln!("Warning: Failed to create plugin '{name}': {e}");
continue;
}
}
}
Ok(registered_count)
}
pub fn register_all_from_discovery(
&mut self,
discovery: &mut PluginDiscovery,
) -> PluginResult<usize> {
discovery.auto_register_all(self)
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new()
}
}
impl PluginRegistryTrait for PluginRegistry {
fn register_plugin(&mut self, plugin: Box<dyn Plugin>) -> PluginResult<()> {
let metadata = plugin.metadata();
let plugin_name = metadata.name.clone();
if self.plugins.contains_key(&plugin_name) {
return Err(PluginError::InvalidMetadata(format!(
"Plugin '{plugin_name}' is already registered"
)));
}
let node_index = self.dependency_graph.add_node(plugin_name.clone());
self.name_to_index.insert(plugin_name.clone(), node_index);
self.plugins.insert(plugin_name, plugin);
self.rebuild_dependency_edges();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{PluginContext, PluginMetadata, PluginOutput};
use async_trait::async_trait;
use serde_json::json;
struct TestPluginA {
metadata: PluginMetadata,
}
impl TestPluginA {
fn new() -> Self {
let mut metadata = PluginMetadata::new("plugin-a", "1.0.0");
metadata.dependencies = vec![];
Self { metadata }
}
}
#[async_trait]
impl Plugin for TestPluginA {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({})
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
Ok(())
}
async fn execute(&mut self, _context: &mut PluginContext) -> PluginResult<PluginOutput> {
Ok(PluginOutput::success(json!({"plugin": "a"})))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
}
struct TestPluginB {
metadata: PluginMetadata,
}
impl TestPluginB {
fn new() -> Self {
let mut metadata = PluginMetadata::new("plugin-b", "1.0.0");
metadata.dependencies = vec!["plugin-a".to_string()];
Self { metadata }
}
}
#[async_trait]
impl Plugin for TestPluginB {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({})
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
Ok(())
}
async fn execute(&mut self, _context: &mut PluginContext) -> PluginResult<PluginOutput> {
Ok(PluginOutput::success(json!({"plugin": "b"})))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
}
#[test]
fn test_registry_creation() {
let registry = PluginRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
}
#[test]
fn test_plugin_registration() {
let mut registry = PluginRegistry::new();
let plugin = TestPluginA::new();
assert!(registry.register(plugin).is_ok());
assert_eq!(registry.len(), 1);
assert!(registry.has_plugin("plugin-a"));
assert!(!registry.has_plugin("plugin-b"));
}
#[test]
fn test_duplicate_plugin_registration() {
let mut registry = PluginRegistry::new();
let plugin1 = TestPluginA::new();
let plugin2 = TestPluginA::new();
assert!(registry.register(plugin1).is_ok());
let result = registry.register(plugin2);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
PluginError::InvalidMetadata(_)
));
}
#[test]
fn test_plugin_retrieval() {
let mut registry = PluginRegistry::new();
let plugin = TestPluginA::new();
registry.register(plugin).unwrap();
let retrieved = registry.get_plugin("plugin-a");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().metadata().name, "plugin-a");
let not_found = registry.get_plugin("nonexistent");
assert!(not_found.is_none());
}
#[test]
fn test_dependency_validation_success() {
let mut registry = PluginRegistry::new();
let plugin_a = TestPluginA::new();
let plugin_b = TestPluginB::new();
registry.register(plugin_a).unwrap();
registry.register(plugin_b).unwrap();
assert!(registry.validate_dependencies().is_ok());
}
#[test]
fn test_dependency_validation_failure() {
let mut registry = PluginRegistry::new();
let plugin_b = TestPluginB::new();
registry.register(plugin_b).unwrap();
let result = registry.validate_dependencies();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
PluginError::DependencyNotSatisfied { .. }
));
}
#[test]
fn test_execution_order_resolution() {
let mut registry = PluginRegistry::new();
let plugin_a = TestPluginA::new();
let plugin_b = TestPluginB::new();
registry.register(plugin_a).unwrap();
registry.register(plugin_b).unwrap();
let execution_order = registry.resolve_execution_order().unwrap();
assert_eq!(execution_order.len(), 2);
assert_eq!(execution_order[0], vec!["plugin-a"]);
assert_eq!(execution_order[1], vec!["plugin-b"]);
}
#[test]
fn test_plugin_names() {
let mut registry = PluginRegistry::new();
let plugin_a = TestPluginA::new();
let plugin_b = TestPluginB::new();
registry.register(plugin_a).unwrap();
registry.register(plugin_b).unwrap();
let mut names = registry.plugin_names();
names.sort();
assert_eq!(names, vec!["plugin-a", "plugin-b"]);
}
}