oxicode_sdk/middleware/
plugin.rs1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use parking_lot::RwLock;
8use serde::{Deserialize, Serialize};
9
10use super::Middleware;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct PluginManifest {
15 pub name: String,
17 pub version: String,
19 pub phases: Vec<String>,
21 pub entry_point: String,
23 pub permissions: Vec<String>,
25}
26
27impl PluginManifest {
28 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
30 serde_json::from_str(json)
31 }
32
33 pub fn from_file(path: &Path) -> std::io::Result<Self> {
35 let content = std::fs::read_to_string(path)?;
36 serde_json::from_str(&content)
37 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
38 }
39}
40
41pub struct PluginLoader {
43 #[expect(dead_code)]
44 plugins_dir: PathBuf,
45 loaded: Arc<RwLock<HashMap<String, Arc<dyn Middleware>>>>,
46 manifests: Arc<RwLock<HashMap<String, PluginManifest>>>,
47}
48
49impl PluginLoader {
50 pub fn new(plugins_dir: impl Into<PathBuf>) -> Self {
52 Self {
53 plugins_dir: plugins_dir.into(),
54 loaded: Arc::new(RwLock::new(HashMap::new())),
55 manifests: Arc::new(RwLock::new(HashMap::new())),
56 }
57 }
58
59 pub async fn load(&self, manifest_path: &Path) -> crate::error::SdkResult<String> {
61 let manifest = PluginManifest::from_file(manifest_path).map_err(|e| {
62 crate::error::SdkError::InvalidState {
63 entity: "plugin".into(),
64 reason: format!("failed to load manifest: {}", e),
65 }
66 })?;
67 let name = manifest.name.clone();
68 let mut manifests = self.manifests.write();
69 manifests.insert(name.clone(), manifest);
70 Ok(name)
71 }
72
73 pub fn middlewares(&self) -> Vec<Arc<dyn Middleware>> {
75 let loaded = self.loaded.read();
76 loaded.values().cloned().collect()
77 }
78
79 pub fn get(&self, name: &str) -> Option<Arc<dyn Middleware>> {
81 self.loaded.read().get(name).cloned()
82 }
83
84 pub fn unload(&self, name: &str) -> bool {
86 let mut loaded = self.loaded.write();
87 let removed = loaded.remove(name).is_some();
88 let mut manifests = self.manifests.write();
89 manifests.remove(name);
90 removed
91 }
92
93 pub fn register(&self, middleware: Arc<dyn Middleware>) {
95 let mut loaded = self.loaded.write();
96 loaded.insert(middleware.name().to_string(), middleware);
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::Middleware;
104 use crate::middleware::{MiddlewareContext, MiddlewarePhase, MiddlewareResult};
105 use std::future::Future;
106 use std::pin::Pin;
107
108 struct MockMiddleware {
109 name: String,
110 }
111
112 impl MockMiddleware {
113 fn new(name: &str) -> Self {
114 Self {
115 name: name.to_string(),
116 }
117 }
118 }
119
120 impl Middleware for MockMiddleware {
121 fn name(&self) -> &str {
122 &self.name
123 }
124 fn phases(&self) -> Vec<MiddlewarePhase> {
125 vec![MiddlewarePhase::BeforeTool]
126 }
127 fn handle<'a>(
128 &'a self,
129 _ctx: &'a MiddlewareContext,
130 ) -> Pin<Box<dyn Future<Output = MiddlewareResult> + Send + 'a>> {
131 Box::pin(async { MiddlewareResult::pass() })
132 }
133 }
134
135 #[test]
136 fn test_plugin_loader_register() {
137 let loader = PluginLoader::new("/tmp/plugins");
138 loader.register(Arc::new(MockMiddleware::new("test-plugin")));
139 let mws = loader.middlewares();
140 assert_eq!(mws.len(), 1);
141 assert_eq!(mws[0].name(), "test-plugin");
142 }
143
144 #[test]
145 fn test_plugin_loader_unload() {
146 let loader = PluginLoader::new("/tmp/plugins");
147 loader.register(Arc::new(MockMiddleware::new("test-plugin")));
148 assert!(loader.get("test-plugin").is_some());
149 loader.unload("test-plugin");
150 assert!(loader.get("test-plugin").is_none());
151 }
152
153 #[test]
154 fn test_plugin_manifest_parse() {
155 let json = r#"{"name":"test-plugin","version":"1.0.0","phases":["before_tool"],"entry_point":"libtest.so","permissions":[]}"#;
156 let manifest = PluginManifest::from_json(json).unwrap();
157 assert_eq!(manifest.name, "test-plugin");
158 }
159}