mergegate/crates/mergegate-core/src/plugin.rs
2026-04-10 15:12:08 +09:00

418 lines
11 KiB
Rust

//! Plugin system for Miyabi
//!
//! Provides a flexible plugin architecture for extending Miyabi functionality.
//!
//! # Example
//!
//! ```rust
//! use mergegate_core::plugin::{Plugin, PluginManager, PluginMetadata, PluginContext, PluginResult};
//! use anyhow::Result;
//!
//! struct MyPlugin;
//!
//! impl Plugin for MyPlugin {
//! fn metadata(&self) -> PluginMetadata {
//! PluginMetadata {
//! name: "my-plugin".to_string(),
//! version: "1.0.0".to_string(),
//! description: Some("My custom plugin".to_string()),
//! author: Some("Miyabi Team".to_string()),
//! }
//! }
//!
//! fn init(&mut self) -> Result<()> {
//! println!("Plugin initialized!");
//! Ok(())
//! }
//!
//! fn execute(&self, context: &PluginContext) -> Result<PluginResult> {
//! Ok(PluginResult {
//! success: true,
//! message: Some("Plugin executed successfully".to_string()),
//! data: None,
//! })
//! }
//! }
//! ```
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
/// Plugin metadata information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PluginMetadata {
/// Plugin name (unique identifier)
pub name: String,
/// Semantic version
pub version: String,
/// Optional description
pub description: Option<String>,
/// Optional author
pub author: Option<String>,
}
/// Plugin execution context
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PluginContext {
/// Plugin parameters
#[serde(default)]
pub params: HashMap<String, serde_json::Value>,
/// Working directory
pub working_dir: Option<String>,
/// Environment variables
#[serde(default)]
pub env: HashMap<String, String>,
}
/// Plugin execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginResult {
/// Execution success status
pub success: bool,
/// Optional result message
pub message: Option<String>,
/// Optional result data
pub data: Option<serde_json::Value>,
}
/// Core plugin trait
///
/// All plugins must implement this trait to be registered with the PluginManager.
pub trait Plugin: Send + Sync {
/// Returns plugin metadata
fn metadata(&self) -> PluginMetadata;
/// Initializes the plugin
///
/// Called once when the plugin is registered.
fn init(&mut self) -> Result<()>;
/// Executes the plugin with the given context
fn execute(&self, context: &PluginContext) -> Result<PluginResult>;
/// Cleans up plugin resources
///
/// Called when the plugin is unregistered or the manager is dropped.
fn shutdown(&mut self) -> Result<()> {
Ok(())
}
}
/// Plugin lifecycle state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginState {
/// Plugin is registered but not initialized
Registered,
/// Plugin is initialized and ready
Initialized,
/// Plugin encountered an error
Error,
/// Plugin is shut down
Shutdown,
}
/// Internal plugin wrapper
struct PluginEntry {
plugin: Box<dyn Plugin>,
state: PluginState,
}
/// Plugin manager
///
/// Manages plugin registration, initialization, and execution.
#[derive(Clone)]
pub struct PluginManager {
plugins: Arc<RwLock<HashMap<String, PluginEntry>>>,
}
impl PluginManager {
/// Creates a new plugin manager
pub fn new() -> Self {
Self {
plugins: Arc::new(RwLock::new(HashMap::new())),
}
}
/// Registers a plugin
///
/// # Errors
///
/// Returns an error if:
/// - A plugin with the same name already exists
/// - Plugin initialization fails
pub fn register(&self, mut plugin: Box<dyn Plugin>) -> Result<()> {
let metadata = plugin.metadata();
let name = metadata.name.clone();
// Check if plugin already exists
{
let plugins = self.plugins.read().unwrap();
if plugins.contains_key(&name) {
return Err(anyhow!("Plugin '{}' is already registered", name));
}
}
// Initialize plugin
plugin.init()?;
// Store plugin
let mut plugins = self.plugins.write().unwrap();
plugins.insert(
name.clone(),
PluginEntry {
plugin,
state: PluginState::Initialized,
},
);
Ok(())
}
/// Unregisters a plugin
///
/// # Errors
///
/// Returns an error if the plugin doesn't exist
pub fn unregister(&self, name: &str) -> Result<()> {
let mut plugins = self.plugins.write().unwrap();
let mut entry = plugins
.remove(name)
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
entry.plugin.shutdown()?;
entry.state = PluginState::Shutdown;
Ok(())
}
/// Executes a plugin with the given context
///
/// # Errors
///
/// Returns an error if:
/// - The plugin doesn't exist
/// - The plugin is not initialized
/// - Plugin execution fails
pub fn execute(&self, name: &str, context: &PluginContext) -> Result<PluginResult> {
let plugins = self.plugins.read().unwrap();
let entry = plugins
.get(name)
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))?;
if entry.state != PluginState::Initialized {
return Err(anyhow!(
"Plugin '{}' is not initialized (state: {:?})",
name,
entry.state
));
}
entry.plugin.execute(context)
}
/// Lists all registered plugins
pub fn list_plugins(&self) -> Vec<PluginMetadata> {
let plugins = self.plugins.read().unwrap();
plugins
.values()
.map(|entry| entry.plugin.metadata())
.collect()
}
/// Gets plugin metadata
///
/// # Errors
///
/// Returns an error if the plugin doesn't exist
pub fn get_metadata(&self, name: &str) -> Result<PluginMetadata> {
let plugins = self.plugins.read().unwrap();
plugins
.get(name)
.map(|entry| entry.plugin.metadata())
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))
}
/// Gets plugin state
///
/// # Errors
///
/// Returns an error if the plugin doesn't exist
pub fn get_state(&self, name: &str) -> Result<PluginState> {
let plugins = self.plugins.read().unwrap();
plugins
.get(name)
.map(|entry| entry.state)
.ok_or_else(|| anyhow!("Plugin '{}' not found", name))
}
/// Checks if a plugin exists
pub fn has_plugin(&self, name: &str) -> bool {
let plugins = self.plugins.read().unwrap();
plugins.contains_key(name)
}
/// Gets the number of registered plugins
pub fn count(&self) -> usize {
let plugins = self.plugins.read().unwrap();
plugins.len()
}
}
impl Default for PluginManager {
fn default() -> Self {
Self::new()
}
}
impl Drop for PluginManager {
fn drop(&mut self) {
// Shutdown all plugins
if let Ok(mut plugins) = self.plugins.write() {
for (name, entry) in plugins.iter_mut() {
if let Err(e) = entry.plugin.shutdown() {
eprintln!("Error shutting down plugin '{}': {}", name, e);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestPlugin {
name: String,
initialized: bool,
}
impl TestPlugin {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
initialized: false,
}
}
}
impl Plugin for TestPlugin {
fn metadata(&self) -> PluginMetadata {
PluginMetadata {
name: self.name.clone(),
version: "1.0.0".to_string(),
description: Some("Test plugin".to_string()),
author: Some("Test Author".to_string()),
}
}
fn init(&mut self) -> Result<()> {
self.initialized = true;
Ok(())
}
fn execute(&self, _context: &PluginContext) -> Result<PluginResult> {
Ok(PluginResult {
success: true,
message: Some(format!("Plugin '{}' executed", self.name)),
data: None,
})
}
}
#[test]
fn test_register_plugin() {
let manager = PluginManager::new();
let plugin = Box::new(TestPlugin::new("test-plugin"));
assert!(manager.register(plugin).is_ok());
assert_eq!(manager.count(), 1);
assert!(manager.has_plugin("test-plugin"));
}
#[test]
fn test_duplicate_registration() {
let manager = PluginManager::new();
let plugin1 = Box::new(TestPlugin::new("test-plugin"));
let plugin2 = Box::new(TestPlugin::new("test-plugin"));
assert!(manager.register(plugin1).is_ok());
assert!(manager.register(plugin2).is_err());
}
#[test]
fn test_execute_plugin() {
let manager = PluginManager::new();
let plugin = Box::new(TestPlugin::new("test-plugin"));
manager.register(plugin).unwrap();
let context = PluginContext::default();
let result = manager.execute("test-plugin", &context).unwrap();
assert!(result.success);
assert!(result.message.is_some());
}
#[test]
fn test_unregister_plugin() {
let manager = PluginManager::new();
let plugin = Box::new(TestPlugin::new("test-plugin"));
manager.register(plugin).unwrap();
assert_eq!(manager.count(), 1);
manager.unregister("test-plugin").unwrap();
assert_eq!(manager.count(), 0);
}
#[test]
fn test_list_plugins() {
let manager = PluginManager::new();
let plugin1 = Box::new(TestPlugin::new("plugin-1"));
let plugin2 = Box::new(TestPlugin::new("plugin-2"));
manager.register(plugin1).unwrap();
manager.register(plugin2).unwrap();
let plugins = manager.list_plugins();
assert_eq!(plugins.len(), 2);
}
#[test]
fn test_get_metadata() {
let manager = PluginManager::new();
let plugin = Box::new(TestPlugin::new("test-plugin"));
manager.register(plugin).unwrap();
let metadata = manager.get_metadata("test-plugin").unwrap();
assert_eq!(metadata.name, "test-plugin");
assert_eq!(metadata.version, "1.0.0");
}
#[test]
fn test_get_state() {
let manager = PluginManager::new();
let plugin = Box::new(TestPlugin::new("test-plugin"));
manager.register(plugin).unwrap();
let state = manager.get_state("test-plugin").unwrap();
assert_eq!(state, PluginState::Initialized);
}
#[test]
fn test_plugin_not_found() {
let manager = PluginManager::new();
let context = PluginContext::default();
assert!(manager.execute("nonexistent", &context).is_err());
assert!(manager.get_metadata("nonexistent").is_err());
assert!(manager.get_state("nonexistent").is_err());
assert!(manager.unregister("nonexistent").is_err());
}
}