use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::core::{Plugin, PluginError, PluginMetadata, PluginResult};
pub type PluginFactoryFn = fn() -> Box<dyn Plugin>;
#[derive(Debug, Clone)]
pub struct PluginFactory {
pub name: &'static str,
pub create: PluginFactoryFn,
pub metadata: fn() -> PluginMetadata,
pub version: &'static str,
pub description: &'static str,
}
impl PluginFactory {
pub const fn new(
name: &'static str,
create: PluginFactoryFn,
metadata: fn() -> PluginMetadata,
version: &'static str,
description: &'static str,
) -> Self {
Self {
name,
create,
metadata,
version,
description,
}
}
pub fn create_plugin(&self) -> Box<dyn Plugin> {
(self.create)()
}
pub fn get_metadata(&self) -> PluginMetadata {
(self.metadata)()
}
}
inventory::collect!(PluginFactory);
#[derive(Debug)]
pub struct PluginDiscovery {
factories: HashMap<String, PluginFactory>,
search_paths: Vec<PathBuf>,
discovery_cache: Arc<Mutex<Option<Vec<PluginFactory>>>>,
}
impl PluginDiscovery {
pub fn new() -> Self {
Self {
factories: HashMap::new(),
search_paths: Vec::new(),
discovery_cache: Arc::new(Mutex::new(None)),
}
}
pub fn add_search_path(&mut self, path: PathBuf) {
self.search_paths.push(path);
*self.discovery_cache.lock().unwrap() = None;
}
pub fn search_paths(&self) -> &[PathBuf] {
&self.search_paths
}
pub fn discover_plugins(&mut self) -> PluginResult<Vec<PluginFactory>> {
{
let cache = self.discovery_cache.lock().unwrap();
if let Some(ref cached) = *cache {
return Ok(cached.clone());
}
}
let mut discovered = Vec::new();
self.factories.clear();
for factory in inventory::iter::<PluginFactory> {
let factory_clone = factory.clone();
if let Err(e) = self.validate_factory(&factory_clone) {
eprintln!(
"Warning: Invalid plugin factory '{}': {}",
factory_clone.name, e
);
continue;
}
self.factories
.insert(factory_clone.name.to_string(), factory_clone.clone());
discovered.push(factory_clone);
}
*self.discovery_cache.lock().unwrap() = Some(discovered.clone());
Ok(discovered)
}
pub fn get_factory(&self, name: &str) -> Option<&PluginFactory> {
self.factories.get(name)
}
pub fn get_all_factories(&self) -> Vec<&PluginFactory> {
self.factories.values().collect()
}
pub fn create_plugin(&self, name: &str) -> PluginResult<Box<dyn Plugin>> {
let factory = self
.factories
.get(name)
.ok_or_else(|| PluginError::PluginNotFound(name.to_string()))?;
Ok(factory.create_plugin())
}
pub fn get_plugin_metadata(&self, name: &str) -> PluginResult<PluginMetadata> {
let factory = self
.factories
.get(name)
.ok_or_else(|| PluginError::PluginNotFound(name.to_string()))?;
Ok(factory.get_metadata())
}
pub fn list_plugin_names(&self) -> Vec<String> {
self.factories.keys().cloned().collect()
}
pub fn has_plugin(&self, name: &str) -> bool {
self.factories.contains_key(name)
}
pub fn clear_cache(&mut self) {
*self.discovery_cache.lock().unwrap() = None;
}
pub fn get_stats(&self) -> DiscoveryStats {
DiscoveryStats {
total_factories: self.factories.len(),
search_paths: self.search_paths.len(),
cached: self.discovery_cache.lock().unwrap().is_some(),
}
}
pub fn validate_factory(&self, factory: &PluginFactory) -> PluginResult<()> {
if factory.name.is_empty() {
return Err(PluginError::InvalidMetadata(
"Plugin name cannot be empty".to_string(),
));
}
if factory.version.is_empty() {
return Err(PluginError::InvalidMetadata(
"Plugin version cannot be empty".to_string(),
));
}
let _test_instance = (factory.create)();
let _test_metadata = (factory.metadata)();
Ok(())
}
pub fn auto_register_all<R>(&mut self, registry: &mut R) -> PluginResult<usize>
where
R: PluginRegistryTrait,
{
let factories = self.discover_plugins()?;
let mut registered_count = 0;
for factory in factories {
match factory.create_plugin() {
plugin => {
if let Err(e) = registry.register_plugin(plugin) {
eprintln!(
"Warning: Failed to register plugin '{}': {}",
factory.name, e
);
continue;
}
registered_count += 1;
}
}
}
Ok(registered_count)
}
}
impl Default for PluginDiscovery {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DiscoveryStats {
pub total_factories: usize,
pub search_paths: usize,
pub cached: bool,
}
pub trait PluginRegistryTrait {
fn register_plugin(&mut self, plugin: Box<dyn Plugin>) -> PluginResult<()>;
}
#[macro_export]
macro_rules! register_plugin {
($plugin_type:ty, $name:literal) => {
$crate::register_plugin!(
$plugin_type,
$name,
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_DESCRIPTION")
);
};
($plugin_type:ty, $name:literal, $version:literal, $description:literal) => {
inventory::submit! {
$crate::core::discovery::PluginFactory::new(
$name,
|| Box::new(<$plugin_type>::default()),
|| <$plugin_type>::default().metadata().clone(),
$version,
$description,
)
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{PluginContext, PluginMetadata, PluginOutput};
use async_trait::async_trait;
use serde_json::json;
#[derive(Default)]
struct TestPlugin {
metadata: PluginMetadata,
}
impl TestPlugin {
fn new() -> Self {
Self {
metadata: PluginMetadata::new("test-plugin", "1.0.0"),
}
}
}
#[async_trait]
impl Plugin for TestPlugin {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({"type": "object"})
}
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!({"test": true})))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
}
#[test]
fn test_plugin_factory_creation() {
let factory = PluginFactory::new(
"test",
|| Box::new(TestPlugin::new()),
|| TestPlugin::new().metadata().clone(),
"1.0.0",
"Test plugin",
);
assert_eq!(factory.name, "test");
assert_eq!(factory.version, "1.0.0");
assert_eq!(factory.description, "Test plugin");
let plugin = factory.create_plugin();
assert_eq!(plugin.metadata().name, "test-plugin");
}
#[test]
fn test_plugin_discovery_basic() {
let discovery = PluginDiscovery::new();
assert_eq!(discovery.search_paths().len(), 0);
assert_eq!(discovery.get_all_factories().len(), 0);
assert!(!discovery.has_plugin("nonexistent"));
}
#[test]
fn test_plugin_discovery_search_paths() {
let mut discovery = PluginDiscovery::new();
discovery.add_search_path(PathBuf::from("/test/path1"));
discovery.add_search_path(PathBuf::from("/test/path2"));
assert_eq!(discovery.search_paths().len(), 2);
assert_eq!(discovery.search_paths()[0], PathBuf::from("/test/path1"));
assert_eq!(discovery.search_paths()[1], PathBuf::from("/test/path2"));
}
#[test]
fn test_discovery_stats() {
let discovery = PluginDiscovery::new();
let stats = discovery.get_stats();
assert_eq!(stats.total_factories, 0);
assert_eq!(stats.search_paths, 0);
assert!(!stats.cached);
}
#[test]
fn test_factory_validation() {
let discovery = PluginDiscovery::new();
let valid_factory = PluginFactory::new(
"valid",
|| Box::new(TestPlugin::new()),
|| TestPlugin::new().metadata().clone(),
"1.0.0",
"Valid plugin",
);
assert!(discovery.validate_factory(&valid_factory).is_ok());
let invalid_factory = PluginFactory::new(
"",
|| Box::new(TestPlugin::new()),
|| TestPlugin::new().metadata().clone(),
"1.0.0",
"Invalid plugin",
);
assert!(discovery.validate_factory(&invalid_factory).is_err());
let invalid_factory2 = PluginFactory::new(
"invalid",
|| Box::new(TestPlugin::new()),
|| TestPlugin::new().metadata().clone(),
"",
"Invalid plugin",
);
assert!(discovery.validate_factory(&invalid_factory2).is_err());
}
}