use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use crate::core::error::PluginResult;
use crate::core::events::{Event, EventBus};
use crate::core::security::{Permission, SecurityContext};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub dependencies: Vec<String>,
pub optional_dependencies: Vec<String>,
pub capabilities: Vec<String>,
pub schema_version: String,
}
impl PluginMetadata {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
description: String::new(),
author: String::new(),
dependencies: Vec::new(),
optional_dependencies: Vec::new(),
capabilities: Vec::new(),
schema_version: "1.0".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginOutput {
pub success: bool,
pub data: serde_json::Value,
pub artifacts: Vec<PathBuf>,
pub metadata: HashMap<String, String>,
pub execution_time: Duration,
}
impl PluginOutput {
pub fn success(data: serde_json::Value) -> Self {
Self {
success: true,
data,
artifacts: Vec::new(),
metadata: HashMap::new(),
execution_time: Duration::from_secs(0),
}
}
pub fn failure(error: impl Into<String>) -> Self {
Self {
success: false,
data: serde_json::json!({ "error": error.into() }),
artifacts: Vec::new(),
metadata: HashMap::new(),
execution_time: Duration::from_secs(0),
}
}
}
#[derive(Clone)]
pub struct PluginContext {
pub plugin_name: String,
pub workspace: PathBuf,
pub dependency_outputs: HashMap<String, PluginOutput>,
pub security_context: Option<SecurityContext>,
pub event_bus: Option<Arc<EventBus>>,
}
impl PluginContext {
pub fn new(plugin_name: impl Into<String>, workspace: PathBuf) -> Self {
Self {
plugin_name: plugin_name.into(),
workspace,
dependency_outputs: HashMap::new(),
security_context: None,
event_bus: None,
}
}
pub fn with_security(
plugin_name: impl Into<String>,
workspace: PathBuf,
security_context: SecurityContext,
) -> Self {
Self {
plugin_name: plugin_name.into(),
workspace,
dependency_outputs: HashMap::new(),
security_context: Some(security_context),
event_bus: None,
}
}
pub fn with_event_bus(
plugin_name: impl Into<String>,
workspace: PathBuf,
event_bus: Arc<EventBus>,
) -> Self {
Self {
plugin_name: plugin_name.into(),
workspace,
dependency_outputs: HashMap::new(),
security_context: None,
event_bus: Some(event_bus),
}
}
pub fn with_security_and_events(
plugin_name: impl Into<String>,
workspace: PathBuf,
security_context: SecurityContext,
event_bus: Arc<EventBus>,
) -> Self {
Self {
plugin_name: plugin_name.into(),
workspace,
dependency_outputs: HashMap::new(),
security_context: Some(security_context),
event_bus: Some(event_bus),
}
}
pub fn get_dependency_output(&self, dep_name: &str) -> Option<&PluginOutput> {
self.dependency_outputs.get(dep_name)
}
pub fn add_dependency_output(&mut self, dep_name: String, output: PluginOutput) {
self.dependency_outputs.insert(dep_name, output);
}
pub fn set_security_context(&mut self, security_context: SecurityContext) {
self.security_context = Some(security_context);
}
pub fn security_context(&self) -> Option<&SecurityContext> {
self.security_context.as_ref()
}
pub fn has_permission(&self, permission: &Permission) -> bool {
self.security_context
.as_ref()
.map(|ctx| ctx.has_permission(permission))
.unwrap_or(false)
}
pub fn set_event_bus(&mut self, event_bus: Arc<EventBus>) {
self.event_bus = Some(event_bus);
}
pub fn event_bus(&self) -> Option<&Arc<EventBus>> {
self.event_bus.as_ref()
}
pub async fn publish_event(&self, event: Event) -> PluginResult<()> {
if let Some(event_bus) = &self.event_bus {
event_bus.publish(event).await
} else {
Err(crate::core::error::PluginError::EventError(
"No event bus available in context".to_string(),
))
}
}
pub async fn emit_event(
&self,
event_type: impl Into<String>,
data: serde_json::Value,
) -> PluginResult<()> {
let event = Event::from_plugin(event_type, &self.plugin_name, data);
self.publish_event(event).await
}
pub fn subscribe_to_events(&self) -> Option<tokio::sync::broadcast::Receiver<Event>> {
self.event_bus.as_ref().map(|bus| bus.subscribe())
}
}
#[async_trait]
pub trait Plugin: Send + Sync {
fn metadata(&self) -> &PluginMetadata;
fn schema(&self) -> serde_json::Value;
fn permissions(&self) -> Vec<Permission> {
vec![]
}
async fn initialize(
&mut self,
config: serde_json::Value,
context: &PluginContext,
) -> PluginResult<()>;
async fn execute(&mut self, context: &mut PluginContext) -> PluginResult<PluginOutput>;
async fn cleanup(&mut self, context: &PluginContext) -> PluginResult<()>;
async fn before_initialize(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
async fn after_initialize(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
async fn before_execute(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
async fn after_execute(
&mut self,
_context: &PluginContext,
_output: &PluginOutput,
) -> PluginResult<()> {
Ok(())
}
async fn before_cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
async fn after_cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
async fn on_error(
&mut self,
_context: &PluginContext,
_error: &crate::core::error::PluginError,
) -> PluginResult<()> {
Ok(())
}
async fn on_success(
&mut self,
_context: &PluginContext,
_output: &PluginOutput,
) -> PluginResult<()> {
Ok(())
}
async fn on_dependency_success(
&mut self,
_dependency: &str,
_output: &PluginOutput,
) -> PluginResult<()> {
Ok(())
}
async fn on_dependency_failure(
&mut self,
_dependency: &str,
_error: &crate::core::error::PluginError,
) -> PluginResult<()> {
Ok(())
}
async fn on_event(&mut self, _context: &PluginContext, _event: &Event) -> PluginResult<()> {
Ok(())
}
fn subscribed_events(&self) -> Vec<String> {
vec![]
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
struct TestPlugin {
metadata: PluginMetadata,
initialized: bool,
executed: bool,
}
impl TestPlugin {
fn new() -> Self {
Self {
metadata: PluginMetadata::new("test", "1.0.0"),
initialized: false,
executed: false,
}
}
}
#[async_trait]
impl Plugin for TestPlugin {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
fn schema(&self) -> serde_json::Value {
json!({
"type": "object",
"properties": {
"test_config": { "type": "string" }
}
})
}
fn permissions(&self) -> Vec<Permission> {
vec![Permission::TempDir]
}
async fn initialize(
&mut self,
_config: serde_json::Value,
_context: &PluginContext,
) -> PluginResult<()> {
self.initialized = true;
Ok(())
}
async fn execute(&mut self, _context: &mut PluginContext) -> PluginResult<PluginOutput> {
self.executed = true;
Ok(PluginOutput::success(json!({"message": "Test executed"})))
}
async fn cleanup(&mut self, _context: &PluginContext) -> PluginResult<()> {
Ok(())
}
}
#[tokio::test]
async fn test_plugin_metadata() {
let plugin = TestPlugin::new();
let metadata = plugin.metadata();
assert_eq!(metadata.name, "test");
assert_eq!(metadata.version, "1.0.0");
assert_eq!(metadata.schema_version, "1.0");
}
#[tokio::test]
async fn test_plugin_initialization() {
let mut plugin = TestPlugin::new();
let context = PluginContext::new("test", PathBuf::from("/tmp"));
assert!(!plugin.initialized);
let result = plugin.initialize(json!({}), &context).await;
assert!(result.is_ok());
assert!(plugin.initialized);
}
#[tokio::test]
async fn test_plugin_execution() {
let mut plugin = TestPlugin::new();
let mut context = PluginContext::new("test", PathBuf::from("/tmp"));
assert!(!plugin.executed);
let result = plugin.execute(&mut context).await;
assert!(result.is_ok());
assert!(plugin.executed);
let output = result.unwrap();
assert!(output.success);
assert_eq!(output.data["message"], "Test executed");
}
#[tokio::test]
async fn test_plugin_context() {
let mut context = PluginContext::new("test", PathBuf::from("/tmp"));
assert_eq!(context.plugin_name, "test");
assert_eq!(context.workspace, PathBuf::from("/tmp"));
assert!(context.dependency_outputs.is_empty());
let output = PluginOutput::success(json!({"test": "data"}));
context.add_dependency_output("dep1".to_string(), output);
assert!(context.get_dependency_output("dep1").is_some());
assert!(context.get_dependency_output("dep2").is_none());
}
#[test]
fn test_plugin_output_success() {
let output = PluginOutput::success(json!({"test": "value"}));
assert!(output.success);
assert_eq!(output.data["test"], "value");
assert!(output.artifacts.is_empty());
}
#[test]
fn test_plugin_output_failure() {
let output = PluginOutput::failure("Test error");
assert!(!output.success);
assert_eq!(output.data["error"], "Test error");
}
}