1mod macros;
65mod manager;
66mod network;
67
68use crate::network::NETWORK;
69pub use async_trait::async_trait;
70use libloading::Symbol;
71pub use manager::PluginManager;
72pub use aiway_protocol as protocol;
73use protocol::gateway::HttpContext;
74pub use semver::Version;
75use serde::{Deserialize, Serialize};
76pub use serde_json;
77use serde_json::Value;
78use std::env::temp_dir;
79use std::fs;
80use std::fs::File;
81use std::io::Write;
82use std::path::PathBuf;
83#[derive(Debug)]
84pub enum PluginError {
85 ExecuteError(String),
87 NotFound(String),
89 LoadError(String),
91}
92
93impl std::fmt::Display for PluginError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 PluginError::ExecuteError(msg) => write!(f, "{}", msg),
97 PluginError::NotFound(msg) => write!(f, "{}", msg),
98 PluginError::LoadError(msg) => write!(f, "{}", msg),
99 }
100 }
101}
102
103#[async_trait]
119pub trait Plugin: Send + Sync {
120 fn name(&self) -> &str;
122 fn info(&self) -> PluginInfo;
124 async fn execute(&self, context: &HttpContext, config: &Value) -> Result<Value, PluginError>;
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PluginInfo {
131 pub version: Version,
133 pub default_config: Value,
135 pub description: String,
137}
138
139impl TryFrom<PathBuf> for Box<dyn Plugin> {
140 type Error = PluginError;
141
142 fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
143 unsafe {
144 let lib = libloading::Library::new(&value)
145 .map_err(|e| PluginError::LoadError(e.to_string()))?;
146
147 let create_plugin: Symbol<unsafe extern "C" fn() -> *mut dyn Plugin> = lib
148 .get(b"create_plugin")
149 .map_err(|e| PluginError::LoadError(e.to_string()))?;
150
151 let plugin_ptr = create_plugin();
152
153 if plugin_ptr.is_null() {
154 return Err(PluginError::LoadError(
155 "Failed to create plugin: ptr is null".to_string(),
156 ));
157 }
158
159 let plugin = Box::from_raw(plugin_ptr);
160
161 let wrapped_plugin = Box::new(LibraryPluginWrapper { plugin, _lib: lib });
163
164 Ok(wrapped_plugin)
165 }
166 }
167}
168
169struct LibraryPluginWrapper {
170 plugin: Box<dyn Plugin>,
171 _lib: libloading::Library,
172}
173
174#[async_trait]
175impl Plugin for LibraryPluginWrapper {
176 fn name(&self) -> &str {
177 self.plugin.name()
178 }
179
180 fn info(&self) -> PluginInfo {
181 self.plugin.info()
182 }
183
184 async fn execute(&self, context: &HttpContext, config: &Value) -> Result<Value, PluginError> {
185 self.plugin.execute(context, config).await
186 }
187}
188
189impl Drop for LibraryPluginWrapper {
190 fn drop(&mut self) {
191 unsafe {
192 let destructor: Symbol<unsafe extern "C" fn(*mut dyn Plugin)> = self
193 ._lib
194 .get(b"destroy_plugin")
195 .expect("Failed to get destructor function");
196
197 destructor(self.plugin.as_mut());
198 }
199 }
200}
201
202pub struct NetworkPlugin(pub String);
204
205#[async_trait]
206pub trait AsyncTryInto<T>: Sized {
207 type Error;
208
209 async fn async_try_into(self) -> Result<T, Self::Error>;
210}
211
212#[async_trait]
213impl AsyncTryInto<Box<dyn Plugin>> for NetworkPlugin {
214 type Error = PluginError;
215
216 async fn async_try_into(self) -> Result<Box<dyn Plugin>, Self::Error> {
217 let response = NETWORK
218 .client
219 .get(&self.0)
220 .send()
221 .await
222 .map_err(|e| PluginError::LoadError(e.to_string()))?
223 .error_for_status()
224 .map_err(|e| PluginError::LoadError(e.to_string()))?;
225
226 let bytes = response
227 .bytes()
228 .await
229 .map_err(|e| PluginError::LoadError(e.to_string()))?;
230
231 let tpf = temp_dir().join(uuid::Uuid::new_v4().to_string());
232
233 let plugin = {
234 let tpf = tpf.clone();
235 let mut file = File::create(&tpf).map_err(|e| PluginError::LoadError(e.to_string()))?;
236
237 file.write_all(&bytes)
238 .map_err(|e| PluginError::LoadError(e.to_string()))?;
239
240 drop(file);
241
242 tpf.try_into()
243 };
244
245 fs::remove_file(tpf).map_err(|e| PluginError::LoadError(e.to_string()))?;
246
247 plugin
248 }
249}
250
251impl TryFrom<Vec<u8>> for Box<dyn Plugin> {
252 type Error = PluginError;
253
254 fn try_from(from: Vec<u8>) -> Result<Box<dyn Plugin>, Self::Error> {
255 let temp = temp_dir().join(format!("{}.so", uuid::Uuid::new_v4()));
256 fs::write(&temp, from).map_err(|e| PluginError::LoadError(e.to_string()))?;
257 temp.try_into()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::manager::PluginManager;
265 use std::io::Read;
266 #[tokio::test]
267 async fn test_network_plugin() {
268 let p = NetworkPlugin(
269 "http://192.168.1.242:10000/aiway/test/plugins/libdemo_plugin.so".to_string(),
270 );
271 let plugin: Box<dyn Plugin> = p.async_try_into().await.unwrap();
272 plugin
273 .execute(&HttpContext::default(), &Value::Null)
274 .await
275 .unwrap();
276 }
277 #[tokio::test]
278 async fn test_plugin_manager() {
279 let p = NetworkPlugin(
280 "http://192.168.1.242:10000/aiway/test/plugins/libdemo_plugin.so".to_string(),
281 );
282 let plugin: Box<dyn Plugin> = p.async_try_into().await.unwrap();
283 let mut manager = PluginManager::new();
284 manager.register(plugin);
285 manager
286 .run("demo", &HttpContext::default(), &Value::Null)
287 .await
288 .unwrap();
289 }
290
291 #[tokio::test]
292 async fn test_plugin_from_bytes() {
293 let file =
294 File::open("../../target/release/libaha_model_request_wrapper_plugin.so").unwrap();
295 let bytes = file.bytes().collect::<Result<Vec<_>, _>>().unwrap();
297 let plugin: Box<dyn Plugin> = bytes.try_into().unwrap();
298 println!("{:?}", plugin.info());
299 }
300}