fusabi_plugin_runtime/
registry.rs

1//! Plugin registry for managing loaded plugins.
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use dashmap::DashMap;
7
8use crate::error::{Error, Result};
9use crate::lifecycle::{LifecycleHooks, LifecycleState};
10use crate::plugin::{Plugin, PluginHandle, PluginInfo};
11
12/// Configuration for the plugin registry.
13#[derive(Debug, Clone)]
14pub struct RegistryConfig {
15    /// Maximum number of plugins allowed.
16    pub max_plugins: usize,
17    /// Whether to allow plugin overwrites.
18    pub allow_overwrite: bool,
19    /// Whether to automatically unload stopped plugins.
20    pub auto_unload_stopped: bool,
21}
22
23impl Default for RegistryConfig {
24    fn default() -> Self {
25        Self {
26            max_plugins: 100,
27            allow_overwrite: false,
28            auto_unload_stopped: false,
29        }
30    }
31}
32
33impl RegistryConfig {
34    /// Create a new registry configuration.
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Set the maximum number of plugins.
40    pub fn with_max_plugins(mut self, max: usize) -> Self {
41        self.max_plugins = max;
42        self
43    }
44
45    /// Allow plugin overwrites.
46    pub fn with_allow_overwrite(mut self, allow: bool) -> Self {
47        self.allow_overwrite = allow;
48        self
49    }
50
51    /// Enable auto-unload for stopped plugins.
52    pub fn with_auto_unload_stopped(mut self, auto: bool) -> Self {
53        self.auto_unload_stopped = auto;
54        self
55    }
56}
57
58/// Registry statistics.
59#[derive(Debug, Clone, Default)]
60pub struct RegistryStats {
61    /// Total plugins registered.
62    pub total: usize,
63    /// Plugins currently running.
64    pub running: usize,
65    /// Plugins stopped.
66    pub stopped: usize,
67    /// Plugins in error state.
68    pub error: usize,
69    /// Plugins unloaded.
70    pub unloaded: usize,
71}
72
73/// Plugin registry for managing loaded plugins.
74pub struct PluginRegistry {
75    config: RegistryConfig,
76    plugins: DashMap<String, PluginHandle>,
77    hooks: Arc<LifecycleHooks>,
78}
79
80impl PluginRegistry {
81    /// Create a new plugin registry.
82    pub fn new(config: RegistryConfig) -> Self {
83        Self {
84            config,
85            plugins: DashMap::new(),
86            hooks: Arc::new(LifecycleHooks::new()),
87        }
88    }
89
90    /// Create with default configuration.
91    pub fn default_config() -> Self {
92        Self::new(RegistryConfig::default())
93    }
94
95    /// Get the registry configuration.
96    pub fn config(&self) -> &RegistryConfig {
97        &self.config
98    }
99
100    /// Register a plugin.
101    pub fn register(&self, plugin: PluginHandle) -> Result<()> {
102        let name = plugin.name();
103
104        // Check capacity
105        if self.plugins.len() >= self.config.max_plugins {
106            return Err(Error::Registry(format!(
107                "registry full: max {} plugins",
108                self.config.max_plugins
109            )));
110        }
111
112        // Check for existing
113        if self.plugins.contains_key(&name) {
114            if !self.config.allow_overwrite {
115                return Err(Error::PluginAlreadyLoaded(name));
116            }
117
118            // Unload existing
119            if let Some((_, existing)) = self.plugins.remove(&name) {
120                let _ = existing.inner().unload();
121            }
122        }
123
124        self.plugins.insert(name.clone(), plugin);
125        self.hooks.emit_created(&name);
126
127        Ok(())
128    }
129
130    /// Unregister a plugin by name.
131    pub fn unregister(&self, name: &str) -> Result<PluginHandle> {
132        let (_, plugin) = self
133            .plugins
134            .remove(name)
135            .ok_or_else(|| Error::plugin_not_found(name))?;
136
137        // Unload the plugin
138        let _ = plugin.inner().unload();
139        self.hooks.emit_unloaded(name);
140
141        Ok(plugin)
142    }
143
144    /// Get a plugin by name.
145    pub fn get(&self, name: &str) -> Option<PluginHandle> {
146        self.plugins.get(name).map(|r| r.clone())
147    }
148
149    /// Check if a plugin exists.
150    pub fn contains(&self, name: &str) -> bool {
151        self.plugins.contains_key(name)
152    }
153
154    /// Get all plugin names.
155    pub fn names(&self) -> Vec<String> {
156        self.plugins.iter().map(|r| r.key().clone()).collect()
157    }
158
159    /// Get all plugins.
160    pub fn all(&self) -> Vec<PluginHandle> {
161        self.plugins.iter().map(|r| r.value().clone()).collect()
162    }
163
164    /// Get plugins by state.
165    pub fn by_state(&self, state: LifecycleState) -> Vec<PluginHandle> {
166        self.plugins
167            .iter()
168            .filter(|r| r.state() == state)
169            .map(|r| r.value().clone())
170            .collect()
171    }
172
173    /// Get running plugins.
174    pub fn running(&self) -> Vec<PluginHandle> {
175        self.by_state(LifecycleState::Running)
176    }
177
178    /// Get plugin count.
179    pub fn len(&self) -> usize {
180        self.plugins.len()
181    }
182
183    /// Check if registry is empty.
184    pub fn is_empty(&self) -> bool {
185        self.plugins.is_empty()
186    }
187
188    /// Get registry statistics.
189    pub fn stats(&self) -> RegistryStats {
190        let mut stats = RegistryStats::default();
191        stats.total = self.plugins.len();
192
193        for entry in self.plugins.iter() {
194            match entry.state() {
195                LifecycleState::Running => stats.running += 1,
196                LifecycleState::Stopped => stats.stopped += 1,
197                LifecycleState::Error => stats.error += 1,
198                LifecycleState::Unloaded => stats.unloaded += 1,
199                _ => {}
200            }
201        }
202
203        stats
204    }
205
206    /// Get all plugin info.
207    pub fn info(&self) -> Vec<PluginInfo> {
208        self.plugins.iter().map(|r| r.info()).collect()
209    }
210
211    /// Start all stopped plugins.
212    pub fn start_all(&self) -> Vec<Result<()>> {
213        self.plugins
214            .iter()
215            .filter(|r| r.state() == LifecycleState::Initialized)
216            .map(|r| {
217                let plugin = r.value();
218                plugin.inner().start()
219            })
220            .collect()
221    }
222
223    /// Stop all running plugins.
224    pub fn stop_all(&self) -> Vec<Result<()>> {
225        self.plugins
226            .iter()
227            .filter(|r| r.state() == LifecycleState::Running)
228            .map(|r| {
229                let plugin = r.value();
230                plugin.inner().stop()
231            })
232            .collect()
233    }
234
235    /// Unload all plugins.
236    pub fn unload_all(&self) {
237        for entry in self.plugins.iter() {
238            let _ = entry.value().inner().unload();
239        }
240        self.plugins.clear();
241    }
242
243    /// Reload a plugin by name.
244    pub fn reload(&self, name: &str) -> Result<()> {
245        let plugin = self
246            .get(name)
247            .ok_or_else(|| Error::plugin_not_found(name))?;
248
249        plugin.inner().reload()?;
250
251        let info = plugin.info();
252        self.hooks.emit_reloaded(name, info.reload_count);
253
254        Ok(())
255    }
256
257    /// Reload all plugins.
258    pub fn reload_all(&self) -> Vec<Result<()>> {
259        self.plugins
260            .iter()
261            .map(|r| {
262                let name = r.key().clone();
263                self.reload(&name)
264            })
265            .collect()
266    }
267
268    /// Find plugins by tag.
269    pub fn find_by_tag(&self, tag: &str) -> Vec<PluginHandle> {
270        self.plugins
271            .iter()
272            .filter(|r| {
273                r.value()
274                    .inner()
275                    .manifest()
276                    .tags
277                    .contains(&tag.to_string())
278            })
279            .map(|r| r.value().clone())
280            .collect()
281    }
282
283    /// Find plugins by capability.
284    pub fn find_by_capability(&self, cap: &str) -> Vec<PluginHandle> {
285        self.plugins
286            .iter()
287            .filter(|r| r.value().inner().requires_capability(cap))
288            .map(|r| r.value().clone())
289            .collect()
290    }
291
292    /// Clean up unloaded and error plugins.
293    pub fn cleanup(&self) -> usize {
294        let to_remove: Vec<String> = self
295            .plugins
296            .iter()
297            .filter(|r| {
298                let state = r.state();
299                state == LifecycleState::Unloaded
300                    || (self.config.auto_unload_stopped && state == LifecycleState::Stopped)
301            })
302            .map(|r| r.key().clone())
303            .collect();
304
305        let count = to_remove.len();
306        for name in to_remove {
307            self.plugins.remove(&name);
308        }
309
310        count
311    }
312}
313
314impl std::fmt::Debug for PluginRegistry {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        f.debug_struct("PluginRegistry")
317            .field("config", &self.config)
318            .field("plugin_count", &self.plugins.len())
319            .finish()
320    }
321}
322
323impl Drop for PluginRegistry {
324    fn drop(&mut self) {
325        // Unload all plugins on drop
326        for entry in self.plugins.iter() {
327            let _ = entry.value().inner().unload();
328        }
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use crate::manifest::ManifestBuilder;
336
337    fn create_test_plugin(name: &str) -> PluginHandle {
338        let manifest = ManifestBuilder::new(name, "1.0.0")
339            .source("test.fsx")
340            .build_unchecked();
341        PluginHandle::new(Plugin::new(manifest))
342    }
343
344    #[test]
345    fn test_registry_creation() {
346        let registry = PluginRegistry::default_config();
347        assert!(registry.is_empty());
348        assert_eq!(registry.len(), 0);
349    }
350
351    #[test]
352    fn test_register_plugin() {
353        let registry = PluginRegistry::default_config();
354        let plugin = create_test_plugin("test-plugin");
355
356        registry.register(plugin).unwrap();
357
358        assert!(registry.contains("test-plugin"));
359        assert_eq!(registry.len(), 1);
360    }
361
362    #[test]
363    fn test_register_duplicate() {
364        let registry = PluginRegistry::default_config();
365
366        let plugin1 = create_test_plugin("test-plugin");
367        let plugin2 = create_test_plugin("test-plugin");
368
369        registry.register(plugin1).unwrap();
370        let result = registry.register(plugin2);
371
372        assert!(matches!(result, Err(Error::PluginAlreadyLoaded(_))));
373    }
374
375    #[test]
376    fn test_register_duplicate_with_overwrite() {
377        let config = RegistryConfig::new().with_allow_overwrite(true);
378        let registry = PluginRegistry::new(config);
379
380        let plugin1 = create_test_plugin("test-plugin");
381        let id1 = plugin1.id();
382
383        let plugin2 = create_test_plugin("test-plugin");
384        let id2 = plugin2.id();
385
386        registry.register(plugin1).unwrap();
387        registry.register(plugin2).unwrap();
388
389        let plugin = registry.get("test-plugin").unwrap();
390        assert_eq!(plugin.id(), id2);
391        assert_ne!(plugin.id(), id1);
392    }
393
394    #[test]
395    fn test_unregister_plugin() {
396        let registry = PluginRegistry::default_config();
397        let plugin = create_test_plugin("test-plugin");
398
399        registry.register(plugin).unwrap();
400        assert!(registry.contains("test-plugin"));
401
402        registry.unregister("test-plugin").unwrap();
403        assert!(!registry.contains("test-plugin"));
404    }
405
406    #[test]
407    fn test_unregister_nonexistent() {
408        let registry = PluginRegistry::default_config();
409        let result = registry.unregister("nonexistent");
410        assert!(matches!(result, Err(Error::PluginNotFound(_))));
411    }
412
413    #[test]
414    fn test_get_all_plugins() {
415        let registry = PluginRegistry::default_config();
416
417        registry.register(create_test_plugin("plugin-1")).unwrap();
418        registry.register(create_test_plugin("plugin-2")).unwrap();
419        registry.register(create_test_plugin("plugin-3")).unwrap();
420
421        let all = registry.all();
422        assert_eq!(all.len(), 3);
423
424        let names = registry.names();
425        assert!(names.contains(&"plugin-1".to_string()));
426        assert!(names.contains(&"plugin-2".to_string()));
427        assert!(names.contains(&"plugin-3".to_string()));
428    }
429
430    #[test]
431    fn test_registry_stats() {
432        let registry = PluginRegistry::default_config();
433
434        registry.register(create_test_plugin("plugin-1")).unwrap();
435        registry.register(create_test_plugin("plugin-2")).unwrap();
436
437        let stats = registry.stats();
438        assert_eq!(stats.total, 2);
439    }
440
441    #[test]
442    fn test_max_plugins() {
443        let config = RegistryConfig::new().with_max_plugins(2);
444        let registry = PluginRegistry::new(config);
445
446        registry.register(create_test_plugin("plugin-1")).unwrap();
447        registry.register(create_test_plugin("plugin-2")).unwrap();
448
449        let result = registry.register(create_test_plugin("plugin-3"));
450        assert!(matches!(result, Err(Error::Registry(_))));
451    }
452}