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}