use std::collections::HashMap;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub license: String,
pub dependencies: Vec<String>,
pub api_version: String,
}
#[async_trait]
pub trait Plugin: Send + Sync {
fn metadata(&self) -> &PluginMetadata;
async fn initialize(&mut self) -> Result<()>;
fn register_commands(&self) -> HashMap<String, Box<dyn PluginCommand>>;
async fn cleanup(&mut self) -> Result<()>;
}
#[async_trait]
pub trait PluginCommand: Send + Sync {
async fn execute(&self, args: &[String]) -> Result<String>;
fn help(&self) -> &str;
fn usage(&self) -> &str;
}
pub struct PluginManager {
plugins: HashMap<String, Box<dyn Plugin>>,
commands: HashMap<String, Box<dyn PluginCommand>>,
}
impl PluginManager {
pub fn new() -> Self {
Self {
plugins: HashMap::new(),
commands: HashMap::new(),
}
}
pub async fn load_plugin(&mut self, plugin: Box<dyn Plugin>) -> Result<()> {
let name = plugin.metadata().name.clone();
let mut plugin = plugin;
plugin.initialize().await?;
let commands = plugin.register_commands();
for (cmd_name, command) in commands {
self.commands.insert(cmd_name, command);
}
self.plugins.insert(name, plugin);
Ok(())
}
pub async fn unload_plugin(&mut self, name: &str) -> Result<()> {
if let Some(mut plugin) = self.plugins.remove(name) {
plugin.cleanup().await?;
}
Ok(())
}
pub fn get_command(&self, name: &str) -> Option<&Box<dyn PluginCommand>> {
self.commands.get(name)
}
pub fn list_plugins(&self) -> Vec<&PluginMetadata> {
self.plugins.values().map(|p| p.metadata()).collect()
}
pub fn list_commands(&self) -> Vec<&str> {
self.commands.keys().map(|s| s.as_str()).collect()
}
}
pub struct HttpPlugin {
metadata: PluginMetadata,
}
impl HttpPlugin {
pub fn new() -> Self {
Self {
metadata: PluginMetadata {
name: "http".to_string(),
version: "1.0.0".to_string(),
description: "HTTP client plugin for OpenScript".to_string(),
author: "OpenScript Team".to_string(),
license: "MIT".to_string(),
dependencies: vec!["reqwest".to_string()],
api_version: "1.0".to_string(),
},
}
}
}
#[async_trait]
impl Plugin for HttpPlugin {
fn metadata(&self) -> &PluginMetadata {
&self.metadata
}
async fn initialize(&mut self) -> Result<()> {
Ok(())
}
fn register_commands(&self) -> HashMap<String, Box<dyn PluginCommand>> {
let mut commands = HashMap::new();
commands.insert("http_get".to_string(), Box::new(HttpGetCommand) as Box<dyn PluginCommand>);
commands.insert("http_post".to_string(), Box::new(HttpPostCommand) as Box<dyn PluginCommand>);
commands
}
async fn cleanup(&mut self) -> Result<()> {
Ok(())
}
}
struct HttpGetCommand;
#[async_trait]
impl PluginCommand for HttpGetCommand {
async fn execute(&self, args: &[String]) -> Result<String> {
if args.is_empty() {
return Err("URL required".into());
}
let url = &args[0];
let client = reqwest::Client::new();
let response = client.get(url).send().await.map_err(|e| e.to_string())?;
let text = response.text().await.map_err(|e| e.to_string())?;
Ok(text)
}
fn help(&self) -> &str {
"Send HTTP GET request to specified URL"
}
fn usage(&self) -> &str {
"http_get <url> [headers...]"
}
}
struct HttpPostCommand;
#[async_trait]
impl PluginCommand for HttpPostCommand {
async fn execute(&self, args: &[String]) -> Result<String> {
if args.len() < 2 {
return Err("URL and body required".into());
}
let url = &args[0];
let body = &args[1];
let client = reqwest::Client::new();
let response = client.post(url)
.body(body.clone())
.header("content-type", "application/json")
.send()
.await
.map_err(|e| e.to_string())?;
let text = response.text().await.map_err(|e| e.to_string())?;
Ok(text)
}
fn help(&self) -> &str {
"Send HTTP POST request with specified body"
}
fn usage(&self) -> &str {
"http_post <url> <body> [headers...]"
}
}