sklears_core/plugin/
registry.rs

1//! Plugin Registry
2//!
3//! This module provides the central registry for managing plugins in the sklears
4//! plugin system. It handles plugin registration, discovery, and lifecycle management
5//! with thread-safe operations and efficient indexing.
6
7use super::core_traits::Plugin;
8use super::types_config::{PluginCategory, PluginMetadata};
9use crate::error::{Result, SklearsError};
10use std::any::TypeId;
11use std::collections::HashMap;
12use std::sync::{Arc, RwLock};
13
14/// Registry for managing plugins
15///
16/// The PluginRegistry provides centralized management of all registered plugins
17/// with thread-safe operations, efficient discovery through indexing, and
18/// comprehensive lifecycle management.
19///
20/// # Features
21///
22/// - Thread-safe plugin registration and unregistration
23/// - Category-based plugin organization
24/// - Type compatibility indexing for automatic plugin selection
25/// - Metadata caching for fast discovery
26/// - Search functionality by name and description
27/// - Validation and lifecycle management
28///
29/// # Examples
30///
31/// ```rust,no_run
32/// use sklears_core::plugin::{PluginRegistry, Plugin, PluginMetadata, PluginCategory};
33/// use sklears_core::error::Result;
34/// use std::any::TypeId;
35///
36/// // Create a registry
37/// let registry = PluginRegistry::new();
38///
39/// // List available plugins
40/// let plugins = registry.list_plugins()?;
41/// println!("Available plugins: {:?}", plugins);
42///
43/// // Search for plugins by category
44/// let algorithms = registry.get_plugins_by_category(&PluginCategory::Algorithm)?;
45/// println!("Algorithm plugins: {:?}", algorithms);
46///
47/// // Find plugins compatible with a specific type
48/// let compatible = registry.get_compatible_plugins(TypeId::of::`<f64>`())?;
49/// println!("f64-compatible plugins: {:?}", compatible);
50/// # Ok::<(), Box<dyn std::error::Error>>(())
51/// ```
52#[derive(Debug)]
53pub struct PluginRegistry {
54    /// Registered plugins (plugin_id -> plugin)
55    plugins: Arc<RwLock<HashMap<String, Box<dyn Plugin>>>>,
56    /// Plugin metadata cache for fast access
57    metadata_cache: Arc<RwLock<HashMap<String, PluginMetadata>>>,
58    /// Category index for efficient category-based discovery
59    category_index: Arc<RwLock<HashMap<PluginCategory, Vec<String>>>>,
60    /// Type compatibility index for automatic plugin selection
61    type_index: Arc<RwLock<HashMap<TypeId, Vec<String>>>>,
62}
63
64impl PluginRegistry {
65    /// Create a new plugin registry
66    ///
67    /// Initializes an empty registry with all necessary internal data structures
68    /// for efficient plugin management and discovery.
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// use sklears_core::plugin::PluginRegistry;
74    ///
75    /// let registry = PluginRegistry::new();
76    /// assert_eq!(registry.list_plugins().unwrap().len(), 0);
77    /// ```
78    pub fn new() -> Self {
79        Self {
80            plugins: Arc::new(RwLock::new(HashMap::new())),
81            metadata_cache: Arc::new(RwLock::new(HashMap::new())),
82            category_index: Arc::new(RwLock::new(HashMap::new())),
83            type_index: Arc::new(RwLock::new(HashMap::new())),
84        }
85    }
86
87    /// Register a plugin in the registry
88    ///
89    /// This method registers a new plugin with the given ID, validates its metadata,
90    /// and updates all internal indices for efficient discovery.
91    ///
92    /// # Arguments
93    ///
94    /// * `id` - Unique identifier for the plugin
95    /// * `plugin` - The plugin instance to register
96    ///
97    /// # Returns
98    ///
99    /// Ok(()) on successful registration, or an error if validation fails
100    /// or the plugin cannot be registered.
101    ///
102    /// # Examples
103    ///
104    /// ```rust,no_run
105    /// use sklears_core::plugin::{PluginRegistry, Plugin};
106    ///
107    /// let registry = PluginRegistry::new();
108    /// // registry.register("my_plugin", Box::new(my_plugin_instance))?;
109    /// # Ok::<(), Box<dyn std::error::Error>>(())
110    /// ```
111    pub fn register(&self, id: &str, plugin: Box<dyn Plugin>) -> Result<()> {
112        let metadata = plugin.metadata();
113
114        // Validate plugin before registration
115        self.validate_plugin(&metadata)?;
116
117        // Update all indices for efficient discovery
118        self.update_indices(id, &metadata)?;
119
120        // Store plugin in the main registry
121        {
122            let mut plugins = self.plugins.write().map_err(|_| {
123                SklearsError::InvalidOperation("Failed to acquire plugin registry lock".to_string())
124            })?;
125            plugins.insert(id.to_string(), plugin);
126        }
127
128        // Cache metadata for fast access
129        {
130            let mut cache = self.metadata_cache.write().map_err(|_| {
131                SklearsError::InvalidOperation("Failed to acquire metadata cache lock".to_string())
132            })?;
133            cache.insert(id.to_string(), metadata);
134        }
135
136        Ok(())
137    }
138
139    /// Unregister a plugin from the registry
140    ///
141    /// This method removes a plugin from the registry, cleans up its resources,
142    /// and updates all internal indices.
143    ///
144    /// # Arguments
145    ///
146    /// * `id` - The ID of the plugin to unregister
147    ///
148    /// # Returns
149    ///
150    /// Ok(()) on successful unregistration, or an error if the operation fails.
151    ///
152    /// # Examples
153    ///
154    /// ```rust,no_run
155    /// use sklears_core::plugin::PluginRegistry;
156    ///
157    /// let registry = PluginRegistry::new();
158    /// // First register a plugin...
159    /// // registry.register("my_plugin", Box::new(my_plugin_instance))?;
160    ///
161    /// // Then unregister it
162    /// registry.unregister("my_plugin")?;
163    /// # Ok::<(), Box<dyn std::error::Error>>(())
164    /// ```
165    pub fn unregister(&self, id: &str) -> Result<()> {
166        // Remove from main registry and get the plugin for cleanup
167        let mut plugin = {
168            let mut plugins = self.plugins.write().map_err(|_| {
169                SklearsError::InvalidOperation("Failed to acquire plugin registry lock".to_string())
170            })?;
171            plugins.remove(id)
172        };
173
174        // Cleanup plugin resources if it exists
175        if let Some(ref mut plugin) = plugin {
176            plugin.cleanup()?;
177        }
178
179        // Remove from metadata cache and get metadata for index cleanup
180        let metadata = {
181            let mut cache = self.metadata_cache.write().map_err(|_| {
182                SklearsError::InvalidOperation("Failed to acquire metadata cache lock".to_string())
183            })?;
184            cache.remove(id)
185        };
186
187        // Update indices if metadata existed
188        if let Some(metadata) = metadata {
189            self.remove_from_indices(id, &metadata)?;
190        }
191
192        Ok(())
193    }
194
195    /// Get a reference to a plugin by ID
196    ///
197    /// Note: This is a simplified implementation. In practice, you'd want
198    /// to return a proper reference or handle that manages plugin lifetime.
199    ///
200    /// # Arguments
201    ///
202    /// * `id` - The ID of the plugin to retrieve
203    ///
204    /// # Returns
205    ///
206    /// An error in this simplified implementation, as proper plugin access
207    /// requires careful lifetime management.
208    pub fn get_plugin(&self, id: &str) -> Result<Arc<RwLock<Box<dyn Plugin>>>> {
209        let plugins = self.plugins.read().map_err(|_| {
210            SklearsError::InvalidOperation("Failed to acquire plugin registry lock".to_string())
211        })?;
212
213        if plugins.contains_key(id) {
214            // Note: This is a simplified implementation
215            // In practice, you'd want to return a proper reference or clone
216            Err(SklearsError::InvalidOperation(
217                "Plugin access needs proper lifetime management".to_string(),
218            ))
219        } else {
220            Err(SklearsError::InvalidOperation(format!(
221                "Plugin '{id}' not found"
222            )))
223        }
224    }
225
226    /// List all registered plugin IDs
227    ///
228    /// Returns a vector of all plugin IDs currently registered in the system.
229    ///
230    /// # Returns
231    ///
232    /// A vector of plugin IDs, or an error if the registry cannot be accessed.
233    ///
234    /// # Examples
235    ///
236    /// ```rust
237    /// use sklears_core::plugin::PluginRegistry;
238    ///
239    /// let registry = PluginRegistry::new();
240    /// let plugins = registry.list_plugins().unwrap();
241    /// assert!(plugins.is_empty()); // No plugins registered yet
242    /// ```
243    pub fn list_plugins(&self) -> Result<Vec<String>> {
244        let plugins = self.plugins.read().map_err(|_| {
245            SklearsError::InvalidOperation("Failed to acquire plugin registry lock".to_string())
246        })?;
247        Ok(plugins.keys().cloned().collect())
248    }
249
250    /// Get all plugins in a specific category
251    ///
252    /// Returns all plugin IDs that belong to the specified category.
253    ///
254    /// # Arguments
255    ///
256    /// * `category` - The category to search for
257    ///
258    /// # Returns
259    ///
260    /// A vector of plugin IDs in the category, or an error if the index
261    /// cannot be accessed.
262    ///
263    /// # Examples
264    ///
265    /// ```rust
266    /// use sklears_core::plugin::{PluginRegistry, PluginCategory};
267    ///
268    /// let registry = PluginRegistry::new();
269    /// let algorithms = registry.get_plugins_by_category(&PluginCategory::Algorithm).unwrap();
270    /// assert!(algorithms.is_empty()); // No plugins registered yet
271    /// ```
272    pub fn get_plugins_by_category(&self, category: &PluginCategory) -> Result<Vec<String>> {
273        let index = self.category_index.read().map_err(|_| {
274            SklearsError::InvalidOperation("Failed to acquire category index lock".to_string())
275        })?;
276        Ok(index.get(category).cloned().unwrap_or_default())
277    }
278
279    /// Get plugins compatible with a specific data type
280    ///
281    /// Returns all plugin IDs that declare compatibility with the given TypeId.
282    ///
283    /// # Arguments
284    ///
285    /// * `type_id` - The TypeId to check compatibility for
286    ///
287    /// # Returns
288    ///
289    /// A vector of compatible plugin IDs, or an error if the index
290    /// cannot be accessed.
291    ///
292    /// # Examples
293    ///
294    /// ```rust
295    /// use sklears_core::plugin::PluginRegistry;
296    /// use std::any::TypeId;
297    ///
298    /// let registry = PluginRegistry::new();
299    /// let compatible = registry.get_compatible_plugins(TypeId::of::`<f64>`()).unwrap();
300    /// assert!(compatible.is_empty()); // No plugins registered yet
301    /// ```
302    pub fn get_compatible_plugins(&self, type_id: TypeId) -> Result<Vec<String>> {
303        let index = self.type_index.read().map_err(|_| {
304            SklearsError::InvalidOperation("Failed to acquire type index lock".to_string())
305        })?;
306        Ok(index.get(&type_id).cloned().unwrap_or_default())
307    }
308
309    /// Get metadata for a specific plugin
310    ///
311    /// Returns the cached metadata for the plugin with the given ID.
312    ///
313    /// # Arguments
314    ///
315    /// * `id` - The ID of the plugin
316    ///
317    /// # Returns
318    ///
319    /// The plugin's metadata, or an error if the plugin is not found.
320    ///
321    /// # Examples
322    ///
323    /// ```rust,no_run
324    /// use sklears_core::plugin::PluginRegistry;
325    ///
326    /// let registry = PluginRegistry::new();
327    /// // First register a plugin...
328    /// // registry.register("my_plugin", Box::new(my_plugin_instance))?;
329    ///
330    /// // Then get its metadata
331    /// let metadata = registry.get_metadata("my_plugin")?;
332    /// println!("Plugin: {} v{}", metadata.name, metadata.version);
333    /// # Ok::<(), Box<dyn std::error::Error>>(())
334    /// ```
335    pub fn get_metadata(&self, id: &str) -> Result<PluginMetadata> {
336        let cache = self.metadata_cache.read().map_err(|_| {
337            SklearsError::InvalidOperation("Failed to acquire metadata cache lock".to_string())
338        })?;
339        cache
340            .get(id)
341            .cloned()
342            .ok_or_else(|| SklearsError::InvalidOperation(format!("Plugin '{id}' not found")))
343    }
344
345    /// Search plugins by name or description
346    ///
347    /// Performs a case-insensitive search through plugin names and descriptions
348    /// for the given query string.
349    ///
350    /// # Arguments
351    ///
352    /// * `query` - The search query string
353    ///
354    /// # Returns
355    ///
356    /// A vector of plugin IDs that match the search query, or an error
357    /// if the search cannot be performed.
358    ///
359    /// # Examples
360    ///
361    /// ```rust,no_run
362    /// use sklears_core::plugin::PluginRegistry;
363    ///
364    /// let registry = PluginRegistry::new();
365    /// // Register some plugins first...
366    ///
367    /// // Search for plugins
368    /// let matches = registry.search_plugins("regression")?;
369    /// println!("Found regression plugins: {:?}", matches);
370    /// # Ok::<(), Box<dyn std::error::Error>>(())
371    /// ```
372    pub fn search_plugins(&self, query: &str) -> Result<Vec<String>> {
373        let cache = self.metadata_cache.read().map_err(|_| {
374            SklearsError::InvalidOperation("Failed to acquire metadata cache lock".to_string())
375        })?;
376
377        let query_lower = query.to_lowercase();
378        let matches: Vec<String> = cache
379            .iter()
380            .filter(|(_, metadata)| {
381                metadata.name.to_lowercase().contains(&query_lower)
382                    || metadata.description.to_lowercase().contains(&query_lower)
383            })
384            .map(|(id, _)| id.clone())
385            .collect();
386
387        Ok(matches)
388    }
389
390    /// Get registry statistics
391    ///
392    /// Returns information about the current state of the registry.
393    ///
394    /// # Returns
395    ///
396    /// A HashMap with statistics about the registry.
397    pub fn get_statistics(&self) -> Result<HashMap<String, usize>> {
398        let plugins = self.plugins.read().map_err(|_| {
399            SklearsError::InvalidOperation("Failed to acquire plugin registry lock".to_string())
400        })?;
401
402        let category_index = self.category_index.read().map_err(|_| {
403            SklearsError::InvalidOperation("Failed to acquire category index lock".to_string())
404        })?;
405
406        let mut stats = HashMap::new();
407        stats.insert("total_plugins".to_string(), plugins.len());
408        stats.insert("categories".to_string(), category_index.len());
409
410        Ok(stats)
411    }
412
413    /// Validate a plugin before registration
414    ///
415    /// Performs validation checks on plugin metadata to ensure it meets
416    /// the requirements for registration.
417    ///
418    /// # Arguments
419    ///
420    /// * `metadata` - The plugin metadata to validate
421    ///
422    /// # Returns
423    ///
424    /// Ok(()) if validation passes, or an error describing the validation failure.
425    fn validate_plugin(&self, metadata: &PluginMetadata) -> Result<()> {
426        if metadata.name.is_empty() {
427            return Err(SklearsError::InvalidOperation(
428                "Plugin name cannot be empty".to_string(),
429            ));
430        }
431
432        if metadata.version.is_empty() {
433            return Err(SklearsError::InvalidOperation(
434                "Plugin version cannot be empty".to_string(),
435            ));
436        }
437
438        if metadata.author.is_empty() {
439            return Err(SklearsError::InvalidOperation(
440                "Plugin author cannot be empty".to_string(),
441            ));
442        }
443
444        // Validate version format (basic check)
445        if !metadata.version.chars().any(|c| c.is_ascii_digit()) {
446            return Err(SklearsError::InvalidOperation(
447                "Plugin version must contain at least one digit".to_string(),
448            ));
449        }
450
451        Ok(())
452    }
453
454    /// Update internal indices when registering a plugin
455    ///
456    /// Updates the category and type compatibility indices to include
457    /// the newly registered plugin.
458    ///
459    /// # Arguments
460    ///
461    /// * `id` - The plugin ID
462    /// * `metadata` - The plugin metadata
463    ///
464    /// # Returns
465    ///
466    /// Ok(()) on success, or an error if the indices cannot be updated.
467    fn update_indices(&self, id: &str, metadata: &PluginMetadata) -> Result<()> {
468        // Update category index
469        {
470            let mut index = self.category_index.write().map_err(|_| {
471                SklearsError::InvalidOperation("Failed to acquire category index lock".to_string())
472            })?;
473            index
474                .entry(metadata.category.clone())
475                .or_insert_with(Vec::new)
476                .push(id.to_string());
477        }
478
479        // Update type compatibility index
480        {
481            let mut index = self.type_index.write().map_err(|_| {
482                SklearsError::InvalidOperation("Failed to acquire type index lock".to_string())
483            })?;
484            for type_id in &metadata.supported_types {
485                index
486                    .entry(*type_id)
487                    .or_insert_with(Vec::new)
488                    .push(id.to_string());
489            }
490        }
491
492        Ok(())
493    }
494
495    /// Remove plugin from indices when unregistering
496    ///
497    /// Removes the plugin from all internal indices when it's unregistered.
498    ///
499    /// # Arguments
500    ///
501    /// * `id` - The plugin ID to remove
502    /// * `metadata` - The plugin metadata
503    ///
504    /// # Returns
505    ///
506    /// Ok(()) on success, or an error if the indices cannot be updated.
507    fn remove_from_indices(&self, id: &str, metadata: &PluginMetadata) -> Result<()> {
508        // Remove from category index
509        {
510            let mut index = self.category_index.write().map_err(|_| {
511                SklearsError::InvalidOperation("Failed to acquire category index lock".to_string())
512            })?;
513            if let Some(plugins) = index.get_mut(&metadata.category) {
514                plugins.retain(|plugin_id| plugin_id != id);
515                if plugins.is_empty() {
516                    index.remove(&metadata.category);
517                }
518            }
519        }
520
521        // Remove from type index
522        {
523            let mut index = self.type_index.write().map_err(|_| {
524                SklearsError::InvalidOperation("Failed to acquire type index lock".to_string())
525            })?;
526            for type_id in &metadata.supported_types {
527                if let Some(plugins) = index.get_mut(type_id) {
528                    plugins.retain(|plugin_id| plugin_id != id);
529                    if plugins.is_empty() {
530                        index.remove(type_id);
531                    }
532                }
533            }
534        }
535
536        Ok(())
537    }
538
539    /// Check if a plugin is registered
540    ///
541    /// # Arguments
542    ///
543    /// * `id` - The plugin ID to check
544    ///
545    /// # Returns
546    ///
547    /// true if the plugin is registered, false otherwise.
548    pub fn is_registered(&self, id: &str) -> bool {
549        if let Ok(plugins) = self.plugins.read() {
550            plugins.contains_key(id)
551        } else {
552            false
553        }
554    }
555
556    /// Get the number of registered plugins
557    ///
558    /// # Returns
559    ///
560    /// The number of currently registered plugins.
561    pub fn plugin_count(&self) -> usize {
562        if let Ok(plugins) = self.plugins.read() {
563            plugins.len()
564        } else {
565            0
566        }
567    }
568}
569
570impl Default for PluginRegistry {
571    fn default() -> Self {
572        Self::new()
573    }
574}
575
576impl Clone for PluginRegistry {
577    /// Clone the registry (creates a new registry with the same structure but empty)
578    ///
579    /// Note: This doesn't clone the actual plugins, just the registry structure.
580    /// This is because plugins contain trait objects that can't be easily cloned.
581    fn clone(&self) -> Self {
582        Self::new()
583    }
584}