use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
use std::sync::atomic::{AtomicBool, Ordering};
pub type PluginId = String;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PluginStatus {
Loaded,
Active,
Inactive,
Error,
Crashed,
}
#[derive(Debug, Clone)]
pub enum PluginError {
PermissionDenied(String),
LoadFailed(String),
ExecutionFailed(String),
Timeout,
ResourceLimitExceeded(String),
NotFound(String),
InvalidConfig(String),
}
impl std::fmt::Display for PluginError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PluginError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
PluginError::LoadFailed(msg) => write!(f, "Load failed: {}", msg),
PluginError::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
PluginError::Timeout => write!(f, "Plugin execution timed out"),
PluginError::ResourceLimitExceeded(msg) => write!(f, "Resource limit exceeded: {}", msg),
PluginError::NotFound(msg) => write!(f, "Plugin not found: {}", msg),
PluginError::InvalidConfig(msg) => write!(f, "Invalid config: {}", msg),
}
}
}
impl std::error::Error for PluginError {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub id: String,
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub entry_point: String,
pub permissions: Vec<String>,
pub dependencies: HashMap<String, String>,
}
impl PluginMetadata {
pub fn simple(id: &str, version: &str) -> Self {
Self {
id: id.to_string(),
name: id.to_string(),
version: version.to_string(),
author: "Unknown".to_string(),
description: format!("Plugin {}", id),
entry_point: "index.js".to_string(),
permissions: vec![],
dependencies: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
pub sandbox_enabled: bool,
pub timeout_ms: u64,
pub max_memory_mb: u64,
pub options: HashMap<String, Value>,
}
impl Default for PluginConfig {
fn default() -> Self {
Self {
sandbox_enabled: true,
timeout_ms: 5000,
max_memory_mb: 100,
options: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct PluginHandle {
id: PluginId,
status: Arc<std::sync::RwLock<PluginStatus>>,
}
impl PluginHandle {
pub fn plugin_id(&self) -> &str {
&self.id
}
pub fn status(&self) -> PluginStatus {
self.status.read().unwrap().clone()
}
fn set_status(&self, status: PluginStatus) {
*self.status.write().unwrap() = status;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginResult {
pub success: bool,
pub data: Option<Value>,
pub error: Option<String>,
pub execution_time_ms: u64,
}
impl PluginResult {
pub fn success(data: Value) -> Self {
Self {
success: true,
data: Some(data),
error: None,
execution_time_ms: 0,
}
}
pub fn failure(error: &str) -> Self {
Self {
success: false,
data: None,
error: Some(error.to_string()),
execution_time_ms: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct PermissionSet {
granted: Vec<String>,
}
impl PermissionSet {
pub fn minimal() -> Self {
Self { granted: vec![] }
}
pub fn full() -> Self {
Self {
granted: vec![
"fs.read".to_string(),
"fs.write".to_string(),
"net.fetch".to_string(),
"net.listen".to_string(),
"env.read".to_string(),
"runtime.execute".to_string(),
],
}
}
pub fn grant(&mut self, permission: &str) {
if !self.granted.contains(&permission.to_string()) {
self.granted.push(permission.to_string());
}
}
pub fn revoke(&mut self, permission: &str) {
self.granted.retain(|p| p != permission);
}
pub fn has(&self, permission: &str) -> bool {
self.granted.iter().any(|p| {
p == permission || permission.starts_with(&format!("{}.", p))
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct ResourceLimits {
pub max_memory_mb: u64,
pub max_cpu_time_ms: u64,
pub max_file_handles: u32,
pub max_network_connections: u32,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_mb: 100,
max_cpu_time_ms: 5000,
max_file_handles: 100,
max_network_connections: 10,
}
}
}
pub struct SandboxContext {
permissions: PermissionSet,
}
impl SandboxContext {
pub fn access_fs(&self, _path: &str) -> Result<(), PluginError> {
if self.permissions.has("fs.read") || self.permissions.has("fs.write") {
Ok(())
} else {
Err(PluginError::PermissionDenied("fs access not allowed".to_string())
}
}
}
pub struct PluginSandbox {
permissions: Arc<PermissionSet>,
limits: ResourceLimits,
}
impl PluginSandbox {
pub fn new(permissions: PermissionSet) -> Self {
Self {
permissions: Arc::new(Mutex::new(permissions)))
limits: ResourceLimits::default(),
}
}
pub fn with_limits(permissions: PermissionSet, limits: ResourceLimits) -> Self {
Self {
permissions: Arc::new(Mutex::new(permissions)))
limits,
}
}
pub fn memory_limit(&self) -> u64 {
self.limits.max_memory_mb
}
pub fn cpu_time_limit(&self) -> u64 {
self.limits.max_cpu_time_ms
}
pub fn check_permission(&self, permission: &str) -> Result<(), PluginError> {
if self.permissions.has(permission) {
Ok(())
} else {
Err(PluginError::PermissionDenied(format!(
"Permission '{}' not granted",
permission
))
}
}
pub async fn execute_in_sandbox<F, T>(&self, f: F) -> Result<T, PluginError>
where
F: FnOnce(&SandboxContext) -> Result<T, PluginError>,
{
let ctx: _ = SandboxContext {
permissions: (*self.permissions).clone(),
};
f(&ctx)
}
pub async fn execute_with_timeout<F, T>(&self, f: F) -> Result<T, PluginError>
where
F: Future<Output = Result<T, PluginError>> + Send,
{
let timeout: _ = tokio::time::Duration::from_millis(self.limits.max_cpu_time_ms);
match tokio::time::timeout(timeout, f).await {
Ok(result) => result,
Err(_) => Err(PluginError::Timeout),
}
}
}
pub struct PluginRegistry {
plugins: Arc<RwLock<HashMap<PluginId, PluginMetadata>>>,
}
impl PluginRegistry {
pub fn new() -> Self {
Self {
plugins: Arc::new(Mutex::new(HashMap::new()))
}
}
pub async fn register(&self, metadata: &PluginMetadata) -> Result<PluginId, PluginError> {
let id: _ = metadata.id.clone();
let mut plugins = self.plugins.write().await;
plugins.insert(id.clone(), metadata.clone());
Ok(id)
}
pub async fn unregister(&self, id: &str) -> Result<(), PluginError> {
let mut plugins = self.plugins.write().await;
plugins.remove(id);
Ok(())
}
pub async fn is_registered_async(&self, id: &PluginId) -> bool {
let plugins: _ = self.plugins.read().await;
plugins.contains_key(id)
}
pub fn is_registered(&self, id: &PluginId) -> bool {
if let Ok(plugins) = self.plugins.try_read() {
plugins.contains_key(id)
} else {
false
}
}
pub async fn get(&self, id: &str) -> Option<PluginMetadata> {
let plugins: _ = self.plugins.read().await;
plugins.get(id).cloned()
}
pub async fn discover_all(&self) -> Vec<PluginMetadata> {
let plugins: _ = self.plugins.read().await;
plugins.values().cloned().collect()
}
pub async fn search(&self, query: &str) -> Vec<PluginMetadata> {
let plugins: _ = self.plugins.read().await;
plugins
.values()
.filter(|p| {
p.name.contains(query)
|| p.id.contains(query)
|| p.description.contains(query)
})
.cloned()
.collect()
}
}
pub struct PluginLoader {
supported_languages: Vec<String>,
}
impl PluginLoader {
pub fn new() -> Self {
Self {
supported_languages: vec![
"javascript".to_string(),
"typescript".to_string(),
"wasm".to_string(),
],
}
}
pub async fn load_from_source(
&self,
code: &str,
language: &str,
) -> Result<LoadedPlugin, PluginError> {
if !self.supported_languages.contains(&language.to_lowercase()) {
return Err(PluginError::LoadFailed(format!(
"Unsupported language: {}",
language
));
}
Ok(LoadedPlugin {
code: code.to_string(),
language: language.to_string(),
compiled: false,
})
}
pub async fn load_from_wasm(&self, _bytes: &[u8]) -> Result<LoadedPlugin, PluginError> {
Ok(LoadedPlugin {
code: String::new(),
language: "wasm".to_string(),
compiled: true,
})
}
pub async fn resolve_dependencies(
&self,
deps: &HashMap<String, String>,
) -> Result<HashMap<String, String>, PluginError> {
let mut resolved = HashMap::new();
for (name, version) in deps {
resolved.insert(name.clone(), version.clone());
}
Ok(resolved)
}
}
pub struct LoadedPlugin {
pub code: String,
pub language: String,
pub compiled: bool,
}
pub struct PluginAPI {
versions: Vec<String>,
}
impl PluginAPI {
pub fn new() -> Self {
Self {
versions: vec!["v1".to_string(), "v2".to_string()],
}
}
pub async fn call(&self, method: &str, params: &Value) -> Result<Value, PluginError> {
match method {
"log" => {
let level: _ = params.get("level").and_then(|v| v.as_str()).unwrap_or("info");
let message: _ = params.get("message").and_then(|v| v.as_str()).unwrap_or("");
println!("[Plugin {}] {}", level.to_uppercase(), message);
Ok(serde_json::json!({"success": true}))
}
"echo" => Ok(params.clone()),
_ => Err(PluginError::NotFound(format!("API method '{}' not found", method)),
}
}
pub async fn call_versioned(
&self,
version: &str,
method: &str,
params: &Value,
) -> Result<Value, PluginError> {
if !self.versions.contains(&version.to_string()) {
return Err(PluginError::NotFound(format!("API version '{}' not found", version));
}
self.call(method, params).await
}
}
#[async_trait::async_trait]
pub trait PluginInterface: Send + Sync {
async fn initialize(&self, config: PluginConfig) -> Result<(), PluginError>;
async fn execute(&self, input: &Value) -> Result<Value, PluginError>;
async fn shutdown(&self) -> Result<(), PluginError>;
}
struct PluginInstance {
metadata: PluginMetadata,
status: Arc<std::sync::RwLock<PluginStatus>>,
sandbox: PluginSandbox,
connections: Vec<String>,
}
pub struct PluginEngine {
initialized: AtomicBool,
registry: Arc<PluginRegistry>,
loader: Arc<PluginLoader>,
api: Arc<PluginAPI>,
instances: Arc<RwLock<HashMap<PluginId, PluginInstance>>>,
}
impl PluginEngine {
pub fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
registry: Arc::new(Mutex::new(PluginRegistry::new()))
loader: Arc::new(Mutex::new(PluginLoader::new()))
api: Arc::new(Mutex::new(PluginAPI::new()))
instances: Arc::new(Mutex::new(HashMap::new()))
}
}
pub async fn initialize(&self) -> Result<(), PluginError> {
self.initialized.store(true, Ordering::SeqCst);
Ok(())
}
pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}
pub async fn load_plugin(&self, metadata: &PluginMetadata) -> Result<PluginHandle, PluginError> {
self.registry.register(metadata).await?;
let mut permissions = PermissionSet::minimal();
for perm in &metadata.permissions {
permissions.grant(perm);
}
let sandbox: _ = PluginSandbox::new(permissions);
let status: _ = Arc::new(Mutex::new(std::sync::RwLock::new(PluginStatus::Loaded)),;
let instance: _ = PluginInstance {
metadata: metadata.clone(),
status: status.clone(),
sandbox,
connections: vec![],
};
let mut instances = self.instances.write().await;
instances.insert(metadata.id.clone(), instance);
Ok(PluginHandle {
id: metadata.id.clone(),
status,
})
}
pub async fn unload_plugin(&self, handle: &PluginHandle) -> Result<(), PluginError> {
let mut instances = self.instances.write().await;
instances.remove(&handle.id);
self.registry.unregister(&handle.id).await?;
Ok(())
}
pub async fn has_plugin_async(&self, id: &str) -> bool {
let instances: _ = self.instances.read().await;
instances.contains_key(id)
}
pub fn has_plugin(&self, id: &str) -> bool {
if let Ok(instances) = self.instances.try_read() {
instances.contains_key(id)
} else {
false
}
}
pub fn get_plugin_status(&self, handle: &PluginHandle) -> PluginStatus {
handle.status()
}
pub async fn activate_plugin(&self, handle: &PluginHandle) -> Result<(), PluginError> {
let instances: _ = self.instances.read().await;
if instances.contains_key(&handle.id) {
handle.set_status(PluginStatus::Active);
Ok(())
} else {
Err(PluginError::NotFound(handle.id.clone())
}
}
pub async fn deactivate_plugin(&self, handle: &PluginHandle) -> Result<(), PluginError> {
let instances: _ = self.instances.read().await;
if instances.contains_key(&handle.id) {
handle.set_status(PluginStatus::Inactive);
Ok(())
} else {
Err(PluginError::NotFound(handle.id.clone())
}
}
pub async fn execute_plugin(
&self,
handle: &PluginHandle,
input: &Value,
) -> Result<PluginResult, PluginError> {
let start: _ = std::time::Instant::now();
let instances: _ = self.instances.read().await;
let _instance: _ = instances
.get(&handle.id)
.ok_or_else(|| PluginError::NotFound(handle.id.clone())?;
let action: _ = input.get("action").and_then(|v| v.as_str()).unwrap_or("default");
let result: _ = match action {
"throw_error" => PluginResult::failure("Simulated error"),
_ => PluginResult::success(serde_json::json!({
"input": input,
"processed": true
})),
};
let mut result = result;
result.execution_time_ms = start.elapsed().as_millis() as u64;
Ok(result)
}
pub async fn list_plugins(&self) -> Vec<PluginMetadata> {
self.registry.discover_all().await
}
pub async fn connect_plugins(
&self,
from: &PluginHandle,
_to: &PluginHandle,
channel: &str,
) -> Result<(), PluginError> {
let mut instances = self.instances.write().await;
if let Some(instance) = instances.get_mut(&from.id) {
instance.connections.push(channel.to_string());
Ok(())
} else {
Err(PluginError::NotFound(from.id.clone())
}
}
pub async fn get_plugin_connections(&self, handle: &PluginHandle) -> Vec<String> {
let instances: _ = self.instances.read().await;
instances
.get(&handle.id)
.map(|i| i.connections.clone())
.unwrap_or_default()
}
}
impl Default for PluginEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_plugin_engine_new() {
let engine: _ = PluginEngine::new();
assert!(!engine.is_initialized());
}
#[tokio::test]
async fn test_plugin_metadata_simple() {
let meta: _ = PluginMetadata::simple("test", "1.0.0");
assert_eq!(meta.id, "test");
assert_eq!(meta.version, "1.0.0");
}
#[tokio::test]
async fn test_permission_set() {
let mut perms = PermissionSet::minimal();
assert!(!perms.has("fs.read"));
perms.grant("fs.read");
assert!(perms.has("fs.read"));
perms.revoke("fs.read");
assert!(!perms.has("fs.read"));
}
#[tokio::test]
async fn test_plugin_result() {
let success: _ = PluginResult::success(serde_json::json!({"test": true}));
assert!(success.success);
let failure: _ = PluginResult::failure("error");
assert!(!failure.success);
}
}