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