use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;
pub mod hooks;
pub mod manager;
pub mod registry;
pub use hooks::{HookExecutor, HookPoint, HookRegistry};
pub use manager::PluginManager;
pub use registry::{PluginDiscovery, PluginRegistry};
#[derive(Debug, thiserror::Error)]
pub enum PluginError {
#[error("Plugin initialization failed: {0}")]
InitFailed(String),
#[error("Plugin hook execution failed: {0}")]
HookFailed(String),
#[error("Plugin not found: {0}")]
NotFound(String),
#[error("Plugin version incompatible: {0}")]
VersionMismatch(String),
#[error("Plugin configuration error: {0}")]
ConfigError(String),
#[error("Plugin already registered: {0}")]
AlreadyRegistered(String),
#[error("Plugin disabled: {0}")]
Disabled(String),
#[error("Plugin error: {0}")]
General(String),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub api_version: String,
pub capabilities: Vec<String>,
pub config_schema: Option<Value>,
}
impl fmt::Display for PluginMetadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} v{} by {} - {} (API: {})",
self.name, self.version, self.author, self.description, self.api_version
)
}
}
#[derive(Debug, Clone)]
pub struct PluginContext {
pub operation: String,
pub data: Value,
pub metadata: HashMap<String, String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl PluginContext {
pub fn new(operation: impl Into<String>, data: Value) -> Self {
Self {
operation: operation.into(),
data,
metadata: HashMap::new(),
timestamp: chrono::Utc::now(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn get_metadata(&self, key: &str) -> Option<&String> {
self.metadata.get(key)
}
pub fn set_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.metadata.insert(key.into(), value.into());
}
pub fn operation(&self) -> &str {
&self.operation
}
pub fn data(&self) -> &Value {
&self.data
}
}
#[async_trait]
pub trait Plugin: Send + Sync {
fn metadata(&self) -> &PluginMetadata;
async fn init(&self) -> Result<(), PluginError> {
Ok(())
}
async fn shutdown(&self) -> Result<(), PluginError> {
Ok(())
}
async fn before_create_node(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn after_create_node(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn before_create_session(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn after_create_session(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn before_query(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn after_query(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn before_create_edge(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn after_create_edge(&self, _context: &PluginContext) -> Result<(), PluginError> {
Ok(())
}
async fn before_hook(
&self,
hook_name: &str,
context: &PluginContext,
) -> Result<(), PluginError> {
match hook_name {
"before_create_node" => self.before_create_node(context).await,
"before_create_session" => self.before_create_session(context).await,
"before_query" => self.before_query(context).await,
"before_create_edge" => self.before_create_edge(context).await,
_ => Ok(()),
}
}
async fn after_hook(
&self,
hook_name: &str,
context: &PluginContext,
) -> Result<(), PluginError> {
match hook_name {
"after_create_node" => self.after_create_node(context).await,
"after_create_session" => self.after_create_session(context).await,
"after_query" => self.after_query(context).await,
"after_create_edge" => self.after_create_edge(context).await,
_ => Ok(()),
}
}
}
pub struct PluginBuilder {
metadata: PluginMetadata,
}
impl PluginBuilder {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
metadata: PluginMetadata {
name: name.into(),
version: version.into(),
author: String::new(),
description: String::new(),
api_version: "1.0.0".to_string(),
capabilities: Vec::new(),
config_schema: None,
},
}
}
pub fn author(mut self, author: impl Into<String>) -> Self {
self.metadata.author = author.into();
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.metadata.description = description.into();
self
}
pub fn api_version(mut self, version: impl Into<String>) -> Self {
self.metadata.api_version = version.into();
self
}
pub fn capability(mut self, capability: impl Into<String>) -> Self {
self.metadata.capabilities.push(capability.into());
self
}
pub fn config_schema(mut self, schema: Value) -> Self {
self.metadata.config_schema = Some(schema);
self
}
pub fn build(self) -> PluginMetadata {
self.metadata
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_builder() {
let metadata = PluginBuilder::new("test_plugin", "1.0.0")
.author("Test Author")
.description("Test plugin")
.capability("validation")
.capability("enrichment")
.build();
assert_eq!(metadata.name, "test_plugin");
assert_eq!(metadata.version, "1.0.0");
assert_eq!(metadata.author, "Test Author");
assert_eq!(metadata.capabilities.len(), 2);
}
#[test]
fn test_plugin_context() {
let context = PluginContext::new("test_operation", serde_json::json!({"key": "value"}))
.with_metadata("test_key", "test_value");
assert_eq!(context.operation(), "test_operation");
assert_eq!(
context.get_metadata("test_key"),
Some(&"test_value".to_string())
);
}
}