rust_rule_engine/engine/
plugin.rs

1use crate::engine::RustRuleEngine;
2use crate::errors::Result;
3use std::collections::HashMap;
4use std::sync::Arc;
5
6/// Plugin state tracking
7#[derive(Debug, Clone, PartialEq)]
8pub enum PluginState {
9    Loading,
10    Loaded,
11    Unloaded,
12    Error,
13}
14
15/// Plugin health status
16#[derive(Debug, Clone, PartialEq)]
17pub enum PluginHealth {
18    Healthy,
19    Warning(String),
20    Error(String),
21}
22
23/// Plugin metadata
24#[derive(Debug, Clone)]
25pub struct PluginMetadata {
26    pub name: String,
27    pub version: String,
28    pub description: String,
29    pub author: String,
30    pub state: PluginState,
31    pub health: PluginHealth,
32    pub actions: Vec<String>,
33    pub functions: Vec<String>,
34    pub dependencies: Vec<String>,
35}
36
37/// Plugin information for external queries
38#[derive(Debug, Clone)]
39pub struct PluginInfo {
40    pub name: String,
41    pub version: String,
42    pub description: String,
43    pub state: PluginState,
44    pub health: PluginHealth,
45}
46
47/// Core trait that all plugins must implement
48pub trait RulePlugin: Send + Sync {
49    /// Get plugin metadata
50    fn get_metadata(&self) -> &PluginMetadata;
51
52    /// Register custom actions with the engine
53    fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()>;
54
55    /// Register custom functions with the engine
56    fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
57        // Default: no functions to register
58        Ok(())
59    }
60
61    /// Called when plugin is unloaded
62    fn unload(&mut self) -> Result<()> {
63        Ok(())
64    }
65
66    /// Health check for the plugin
67    fn health_check(&mut self) -> PluginHealth {
68        PluginHealth::Healthy
69    }
70}
71
72/// Plugin statistics
73#[derive(Debug, Clone)]
74pub struct PluginStats {
75    pub total_plugins: usize,
76    pub loaded_plugins: usize,
77    pub failed_plugins: usize,
78    pub warnings: usize,
79}
80
81/// Plugin configuration
82#[derive(Debug, Clone)]
83pub struct PluginConfig {
84    pub max_plugins: usize,
85    pub enable_hot_reload: bool,
86    pub plugin_timeout_ms: u64,
87    pub safety_checks: bool,
88}
89
90impl Default for PluginConfig {
91    fn default() -> Self {
92        Self {
93            max_plugins: 50,
94            enable_hot_reload: true,
95            plugin_timeout_ms: 5000,
96            safety_checks: true,
97        }
98    }
99}
100
101/// Main plugin manager
102pub struct PluginManager {
103    plugins: HashMap<String, Arc<dyn RulePlugin>>,
104    config: PluginConfig,
105    load_order: Vec<String>,
106}
107
108impl PluginManager {
109    pub fn new(config: PluginConfig) -> Self {
110        Self {
111            plugins: HashMap::new(),
112            config,
113            load_order: Vec::new(),
114        }
115    }
116
117    pub fn with_default_config() -> Self {
118        Self::new(PluginConfig::default())
119    }
120
121    /// Load a plugin into the manager
122    pub fn load_plugin(&mut self, plugin: Arc<dyn RulePlugin>) -> Result<()> {
123        let metadata = plugin.get_metadata();
124        let name = metadata.name.clone();
125
126        // Check if already loaded
127        if self.plugins.contains_key(&name) {
128            return Err(crate::errors::RuleEngineError::PluginError {
129                message: format!("Plugin '{}' is already loaded", name),
130            });
131        }
132
133        // Check plugin limit
134        if self.plugins.len() >= self.config.max_plugins {
135            return Err(crate::errors::RuleEngineError::PluginError {
136                message: format!("Maximum plugin limit ({}) reached", self.config.max_plugins),
137            });
138        }
139
140        // Check dependencies
141        if self.config.safety_checks {
142            self.validate_dependencies(&metadata.dependencies)?;
143        }
144
145        // Store plugin
146        self.plugins.insert(name.clone(), plugin);
147        self.load_order.push(name.clone());
148
149        Ok(())
150    }
151
152    /// Unload a plugin
153    pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
154        let plugin = self.plugins.get_mut(name).ok_or_else(|| {
155            crate::errors::RuleEngineError::PluginError {
156                message: format!("Plugin '{}' not found", name),
157            }
158        })?;
159
160        // Note: We need a mutable reference to call unload
161        // This is a design limitation - we'll work around it
162        self.plugins.remove(name);
163        self.load_order.retain(|n| n != name);
164
165        Ok(())
166    }
167
168    /// Hot reload a plugin
169    pub fn hot_reload_plugin(&mut self, name: &str, new_plugin: Arc<dyn RulePlugin>) -> Result<()> {
170        // Remove old plugin
171        self.unload_plugin(name)?;
172
173        // Load new plugin (registration happens in engine)
174        self.load_plugin(new_plugin)?;
175
176        Ok(())
177    }
178
179    /// Get plugin metadata
180    pub fn get_plugin_info(&self, name: &str) -> Option<&PluginMetadata> {
181        self.plugins.get(name).map(|p| p.get_metadata())
182    }
183
184    /// List all loaded plugins  
185    pub fn list_plugins(&self) -> Vec<PluginInfo> {
186        self.plugins
187            .values()
188            .map(|plugin| {
189                let metadata = plugin.get_metadata();
190                PluginInfo {
191                    name: metadata.name.clone(),
192                    version: metadata.version.clone(),
193                    description: metadata.description.clone(),
194                    state: metadata.state.clone(),
195                    health: metadata.health.clone(),
196                }
197            })
198            .collect()
199    }
200
201    /// Health check all plugins
202    pub fn plugin_health_check(&mut self) -> HashMap<String, PluginHealth> {
203        let mut results = HashMap::new();
204
205        // For this demo, we'll return the current health from metadata
206        for plugin in self.plugins.values() {
207            let metadata = plugin.get_metadata();
208            results.insert(metadata.name.clone(), metadata.health.clone());
209        }
210
211        results
212    }
213
214    /// Validate plugin dependencies
215    fn validate_dependencies(&self, dependencies: &[String]) -> Result<()> {
216        for dep in dependencies {
217            if !self.plugins.contains_key(dep) {
218                return Err(crate::errors::RuleEngineError::PluginError {
219                    message: format!("Dependency '{}' is not loaded", dep),
220                });
221            }
222        }
223        Ok(())
224    }
225
226    /// Get plugin statistics
227    pub fn get_stats(&self) -> PluginStats {
228        let mut loaded_count = 0;
229        let mut failed_count = 0;
230        let mut warning_count = 0;
231
232        for plugin in self.plugins.values() {
233            let metadata = plugin.get_metadata();
234            match metadata.health {
235                PluginHealth::Healthy => loaded_count += 1,
236                PluginHealth::Warning(_) => warning_count += 1,
237                PluginHealth::Error(_) => failed_count += 1,
238            }
239        }
240
241        PluginStats {
242            total_plugins: self.plugins.len(),
243            loaded_plugins: loaded_count,
244            failed_plugins: failed_count,
245            warnings: warning_count,
246        }
247    }
248}
249
250impl std::fmt::Display for PluginStats {
251    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252        write!(
253            f,
254            "Plugins: {} total (✅ {} loaded, ⚠️ {} warnings, ❌ {} failed)",
255            self.total_plugins, self.loaded_plugins, self.warnings, self.failed_plugins
256        )
257    }
258}