llm_memory_graph/plugin/
manager.rs

1//! Plugin lifecycle management
2//!
3//! This module provides the `PluginManager` which handles:
4//! - Plugin registration and deregistration
5//! - Plugin initialization and shutdown
6//! - Plugin enable/disable state
7//! - Plugin execution coordination
8//! - Version compatibility checking
9
10use super::{Plugin, PluginContext, PluginError, PluginMetadata};
11use std::collections::HashMap;
12use std::path::Path;
13use std::sync::Arc;
14use tracing::{debug, error, info, warn};
15
16/// Plugin state
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum PluginState {
19    /// Plugin is registered but not initialized
20    Registered,
21    /// Plugin is initialized and ready
22    Initialized,
23    /// Plugin is enabled and active
24    Enabled,
25    /// Plugin is disabled
26    Disabled,
27    /// Plugin encountered an error
28    Error,
29}
30
31/// Plugin wrapper with state tracking
32struct PluginWrapper {
33    plugin: Arc<dyn Plugin>,
34    state: PluginState,
35}
36
37/// Plugin manager
38///
39/// Manages the lifecycle of all plugins in the system. This includes:
40/// - Registering new plugins
41/// - Initializing plugins on startup
42/// - Enabling/disabling plugins at runtime
43/// - Executing plugin hooks
44/// - Shutting down plugins gracefully
45///
46/// # Thread Safety
47///
48/// The `PluginManager` is not thread-safe by itself. It should be wrapped
49/// in `Arc<RwLock<PluginManager>>` for concurrent access.
50///
51/// # Example
52///
53/// ```rust
54/// use llm_memory_graph::plugin::PluginManager;
55/// use std::sync::Arc;
56/// use tokio::sync::RwLock;
57///
58/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
59/// let mut manager = PluginManager::new();
60/// // Register plugins...
61/// manager.init_all().await?;
62///
63/// // Wrap for concurrent access
64/// let manager = Arc::new(RwLock::new(manager));
65/// # Ok(())
66/// # }
67/// ```
68pub struct PluginManager {
69    plugins: HashMap<String, PluginWrapper>,
70    api_version: String,
71}
72
73impl PluginManager {
74    /// Create a new plugin manager
75    pub fn new() -> Self {
76        Self {
77            plugins: HashMap::new(),
78            api_version: "1.0.0".to_string(),
79        }
80    }
81
82    /// Create a plugin manager with a specific API version
83    pub fn with_api_version(api_version: impl Into<String>) -> Self {
84        Self {
85            plugins: HashMap::new(),
86            api_version: api_version.into(),
87        }
88    }
89
90    /// Register a plugin
91    ///
92    /// Registers a new plugin with the manager. The plugin must not already
93    /// be registered. After registration, the plugin is in the `Registered` state
94    /// and must be initialized before it can be used.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if:
99    /// - The plugin is already registered
100    /// - The plugin's API version is incompatible
101    pub fn register(&mut self, plugin: Arc<dyn Plugin>) -> Result<(), PluginError> {
102        let metadata = plugin.metadata();
103        let name = metadata.name.clone();
104
105        // Check if already registered
106        if self.plugins.contains_key(&name) {
107            return Err(PluginError::AlreadyRegistered(name));
108        }
109
110        info!(
111            "Registering plugin: {} v{} by {}",
112            name, metadata.version, metadata.author
113        );
114
115        // Check API version compatibility
116        if !self.is_compatible_version(&metadata.api_version) {
117            return Err(PluginError::VersionMismatch(format!(
118                "Plugin {} requires API version {}, but {} is supported",
119                name, metadata.api_version, self.api_version
120            )));
121        }
122
123        // Register the plugin
124        self.plugins.insert(
125            name.clone(),
126            PluginWrapper {
127                plugin,
128                state: PluginState::Registered,
129            },
130        );
131
132        debug!("Plugin {} registered successfully", name);
133        Ok(())
134    }
135
136    /// Unregister a plugin
137    ///
138    /// Removes a plugin from the manager. The plugin must be disabled before
139    /// it can be unregistered.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if:
144    /// - The plugin is not found
145    /// - The plugin is still enabled
146    pub fn unregister(&mut self, name: &str) -> Result<(), PluginError> {
147        let wrapper = self
148            .plugins
149            .get(name)
150            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
151
152        if wrapper.state == PluginState::Enabled {
153            return Err(PluginError::General(format!(
154                "Plugin {} must be disabled before unregistering",
155                name
156            )));
157        }
158
159        self.plugins.remove(name);
160        info!("Unregistered plugin: {}", name);
161        Ok(())
162    }
163
164    /// Initialize a specific plugin
165    ///
166    /// Initializes a registered plugin, calling its `init()` method.
167    /// After successful initialization, the plugin is in the `Initialized` state.
168    pub async fn initialize(&mut self, name: &str) -> Result<(), PluginError> {
169        let wrapper = self
170            .plugins
171            .get_mut(name)
172            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
173
174        if wrapper.state != PluginState::Registered {
175            return Ok(()); // Already initialized
176        }
177
178        info!("Initializing plugin: {}", name);
179
180        // Call init on the plugin (plugins use interior mutability for state changes)
181        match wrapper.plugin.init().await {
182            Ok(()) => {
183                wrapper.state = PluginState::Initialized;
184                info!("Plugin {} initialized successfully", name);
185                Ok(())
186            }
187            Err(e) => {
188                wrapper.state = PluginState::Error;
189                error!("Failed to initialize plugin {}: {}", name, e);
190                Err(e)
191            }
192        }
193    }
194
195    /// Enable a plugin
196    ///
197    /// Enables an initialized plugin, making it active and ready to execute hooks.
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if:
202    /// - The plugin is not found
203    /// - The plugin is not initialized
204    pub fn enable(&mut self, name: &str) -> Result<(), PluginError> {
205        let wrapper = self
206            .plugins
207            .get_mut(name)
208            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
209
210        match wrapper.state {
211            PluginState::Initialized | PluginState::Disabled => {
212                wrapper.state = PluginState::Enabled;
213                info!("Enabled plugin: {}", name);
214                Ok(())
215            }
216            PluginState::Enabled => {
217                debug!("Plugin {} is already enabled", name);
218                Ok(())
219            }
220            PluginState::Registered => Err(PluginError::General(format!(
221                "Plugin {} must be initialized before enabling",
222                name
223            ))),
224            PluginState::Error => Err(PluginError::General(format!(
225                "Plugin {} is in error state",
226                name
227            ))),
228        }
229    }
230
231    /// Disable a plugin
232    ///
233    /// Disables an enabled plugin, preventing it from executing hooks.
234    ///
235    /// # Errors
236    ///
237    /// Returns an error if the plugin is not found
238    pub fn disable(&mut self, name: &str) -> Result<(), PluginError> {
239        let wrapper = self
240            .plugins
241            .get_mut(name)
242            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
243
244        if wrapper.state == PluginState::Enabled {
245            wrapper.state = PluginState::Disabled;
246            info!("Disabled plugin: {}", name);
247        } else {
248            debug!("Plugin {} is already disabled", name);
249        }
250
251        Ok(())
252    }
253
254    /// Get active plugins
255    ///
256    /// Returns a list of all enabled plugins that are ready to execute.
257    pub fn active_plugins(&self) -> Vec<Arc<dyn Plugin>> {
258        self.plugins
259            .iter()
260            .filter(|(_, wrapper)| wrapper.state == PluginState::Enabled)
261            .map(|(_, wrapper)| Arc::clone(&wrapper.plugin))
262            .collect()
263    }
264
265    /// Get all plugins regardless of state
266    pub fn all_plugins(&self) -> Vec<(PluginMetadata, PluginState)> {
267        self.plugins
268            .values()
269            .map(|wrapper| (wrapper.plugin.metadata().clone(), wrapper.state))
270            .collect()
271    }
272
273    /// Get plugin state
274    pub fn get_state(&self, name: &str) -> Option<PluginState> {
275        self.plugins.get(name).map(|wrapper| wrapper.state)
276    }
277
278    /// Check if a plugin is enabled
279    pub fn is_enabled(&self, name: &str) -> bool {
280        self.plugins
281            .get(name)
282            .map(|wrapper| wrapper.state == PluginState::Enabled)
283            .unwrap_or(false)
284    }
285
286    /// Initialize all registered plugins
287    ///
288    /// Initializes all plugins that are in the `Registered` state.
289    /// Continues even if some plugins fail to initialize.
290    ///
291    /// # Errors
292    ///
293    /// Returns an error if any plugin fails to initialize.
294    /// The error contains information about the first failure encountered.
295    pub async fn init_all(&mut self) -> Result<(), PluginError> {
296        let plugin_names: Vec<String> = self
297            .plugins
298            .iter()
299            .filter(|(_, wrapper)| wrapper.state == PluginState::Registered)
300            .map(|(name, _)| name.clone())
301            .collect();
302
303        let mut errors = Vec::new();
304
305        for name in plugin_names {
306            if let Err(e) = self.initialize(&name).await {
307                errors.push((name, e));
308            }
309        }
310
311        if !errors.is_empty() {
312            let (name, error) = &errors[0];
313            return Err(PluginError::InitFailed(format!(
314                "Failed to initialize plugin {}: {}",
315                name, error
316            )));
317        }
318
319        info!("All plugins initialized successfully");
320        Ok(())
321    }
322
323    /// Enable all initialized plugins
324    pub fn enable_all(&mut self) -> Result<(), PluginError> {
325        let plugin_names: Vec<String> = self
326            .plugins
327            .iter()
328            .filter(|(_, wrapper)| wrapper.state == PluginState::Initialized)
329            .map(|(name, _)| name.clone())
330            .collect();
331
332        for name in plugin_names {
333            self.enable(&name)?;
334        }
335
336        info!("All plugins enabled");
337        Ok(())
338    }
339
340    /// Disable all plugins
341    pub fn disable_all(&mut self) -> Result<(), PluginError> {
342        let plugin_names: Vec<String> = self.plugins.keys().cloned().collect();
343
344        for name in plugin_names {
345            self.disable(&name)?;
346        }
347
348        info!("All plugins disabled");
349        Ok(())
350    }
351
352    /// Shutdown all plugins
353    ///
354    /// Calls `shutdown()` on all plugins and removes them from the manager.
355    pub async fn shutdown_all(&mut self) -> Result<(), PluginError> {
356        let plugin_names: Vec<String> = self.plugins.keys().cloned().collect();
357
358        for name in plugin_names {
359            info!("Shutting down plugin: {}", name);
360
361            if let Some(wrapper) = self.plugins.get(&name) {
362                // Call shutdown on the plugin
363                if let Err(e) = wrapper.plugin.shutdown().await {
364                    error!("Error shutting down plugin {}: {}", name, e);
365                }
366            }
367        }
368
369        self.plugins.clear();
370        info!("All plugins shut down");
371        Ok(())
372    }
373
374    /// Execute before hooks for all active plugins
375    ///
376    /// Executes the specified hook on all enabled plugins in registration order.
377    /// If any plugin returns an error, execution stops and the error is returned.
378    pub async fn execute_before_hooks(
379        &self,
380        hook_name: &str,
381        context: &PluginContext,
382    ) -> Result<(), PluginError> {
383        for plugin in self.active_plugins() {
384            if let Err(e) = plugin.before_hook(hook_name, context).await {
385                error!(
386                    "Plugin {} failed on hook {}: {}",
387                    plugin.metadata().name,
388                    hook_name,
389                    e
390                );
391                return Err(e);
392            }
393        }
394        Ok(())
395    }
396
397    /// Execute after hooks for all active plugins
398    ///
399    /// Executes the specified hook on all enabled plugins in registration order.
400    /// Unlike before hooks, errors in after hooks are logged but don't stop execution.
401    pub async fn execute_after_hooks(
402        &self,
403        hook_name: &str,
404        context: &PluginContext,
405    ) -> Result<(), PluginError> {
406        for plugin in self.active_plugins() {
407            if let Err(e) = plugin.after_hook(hook_name, context).await {
408                // After hooks should not fail the operation
409                warn!(
410                    "Plugin {} failed on after hook {}: {}",
411                    plugin.metadata().name,
412                    hook_name,
413                    e
414                );
415            }
416        }
417        Ok(())
418    }
419
420    /// Load plugins from directory (dynamic loading - future)
421    ///
422    /// This is a placeholder for future dynamic plugin loading functionality.
423    /// Currently, plugins must be compiled into the application.
424    pub fn load_from_directory(&mut self, _path: impl AsRef<Path>) -> Result<(), PluginError> {
425        // TODO: Implement dynamic plugin loading using libloading or similar
426        warn!("Dynamic plugin loading not yet implemented");
427        Ok(())
428    }
429
430    /// List all plugins with their metadata and state
431    pub fn list_plugins(&self) -> Vec<(PluginMetadata, PluginState)> {
432        self.all_plugins()
433    }
434
435    /// Get plugin count by state
436    pub fn count_by_state(&self) -> HashMap<PluginState, usize> {
437        let mut counts = HashMap::new();
438        for wrapper in self.plugins.values() {
439            *counts.entry(wrapper.state).or_insert(0) += 1;
440        }
441        counts
442    }
443
444    /// Check if API version is compatible
445    fn is_compatible_version(&self, plugin_version: &str) -> bool {
446        // For now, we only support exact version match
447        // TODO: Implement semantic versioning comparison
448        plugin_version == self.api_version
449    }
450}
451
452impl Default for PluginManager {
453    fn default() -> Self {
454        Self::new()
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461    use crate::plugin::{PluginBuilder, PluginMetadata};
462    use async_trait::async_trait;
463
464    struct MockPlugin {
465        metadata: PluginMetadata,
466    }
467
468    impl MockPlugin {
469        fn new(name: &str) -> Self {
470            let metadata = PluginBuilder::new(name, "1.0.0")
471                .author("Test")
472                .description("Test plugin")
473                .build();
474            Self { metadata }
475        }
476    }
477
478    #[async_trait]
479    impl Plugin for MockPlugin {
480        fn metadata(&self) -> &PluginMetadata {
481            &self.metadata
482        }
483    }
484
485    #[tokio::test]
486    async fn test_plugin_registration() {
487        let mut manager = PluginManager::new();
488        let plugin = Arc::new(MockPlugin::new("test_plugin"));
489
490        assert!(manager.register(plugin).is_ok());
491        assert!(manager.plugins.contains_key("test_plugin"));
492    }
493
494    #[tokio::test]
495    async fn test_plugin_lifecycle() {
496        let mut manager = PluginManager::new();
497        let plugin = Arc::new(MockPlugin::new("test_plugin"));
498
499        manager.register(plugin).unwrap();
500        assert_eq!(
501            manager.get_state("test_plugin"),
502            Some(PluginState::Registered)
503        );
504
505        manager.initialize("test_plugin").await.unwrap();
506        assert_eq!(
507            manager.get_state("test_plugin"),
508            Some(PluginState::Initialized)
509        );
510
511        manager.enable("test_plugin").unwrap();
512        assert_eq!(manager.get_state("test_plugin"), Some(PluginState::Enabled));
513        assert!(manager.is_enabled("test_plugin"));
514
515        manager.disable("test_plugin").unwrap();
516        assert_eq!(
517            manager.get_state("test_plugin"),
518            Some(PluginState::Disabled)
519        );
520        assert!(!manager.is_enabled("test_plugin"));
521    }
522
523    #[tokio::test]
524    async fn test_active_plugins() {
525        let mut manager = PluginManager::new();
526        let plugin1 = Arc::new(MockPlugin::new("plugin1"));
527        let plugin2 = Arc::new(MockPlugin::new("plugin2"));
528
529        manager.register(plugin1).unwrap();
530        manager.register(plugin2).unwrap();
531
532        manager.initialize("plugin1").await.unwrap();
533        manager.initialize("plugin2").await.unwrap();
534
535        manager.enable("plugin1").unwrap();
536
537        let active = manager.active_plugins();
538        assert_eq!(active.len(), 1);
539        assert_eq!(active[0].metadata().name, "plugin1");
540    }
541}