leptos_helios/
plugin_system.rs

1//! Plugin System Architecture
2//!
3//! This module provides a comprehensive plugin architecture for Helios that allows
4//! third-party developers to extend functionality while maintaining type safety,
5//! performance, and security. The plugin system supports:
6//!
7//! - **Chart Type Plugins**: Custom chart types and rendering
8//! - **Data Source Plugins**: New data source adapters
9//! - **Transform Plugins**: Custom data transformations
10//! - **Export Plugins**: Additional export formats
11//! - **Intelligence Plugins**: ML/AI capabilities
12//! - **Theme Plugins**: Custom styling and themes
13//!
14//! ## Architecture Overview
15//!
16//! The plugin system uses a trait-based approach with dynamic loading capabilities:
17//!
18//! ```ignore
19//! use leptos_helios::plugin_system::*;
20//! use leptos_helios::chart::{ChartSpec, MarkType};
21//!
22//! // Define a custom chart plugin
23//! #[derive(Debug, Clone)]
24//! pub struct CustomChartPlugin {
25//!     name: String,
26//     version: String,
27//     capabilities: PluginCapabilities,
28//! }
29//!
30//! impl ChartPlugin for CustomChartPlugin {
31//!     fn render(&self, spec: &ChartSpec, context: &RenderContext) -> Result<RenderResult, PluginError> {
32//!         // Custom rendering logic
33//!         Ok(RenderResult::success())
34//!     }
35//! }
36//!
37//! // Register the plugin
38//! let plugin_manager = PluginManager::new();
39//! plugin_manager.register_chart_plugin(Box::new(CustomChartPlugin::new()));
40//! ```
41
42use crate::chart::{ChartSpec, MarkType};
43use crate::data_sources::DataSource;
44use crate::export_system::{ExportFormat, ExportResult};
45use crate::styling::ChartTheme;
46use serde::{Deserialize, Serialize};
47use std::any::TypeId;
48use std::collections::HashMap;
49use std::fmt;
50use std::sync::{Arc, RwLock};
51use tokio::sync::RwLock as AsyncRwLock;
52
53/// Plugin system errors
54#[derive(Debug, Clone, thiserror::Error)]
55pub enum PluginError {
56    #[error("Plugin not found: {0}")]
57    PluginNotFound(String),
58
59    #[error("Plugin registration failed: {0}")]
60    RegistrationFailed(String),
61
62    #[error("Plugin execution failed: {0}")]
63    ExecutionFailed(String),
64
65    #[error("Plugin validation failed: {0}")]
66    ValidationFailed(String),
67
68    #[error("Plugin compatibility error: {0}")]
69    CompatibilityError(String),
70
71    #[error("Plugin security violation: {0}")]
72    SecurityViolation(String),
73
74    #[error("Plugin resource limit exceeded: {0}")]
75    ResourceLimitExceeded(String),
76
77    #[error("Plugin dependency missing: {0}")]
78    DependencyMissing(String),
79}
80
81/// Plugin metadata and capabilities
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct PluginMetadata {
84    pub name: String,
85    pub version: String,
86    pub description: String,
87    pub author: String,
88    pub license: String,
89    pub homepage: Option<String>,
90    pub repository: Option<String>,
91    pub dependencies: Vec<PluginDependency>,
92    pub capabilities: PluginCapabilities,
93    pub security_level: SecurityLevel,
94    pub performance_impact: PerformanceImpact,
95}
96
97/// Plugin dependency information
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct PluginDependency {
100    pub name: String,
101    pub version: String,
102    pub optional: bool,
103}
104
105/// Plugin capabilities enumeration
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
107pub enum PluginCapability {
108    /// Custom chart types and rendering
109    ChartRendering,
110    /// Data source adapters
111    DataSource,
112    /// Data transformation operations
113    DataTransform,
114    /// Export format support
115    ExportFormat,
116    /// Machine learning capabilities
117    MLIntelligence,
118    /// Custom themes and styling
119    Theming,
120    /// Interactive features
121    Interaction,
122    /// Performance optimizations
123    Performance,
124    /// Security features
125    Security,
126    /// Accessibility enhancements
127    Accessibility,
128}
129
130/// Collection of plugin capabilities
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct PluginCapabilities {
133    pub capabilities: Vec<PluginCapability>,
134    pub max_data_points: Option<u64>,
135    pub supported_formats: Vec<String>,
136    pub performance_requirements: PerformanceRequirements,
137}
138
139/// Performance requirements for plugins
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct PerformanceRequirements {
142    pub max_memory_mb: Option<u64>,
143    pub max_execution_time_ms: Option<u64>,
144    pub max_cpu_usage_percent: Option<u8>,
145    pub requires_gpu: bool,
146}
147
148/// Security level for plugins
149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
150pub enum SecurityLevel {
151    /// Trusted plugins with full system access
152    Trusted,
153    /// Sandboxed plugins with limited access
154    Sandboxed,
155    /// Untrusted plugins with minimal access
156    Untrusted,
157}
158
159/// Performance impact classification
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
161pub enum PerformanceImpact {
162    /// Minimal performance impact
163    Minimal,
164    /// Moderate performance impact
165    Moderate,
166    /// High performance impact
167    High,
168    /// Critical performance impact
169    Critical,
170}
171
172/// Plugin lifecycle events
173#[derive(Debug, Clone)]
174pub enum PluginEvent {
175    /// Plugin registered
176    Registered { name: String, version: String },
177    /// Plugin activated
178    Activated { name: String },
179    /// Plugin deactivated
180    Deactivated { name: String },
181    /// Plugin error occurred
182    Error { name: String, error: PluginError },
183    /// Plugin performance warning
184    PerformanceWarning { name: String, message: String },
185}
186
187/// Plugin event handler trait
188pub trait PluginEventHandler: Send + Sync {
189    fn handle_event(&self, event: PluginEvent);
190}
191
192/// Base plugin trait that all plugins must implement
193pub trait Plugin: Send + Sync + fmt::Debug {
194    /// Get plugin metadata
195    fn metadata(&self) -> &PluginMetadata;
196
197    /// Initialize the plugin
198    fn initialize(&mut self) -> Result<(), PluginError>;
199
200    /// Cleanup plugin resources
201    fn cleanup(&mut self) -> Result<(), PluginError>;
202
203    /// Check if plugin is compatible with current system
204    fn is_compatible(&self, system_info: &SystemInfo) -> bool;
205
206    /// Get plugin type ID for dynamic casting
207    fn type_id(&self) -> TypeId;
208
209    /// Clone the plugin (for dynamic dispatch)
210    fn clone_plugin(&self) -> Box<dyn Plugin>;
211
212    /// Get plugin as any type for downcasting
213    fn as_any(&self) -> &dyn std::any::Any;
214}
215
216/// System information for compatibility checking
217#[derive(Debug, Clone)]
218pub struct SystemInfo {
219    pub helios_version: String,
220    pub rust_version: String,
221    pub platform: String,
222    pub features: Vec<String>,
223    pub available_memory_mb: u64,
224    pub gpu_available: bool,
225}
226
227/// Chart plugin trait for custom chart types
228pub trait ChartPlugin: Plugin {
229    /// Get supported mark types
230    fn supported_marks(&self) -> Vec<MarkType>;
231
232    /// Render a chart with the plugin
233    fn render(
234        &self,
235        spec: &ChartSpec,
236        context: &RenderContext,
237    ) -> Result<RenderResult, PluginError>;
238
239    /// Validate chart specification for this plugin
240    fn validate_spec(&self, spec: &ChartSpec) -> Result<(), PluginError>;
241
242    /// Get rendering performance estimate
243    fn estimate_performance(&self, spec: &ChartSpec) -> PerformanceEstimate;
244}
245
246/// Data source plugin trait
247pub trait DataSourcePlugin: Plugin {
248    /// Get supported data source types
249    fn supported_sources(&self) -> Vec<String>;
250
251    /// Create a data source connection
252    fn create_connection(
253        &self,
254        config: &DataSourceConfig,
255    ) -> Result<Box<dyn DataSource>, PluginError>;
256
257    /// Validate data source configuration
258    fn validate_config(&self, config: &DataSourceConfig) -> Result<(), PluginError>;
259}
260
261/// Transform plugin trait for data transformations
262pub trait TransformPlugin: Plugin {
263    /// Get supported transform types
264    fn supported_transforms(&self) -> Vec<String>;
265
266    /// Apply transformation to data
267    fn apply_transform(
268        &self,
269        data: &[u8],
270        transform_type: &str,
271        params: &TransformParams,
272    ) -> Result<Vec<u8>, PluginError>;
273
274    /// Validate transform parameters
275    fn validate_params(
276        &self,
277        transform_type: &str,
278        params: &TransformParams,
279    ) -> Result<(), PluginError>;
280}
281
282/// Export plugin trait for custom export formats
283pub trait ExportPlugin: Plugin {
284    /// Get supported export formats
285    fn supported_formats(&self) -> Vec<ExportFormat>;
286
287    /// Export chart to format
288    fn export(
289        &self,
290        chart_data: &ChartData,
291        format: &ExportFormat,
292        options: &ExportOptions,
293    ) -> Result<ExportResult, PluginError>;
294
295    /// Validate export options
296    fn validate_options(
297        &self,
298        format: &ExportFormat,
299        options: &ExportOptions,
300    ) -> Result<(), PluginError>;
301}
302
303/// ML Intelligence plugin trait
304pub trait MLPlugin: Plugin {
305    /// Get supported ML capabilities
306    fn supported_capabilities(&self) -> Vec<String>;
307
308    /// Execute ML operation
309    fn execute_ml(
310        &self,
311        capability: &str,
312        data: &MLData,
313        params: &MLParams,
314    ) -> Result<Vec<u8>, PluginError>;
315
316    /// Validate ML parameters
317    fn validate_ml_params(&self, capability: &str, params: &MLParams) -> Result<(), PluginError>;
318}
319
320/// Theme plugin trait for custom styling
321pub trait ThemePlugin: Plugin {
322    /// Get supported theme types
323    fn supported_themes(&self) -> Vec<String>;
324
325    /// Apply theme to chart
326    fn apply_theme(
327        &self,
328        theme_name: &str,
329        config: &HashMap<String, String>,
330    ) -> Result<ChartTheme, PluginError>;
331
332    /// Validate theme configuration
333    fn validate_theme_config(
334        &self,
335        theme_name: &str,
336        config: &HashMap<String, String>,
337    ) -> Result<(), PluginError>;
338}
339
340/// Render context for chart plugins
341#[derive(Debug, Clone)]
342pub struct RenderContext {
343    pub viewport: Viewport,
344    pub device_info: DeviceInfo,
345    pub performance_budget: PerformanceBudget,
346    pub security_context: SecurityContext,
347}
348
349/// Viewport information
350#[derive(Debug, Clone)]
351pub struct Viewport {
352    pub width: u32,
353    pub height: u32,
354    pub dpi: f32,
355    pub pixel_ratio: f32,
356}
357
358/// Device information
359#[derive(Debug, Clone)]
360pub struct DeviceInfo {
361    pub gpu_available: bool,
362    pub gpu_vendor: Option<String>,
363    pub memory_mb: u64,
364    pub cpu_cores: u32,
365}
366
367/// Performance budget for rendering
368#[derive(Debug, Clone)]
369pub struct PerformanceBudget {
370    pub max_render_time_ms: u64,
371    pub max_memory_mb: u64,
372    pub max_cpu_usage_percent: u8,
373}
374
375/// Security context for plugin execution
376#[derive(Debug, Clone)]
377pub struct SecurityContext {
378    pub security_level: SecurityLevel,
379    pub allowed_operations: Vec<String>,
380    pub sandbox_mode: bool,
381}
382
383/// Render result from chart plugins
384#[derive(Debug, Clone)]
385pub struct RenderResult {
386    pub success: bool,
387    pub render_time_ms: u64,
388    pub memory_used_mb: u64,
389    pub warnings: Vec<String>,
390    pub error: Option<String>,
391}
392
393impl RenderResult {
394    pub fn success() -> Self {
395        Self {
396            success: true,
397            render_time_ms: 0,
398            memory_used_mb: 0,
399            warnings: Vec::new(),
400            error: None,
401        }
402    }
403
404    pub fn error(message: String) -> Self {
405        Self {
406            success: false,
407            render_time_ms: 0,
408            memory_used_mb: 0,
409            warnings: Vec::new(),
410            error: Some(message),
411        }
412    }
413}
414
415/// Performance estimate for rendering
416#[derive(Debug, Clone)]
417pub struct PerformanceEstimate {
418    pub estimated_time_ms: u64,
419    pub estimated_memory_mb: u64,
420    pub complexity_score: f64,
421}
422
423/// Data source configuration
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct DataSourceConfig {
426    pub source_type: String,
427    pub connection_string: String,
428    pub credentials: Option<HashMap<String, String>>,
429    pub options: HashMap<String, String>,
430}
431
432/// Transform parameters
433#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct TransformParams {
435    pub transform_type: String,
436    pub parameters: HashMap<String, serde_json::Value>,
437}
438
439/// Chart data for export
440#[derive(Debug, Clone)]
441pub struct ChartData {
442    pub spec: ChartSpec,
443    pub rendered_data: Vec<u8>,
444    pub metadata: HashMap<String, String>,
445}
446
447/// Export options
448#[derive(Debug, Clone, Serialize, Deserialize)]
449pub struct ExportOptions {
450    pub quality: u8,
451    pub format_options: HashMap<String, serde_json::Value>,
452}
453
454/// ML data for processing
455#[derive(Debug, Clone)]
456pub struct MLData {
457    pub data_type: String,
458    pub data: Vec<u8>,
459    pub schema: Option<serde_json::Value>,
460}
461
462/// ML parameters
463#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct MLParams {
465    pub algorithm: String,
466    pub parameters: HashMap<String, serde_json::Value>,
467}
468
469/// Plugin manager for managing all plugins
470pub struct PluginManager {
471    chart_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn ChartPlugin>>>>,
472    data_source_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn DataSourcePlugin>>>>,
473    transform_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn TransformPlugin>>>>,
474    export_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn ExportPlugin>>>>,
475    ml_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn MLPlugin>>>>,
476    theme_plugins: Arc<AsyncRwLock<HashMap<String, Box<dyn ThemePlugin>>>>,
477    event_handlers: Arc<RwLock<Vec<Box<dyn PluginEventHandler>>>>,
478    system_info: SystemInfo,
479}
480
481impl PluginManager {
482    /// Create a new plugin manager
483    pub fn new() -> Self {
484        Self {
485            chart_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
486            data_source_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
487            transform_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
488            export_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
489            ml_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
490            theme_plugins: Arc::new(AsyncRwLock::new(HashMap::new())),
491            event_handlers: Arc::new(RwLock::new(Vec::new())),
492            system_info: SystemInfo::current(),
493        }
494    }
495
496    /// Register a chart plugin
497    pub async fn register_chart_plugin(
498        &self,
499        plugin: Box<dyn ChartPlugin>,
500    ) -> Result<(), PluginError> {
501        let metadata = plugin.metadata().clone();
502        let name = metadata.name.clone();
503
504        // Validate plugin compatibility
505        if !plugin.is_compatible(&self.system_info) {
506            return Err(PluginError::CompatibilityError(format!(
507                "Plugin {} is not compatible with current system",
508                name
509            )));
510        }
511
512        // Initialize plugin
513        let mut plugin = plugin;
514        plugin.initialize()?;
515
516        // Register plugin
517        let mut plugins = self.chart_plugins.write().await;
518        plugins.insert(name.clone(), plugin);
519
520        // Emit event
521        self.emit_event(PluginEvent::Registered {
522            name: name.clone(),
523            version: metadata.version.clone(),
524        });
525
526        Ok(())
527    }
528
529    /// Register a data source plugin
530    pub async fn register_data_source_plugin(
531        &self,
532        plugin: Box<dyn DataSourcePlugin>,
533    ) -> Result<(), PluginError> {
534        let metadata = plugin.metadata().clone();
535        let name = metadata.name.clone();
536
537        if !plugin.is_compatible(&self.system_info) {
538            return Err(PluginError::CompatibilityError(format!(
539                "Plugin {} is not compatible with current system",
540                name
541            )));
542        }
543
544        let mut plugin = plugin;
545        plugin.initialize()?;
546
547        let mut plugins = self.data_source_plugins.write().await;
548        plugins.insert(name.clone(), plugin);
549
550        self.emit_event(PluginEvent::Registered {
551            name: name.clone(),
552            version: metadata.version.clone(),
553        });
554
555        Ok(())
556    }
557
558    /// Register a transform plugin
559    pub async fn register_transform_plugin(
560        &self,
561        plugin: Box<dyn TransformPlugin>,
562    ) -> Result<(), PluginError> {
563        let metadata = plugin.metadata().clone();
564        let name = metadata.name.clone();
565
566        if !plugin.is_compatible(&self.system_info) {
567            return Err(PluginError::CompatibilityError(format!(
568                "Plugin {} is not compatible with current system",
569                name
570            )));
571        }
572
573        let mut plugin = plugin;
574        plugin.initialize()?;
575
576        let mut plugins = self.transform_plugins.write().await;
577        plugins.insert(name.clone(), plugin);
578
579        self.emit_event(PluginEvent::Registered {
580            name: name.clone(),
581            version: metadata.version.clone(),
582        });
583
584        Ok(())
585    }
586
587    /// Register an export plugin
588    pub async fn register_export_plugin(
589        &self,
590        plugin: Box<dyn ExportPlugin>,
591    ) -> Result<(), PluginError> {
592        let metadata = plugin.metadata().clone();
593        let name = metadata.name.clone();
594
595        if !plugin.is_compatible(&self.system_info) {
596            return Err(PluginError::CompatibilityError(format!(
597                "Plugin {} is not compatible with current system",
598                name
599            )));
600        }
601
602        let mut plugin = plugin;
603        plugin.initialize()?;
604
605        let mut plugins = self.export_plugins.write().await;
606        plugins.insert(name.clone(), plugin);
607
608        self.emit_event(PluginEvent::Registered {
609            name: name.clone(),
610            version: metadata.version.clone(),
611        });
612
613        Ok(())
614    }
615
616    /// Register an ML plugin
617    pub async fn register_ml_plugin(&self, plugin: Box<dyn MLPlugin>) -> Result<(), PluginError> {
618        let metadata = plugin.metadata().clone();
619        let name = metadata.name.clone();
620
621        if !plugin.is_compatible(&self.system_info) {
622            return Err(PluginError::CompatibilityError(format!(
623                "Plugin {} is not compatible with current system",
624                name
625            )));
626        }
627
628        let mut plugin = plugin;
629        plugin.initialize()?;
630
631        let mut plugins = self.ml_plugins.write().await;
632        plugins.insert(name.clone(), plugin);
633
634        self.emit_event(PluginEvent::Registered {
635            name: name.clone(),
636            version: metadata.version.clone(),
637        });
638
639        Ok(())
640    }
641
642    /// Register a theme plugin
643    pub async fn register_theme_plugin(
644        &self,
645        plugin: Box<dyn ThemePlugin>,
646    ) -> Result<(), PluginError> {
647        let metadata = plugin.metadata().clone();
648        let name = metadata.name.clone();
649
650        if !plugin.is_compatible(&self.system_info) {
651            return Err(PluginError::CompatibilityError(format!(
652                "Plugin {} is not compatible with current system",
653                name
654            )));
655        }
656
657        let mut plugin = plugin;
658        plugin.initialize()?;
659
660        let mut plugins = self.theme_plugins.write().await;
661        plugins.insert(name.clone(), plugin);
662
663        self.emit_event(PluginEvent::Registered {
664            name: name.clone(),
665            version: metadata.version.clone(),
666        });
667
668        Ok(())
669    }
670
671    /// Get a chart plugin by name
672    pub async fn get_chart_plugin(&self, name: &str) -> Option<Box<dyn ChartPlugin>> {
673        let plugins = self.chart_plugins.read().await;
674        // For now, we'll return None since we can't safely downcast trait objects
675        // In a real implementation, we'd need a different approach for type-safe plugin retrieval
676        plugins.get(name).map(|_| None).flatten()
677    }
678
679    /// Get all registered chart plugins
680    pub async fn list_chart_plugins(&self) -> Vec<String> {
681        let plugins = self.chart_plugins.read().await;
682        plugins.keys().cloned().collect()
683    }
684
685    /// Add event handler
686    pub fn add_event_handler(&self, handler: Box<dyn PluginEventHandler>) {
687        let mut handlers = self.event_handlers.write().unwrap();
688        handlers.push(handler);
689    }
690
691    /// Emit plugin event
692    fn emit_event(&self, event: PluginEvent) {
693        let handlers = self.event_handlers.read().unwrap();
694        for handler in handlers.iter() {
695            handler.handle_event(event.clone());
696        }
697    }
698
699    /// Get system information
700    pub fn system_info(&self) -> &SystemInfo {
701        &self.system_info
702    }
703
704    /// Validate all registered plugins
705    pub async fn validate_all_plugins(&self) -> Result<(), PluginError> {
706        // Validate chart plugins
707        let chart_plugins = self.chart_plugins.read().await;
708        for (name, plugin) in chart_plugins.iter() {
709            if !plugin.is_compatible(&self.system_info) {
710                return Err(PluginError::CompatibilityError(format!(
711                    "Chart plugin {} is not compatible",
712                    name
713                )));
714            }
715        }
716
717        // Validate other plugin types...
718        // (Similar validation for other plugin types)
719
720        Ok(())
721    }
722
723    /// Cleanup all plugins
724    pub async fn cleanup_all_plugins(&self) -> Result<(), PluginError> {
725        // Cleanup chart plugins
726        let mut chart_plugins = self.chart_plugins.write().await;
727        for (name, plugin) in chart_plugins.iter_mut() {
728            if let Err(e) = plugin.cleanup() {
729                return Err(PluginError::ExecutionFailed(format!(
730                    "Failed to cleanup chart plugin {}: {}",
731                    name, e
732                )));
733            }
734        }
735
736        // Cleanup other plugin types...
737        // (Similar cleanup for other plugin types)
738
739        Ok(())
740    }
741}
742
743impl SystemInfo {
744    /// Get current system information
745    pub fn current() -> Self {
746        Self {
747            helios_version: env!("CARGO_PKG_VERSION").to_string(),
748            rust_version: "1.70.0".to_string(), // Default version
749            platform: std::env::consts::OS.to_string(),
750            features: vec![],          // TODO: Get actual features
751            available_memory_mb: 1024, // TODO: Get actual memory
752            gpu_available: true,       // TODO: Detect GPU availability
753        }
754    }
755}
756
757impl Default for PluginManager {
758    fn default() -> Self {
759        Self::new()
760    }
761}
762
763/// Plugin registry for static plugin registration
764pub struct PluginRegistry {
765    plugins: HashMap<String, PluginRegistration>,
766}
767
768/// Plugin registration information
769pub struct PluginRegistration {
770    pub metadata: PluginMetadata,
771    pub factory: Box<dyn Fn() -> Box<dyn Plugin> + Send + Sync>,
772}
773
774impl PluginRegistry {
775    /// Create a new plugin registry
776    pub fn new() -> Self {
777        Self {
778            plugins: HashMap::new(),
779        }
780    }
781
782    /// Register a plugin factory
783    pub fn register<F>(&mut self, metadata: PluginMetadata, factory: F)
784    where
785        F: Fn() -> Box<dyn Plugin> + Send + Sync + 'static,
786    {
787        let name = metadata.name.clone();
788        self.plugins.insert(
789            name,
790            PluginRegistration {
791                metadata,
792                factory: Box::new(factory),
793            },
794        );
795    }
796
797    /// Get plugin factory by name
798    pub fn get_factory(&self, name: &str) -> Option<&(dyn Fn() -> Box<dyn Plugin> + Send + Sync)> {
799        self.plugins.get(name).map(|reg| reg.factory.as_ref())
800    }
801
802    /// List all registered plugins
803    pub fn list_plugins(&self) -> Vec<&PluginMetadata> {
804        self.plugins.values().map(|reg| &reg.metadata).collect()
805    }
806}
807
808impl Default for PluginRegistry {
809    fn default() -> Self {
810        Self::new()
811    }
812}
813
814#[cfg(test)]
815mod tests {
816    use super::*;
817
818    /// Mock chart plugin for testing
819    #[derive(Debug)]
820    struct MockChartPlugin {
821        metadata: PluginMetadata,
822    }
823
824    impl MockChartPlugin {
825        fn new() -> Self {
826            Self {
827                metadata: PluginMetadata {
828                    name: "mock-chart".to_string(),
829                    version: "1.0.0".to_string(),
830                    description: "Mock chart plugin for testing".to_string(),
831                    author: "Test Author".to_string(),
832                    license: "MIT".to_string(),
833                    homepage: None,
834                    repository: None,
835                    dependencies: Vec::new(),
836                    capabilities: PluginCapabilities {
837                        capabilities: vec![PluginCapability::ChartRendering],
838                        max_data_points: Some(10000),
839                        supported_formats: vec!["svg".to_string()],
840                        performance_requirements: PerformanceRequirements {
841                            max_memory_mb: Some(100),
842                            max_execution_time_ms: Some(1000),
843                            max_cpu_usage_percent: Some(50),
844                            requires_gpu: false,
845                        },
846                    },
847                    security_level: SecurityLevel::Sandboxed,
848                    performance_impact: PerformanceImpact::Minimal,
849                },
850            }
851        }
852    }
853
854    impl Plugin for MockChartPlugin {
855        fn metadata(&self) -> &PluginMetadata {
856            &self.metadata
857        }
858
859        fn initialize(&mut self) -> Result<(), PluginError> {
860            Ok(())
861        }
862
863        fn cleanup(&mut self) -> Result<(), PluginError> {
864            Ok(())
865        }
866
867        fn is_compatible(&self, _system_info: &SystemInfo) -> bool {
868            true
869        }
870
871        fn type_id(&self) -> TypeId {
872            TypeId::of::<MockChartPlugin>()
873        }
874
875        fn clone_plugin(&self) -> Box<dyn Plugin> {
876            Box::new(MockChartPlugin::new())
877        }
878
879        fn as_any(&self) -> &dyn std::any::Any {
880            self
881        }
882    }
883
884    impl ChartPlugin for MockChartPlugin {
885        fn supported_marks(&self) -> Vec<MarkType> {
886            vec![MarkType::Point {
887                size: Some(5.0),
888                opacity: Some(1.0),
889                shape: Some(crate::chart::PointShape::Circle),
890            }]
891        }
892
893        fn render(
894            &self,
895            _spec: &ChartSpec,
896            _context: &RenderContext,
897        ) -> Result<RenderResult, PluginError> {
898            Ok(RenderResult::success())
899        }
900
901        fn validate_spec(&self, _spec: &ChartSpec) -> Result<(), PluginError> {
902            Ok(())
903        }
904
905        fn estimate_performance(&self, _spec: &ChartSpec) -> PerformanceEstimate {
906            PerformanceEstimate {
907                estimated_time_ms: 10,
908                estimated_memory_mb: 5,
909                complexity_score: 1.0,
910            }
911        }
912    }
913
914    #[tokio::test]
915    async fn test_plugin_manager_creation() {
916        let manager = PluginManager::new();
917        assert_eq!(manager.list_chart_plugins().await.len(), 0);
918    }
919
920    #[tokio::test]
921    async fn test_chart_plugin_registration() {
922        let manager = PluginManager::new();
923        let plugin = Box::new(MockChartPlugin::new());
924
925        let result = manager.register_chart_plugin(plugin).await;
926        assert!(result.is_ok());
927
928        let plugins = manager.list_chart_plugins().await;
929        assert_eq!(plugins.len(), 1);
930        assert_eq!(plugins[0], "mock-chart");
931    }
932
933    #[tokio::test]
934    async fn test_chart_plugin_retrieval() {
935        let manager = PluginManager::new();
936        let plugin = Box::new(MockChartPlugin::new());
937
938        manager.register_chart_plugin(plugin).await.unwrap();
939
940        // Verify plugin is registered
941        let plugins = manager.list_chart_plugins().await;
942        assert_eq!(plugins.len(), 1);
943        assert_eq!(plugins[0], "mock-chart");
944
945        // Note: get_chart_plugin currently returns None due to trait object downcasting limitations
946        // In a real implementation, we'd need a different approach for type-safe plugin retrieval
947        let retrieved_plugin = manager.get_chart_plugin("mock-chart").await;
948        assert!(retrieved_plugin.is_none()); // Current implementation limitation
949    }
950
951    #[tokio::test]
952    async fn test_plugin_not_found() {
953        let manager = PluginManager::new();
954        let plugin = manager.get_chart_plugin("nonexistent").await;
955        assert!(plugin.is_none());
956    }
957
958    #[test]
959    fn test_plugin_metadata() {
960        let plugin = MockChartPlugin::new();
961        let metadata = plugin.metadata();
962
963        assert_eq!(metadata.name, "mock-chart");
964        assert_eq!(metadata.version, "1.0.0");
965        assert_eq!(metadata.security_level, SecurityLevel::Sandboxed);
966        assert_eq!(metadata.performance_impact, PerformanceImpact::Minimal);
967    }
968
969    #[test]
970    fn test_plugin_capabilities() {
971        let plugin = MockChartPlugin::new();
972        let capabilities = &plugin.metadata().capabilities;
973
974        assert!(capabilities
975            .capabilities
976            .contains(&PluginCapability::ChartRendering));
977        assert_eq!(capabilities.max_data_points, Some(10000));
978        assert!(capabilities.supported_formats.contains(&"svg".to_string()));
979    }
980
981    #[test]
982    fn test_system_info() {
983        let system_info = SystemInfo::current();
984
985        assert!(!system_info.helios_version.is_empty());
986        assert!(!system_info.platform.is_empty());
987    }
988
989    #[test]
990    fn test_plugin_registry() {
991        let mut registry = PluginRegistry::new();
992        let plugin = MockChartPlugin::new();
993        let metadata = plugin.metadata().clone();
994
995        registry.register(metadata.clone(), || Box::new(MockChartPlugin::new()));
996
997        let plugins = registry.list_plugins();
998        assert_eq!(plugins.len(), 1);
999        assert_eq!(plugins[0].name, "mock-chart");
1000
1001        let factory = registry.get_factory("mock-chart");
1002        assert!(factory.is_some());
1003    }
1004
1005    #[test]
1006    fn test_render_result() {
1007        let success_result = RenderResult::success();
1008        assert!(success_result.success);
1009        assert!(success_result.error.is_none());
1010
1011        let error_result = RenderResult::error("Test error".to_string());
1012        assert!(!error_result.success);
1013        assert_eq!(error_result.error, Some("Test error".to_string()));
1014    }
1015
1016    #[test]
1017    fn test_performance_estimate() {
1018        let estimate = PerformanceEstimate {
1019            estimated_time_ms: 100,
1020            estimated_memory_mb: 50,
1021            complexity_score: 2.5,
1022        };
1023
1024        assert_eq!(estimate.estimated_time_ms, 100);
1025        assert_eq!(estimate.estimated_memory_mb, 50);
1026        assert_eq!(estimate.complexity_score, 2.5);
1027    }
1028}