1use crate::types::Plugin;
4use crate::{ZoeyError, Result};
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8pub fn validate_plugin(plugin: &Arc<dyn Plugin>) -> Result<()> {
16 let mut errors = Vec::new();
17
18 if plugin.name().is_empty() {
20 errors.push("Plugin must have a name".to_string());
21 }
22
23 if plugin.description().is_empty() {
25 errors.push("Plugin should have a description".to_string());
26 }
27
28 if !errors.is_empty() {
29 return Err(ZoeyError::Validation(format!(
30 "Plugin validation failed: {}",
31 errors.join(", ")
32 )));
33 }
34
35 Ok(())
36}
37
38pub fn resolve_plugin_dependencies(
48 plugins: HashMap<String, Arc<dyn Plugin>>,
49 is_test_mode: bool,
50) -> Result<Vec<Arc<dyn Plugin>>> {
51 let mut resolution_order: Vec<String> = Vec::new();
52 let mut visited: HashSet<String> = HashSet::new();
53 let mut visiting: HashSet<String> = HashSet::new();
54
55 fn visit(
56 plugin_name: &str,
57 plugins: &HashMap<String, Arc<dyn Plugin>>,
58 is_test_mode: bool,
59 visited: &mut HashSet<String>,
60 visiting: &mut HashSet<String>,
61 resolution_order: &mut Vec<String>,
62 ) -> Result<()> {
63 if !plugins.contains_key(plugin_name) {
64 return Err(ZoeyError::NotFound(format!(
65 "Plugin dependency '{}' not found",
66 plugin_name
67 )));
68 }
69
70 if visited.contains(plugin_name) {
71 return Ok(());
72 }
73
74 if visiting.contains(plugin_name) {
75 return Err(ZoeyError::Validation(format!(
76 "Circular dependency detected involving plugin: {}",
77 plugin_name
78 )));
79 }
80
81 visiting.insert(plugin_name.to_string());
82
83 if let Some(plugin) = plugins.get(plugin_name) {
84 for dep in plugin.dependencies() {
86 visit(
87 &dep,
88 plugins,
89 is_test_mode,
90 visited,
91 visiting,
92 resolution_order,
93 )?;
94 }
95
96 if is_test_mode {
98 for dep in plugin.test_dependencies() {
99 visit(
100 &dep,
101 plugins,
102 is_test_mode,
103 visited,
104 visiting,
105 resolution_order,
106 )?;
107 }
108 }
109 }
110
111 visiting.remove(plugin_name);
112 visited.insert(plugin_name.to_string());
113 resolution_order.push(plugin_name.to_string());
114
115 Ok(())
116 }
117
118 for name in plugins.keys() {
120 if !visited.contains(name) {
121 visit(
122 name,
123 &plugins,
124 is_test_mode,
125 &mut visited,
126 &mut visiting,
127 &mut resolution_order,
128 )?;
129 }
130 }
131
132 let final_plugins: Vec<Arc<dyn Plugin>> = resolution_order
134 .iter()
135 .filter_map(|name| plugins.get(name).cloned())
136 .collect();
137
138 Ok(final_plugins)
139}
140
141pub async fn load_plugins(
150 plugins: Vec<Arc<dyn Plugin>>,
151 is_test_mode: bool,
152) -> Result<Vec<Arc<dyn Plugin>>> {
153 for plugin in &plugins {
155 validate_plugin(plugin)?;
156 }
157
158 let mut plugin_map: HashMap<String, Arc<dyn Plugin>> = HashMap::new();
160 for plugin in plugins {
161 plugin_map.insert(plugin.name().to_string(), plugin);
162 }
163
164 resolve_plugin_dependencies(plugin_map, is_test_mode)
166}
167
168pub async fn initialize_plugins(
178 plugins: &[Arc<dyn Plugin>],
179 config: HashMap<String, String>,
180 runtime: Arc<dyn std::any::Any + Send + Sync>,
181) -> Result<()> {
182 for plugin in plugins {
183 plugin.init(config.clone(), runtime.clone()).await?;
184 }
185
186 Ok(())
187}
188
189pub fn get_plugin_actions(plugins: &[Arc<dyn Plugin>]) -> Vec<Arc<dyn crate::types::Action>> {
197 plugins.iter().flat_map(|plugin| plugin.actions()).collect()
198}
199
200pub fn get_plugin_providers(plugins: &[Arc<dyn Plugin>]) -> Vec<Arc<dyn crate::types::Provider>> {
208 plugins
209 .iter()
210 .flat_map(|plugin| plugin.providers())
211 .collect()
212}
213
214pub fn get_plugin_evaluators(plugins: &[Arc<dyn Plugin>]) -> Vec<Arc<dyn crate::types::Evaluator>> {
222 plugins
223 .iter()
224 .flat_map(|plugin| plugin.evaluators())
225 .collect()
226}
227
228pub fn get_plugin_services(plugins: &[Arc<dyn Plugin>]) -> Vec<Arc<dyn crate::types::Service>> {
236 plugins
237 .iter()
238 .flat_map(|plugin| plugin.services())
239 .collect()
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use async_trait::async_trait;
246
247 struct MockPlugin {
248 name: String,
249 dependencies: Vec<String>,
250 }
251
252 #[async_trait]
253 impl Plugin for MockPlugin {
254 fn name(&self) -> &str {
255 &self.name
256 }
257
258 fn description(&self) -> &str {
259 "Mock plugin"
260 }
261
262 fn dependencies(&self) -> Vec<String> {
263 self.dependencies.clone()
264 }
265 }
266
267 #[tokio::test]
268 async fn test_validate_plugin() {
269 let plugin: Arc<dyn Plugin> = Arc::new(MockPlugin {
270 name: "test-plugin".to_string(),
271 dependencies: vec![],
272 });
273
274 assert!(validate_plugin(&plugin).is_ok());
275 }
276
277 #[tokio::test]
278 async fn test_empty_name_validation() {
279 let plugin: Arc<dyn Plugin> = Arc::new(MockPlugin {
280 name: "".to_string(),
281 dependencies: vec![],
282 });
283
284 assert!(validate_plugin(&plugin).is_err());
285 }
286
287 #[test]
288 fn test_resolve_dependencies() {
289 let plugin_a: Arc<dyn Plugin> = Arc::new(MockPlugin {
290 name: "plugin-a".to_string(),
291 dependencies: vec![],
292 });
293
294 let plugin_b: Arc<dyn Plugin> = Arc::new(MockPlugin {
295 name: "plugin-b".to_string(),
296 dependencies: vec!["plugin-a".to_string()],
297 });
298
299 let mut plugins = HashMap::new();
300 plugins.insert("plugin-a".to_string(), plugin_a);
301 plugins.insert("plugin-b".to_string(), plugin_b);
302
303 let result = resolve_plugin_dependencies(plugins, false);
304 assert!(result.is_ok());
305
306 let resolved = result.unwrap();
307 assert_eq!(resolved.len(), 2);
308 assert_eq!(resolved[0].name(), "plugin-a");
309 assert_eq!(resolved[1].name(), "plugin-b");
310 }
311
312 #[test]
313 fn test_circular_dependency_detection() {
314 let plugin_a: Arc<dyn Plugin> = Arc::new(MockPlugin {
315 name: "plugin-a".to_string(),
316 dependencies: vec!["plugin-b".to_string()],
317 });
318
319 let plugin_b: Arc<dyn Plugin> = Arc::new(MockPlugin {
320 name: "plugin-b".to_string(),
321 dependencies: vec!["plugin-a".to_string()],
322 });
323
324 let mut plugins = HashMap::new();
325 plugins.insert("plugin-a".to_string(), plugin_a);
326 plugins.insert("plugin-b".to_string(), plugin_b);
327
328 let result = resolve_plugin_dependencies(plugins, false);
329 assert!(result.is_err());
330 }
331}