Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/plugin.rs
StratoSDK / crates / strato-core / src / plugin.rs
1//! Plugin system for StratoUI
2//!
3//! Provides a flexible plugin architecture for extending framework functionality
4//! with custom widgets, themes, and behaviors.
5 
6use crate::{
7 error::{Result, StratoError},
8 event::{Event, EventHandler, EventResult},
9 theme::Theme,
10 widget::Widget,
11};
12use serde::{Deserialize, Serialize};
13use std::{
14 any::Any,
15 collections::HashMap,
16 sync::{Arc, RwLock},
17};
18 
19/// Plugin metadata
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PluginMetadata {
22 /// Plugin name
23 pub name: String,
24 /// Plugin version
25 pub version: String,
26 /// Plugin description
27 pub description: String,
28 /// Plugin author
29 pub author: String,
30 /// Plugin dependencies
31 pub dependencies: Vec<String>,
32 /// Minimum StratoUI version required
33 pub min_strato_version: String,
34 /// Plugin capabilities
35 pub capabilities: Vec<PluginCapability>,
36}
37 
38/// Plugin capabilities
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
40pub enum PluginCapability {
41 /// Provides custom widgets
42 Widgets,
43 /// Provides custom themes
44 Themes,
45 /// Provides event handlers
46 EventHandlers,
47 /// Provides layout engines
48 LayoutEngines,
49 /// Provides state management
50 StateManagement,
51 /// Provides rendering backends
52 RenderingBackends,
53 /// Provides platform integration
54 PlatformIntegration,
55}
56 
57/// Plugin lifecycle states
58#[derive(Debug, Clone, PartialEq)]
59pub enum PluginState {
60 Unloaded,
61 Loading,
62 Loaded,
63 Active,
64 Error(String),
65}
66 
67/// Plugin trait that all plugins must implement
68pub trait Plugin: Send + Sync {
69 /// Get plugin metadata
70 fn metadata(&self) -> &PluginMetadata;
71 
72 /// Initialize the plugin
73 fn initialize(&mut self, context: &mut PluginContext) -> Result<()>;
74 
75 /// Activate the plugin
76 fn activate(&mut self, context: &mut PluginContext) -> Result<()>;
77 
78 /// Deactivate the plugin
79 fn deactivate(&mut self, context: &mut PluginContext) -> Result<()>;
80 
81 /// Cleanup plugin resources
82 fn cleanup(&mut self, context: &mut PluginContext) -> Result<()>;
83 
84 /// Handle plugin events
85 fn handle_event(&mut self, _event: &Event, _context: &mut PluginContext) -> EventResult {
86 EventResult::Ignored
87 }
88 
89 /// Get plugin as Any for downcasting
90 fn as_any(&self) -> &dyn Any;
91 
92 /// Get mutable plugin as Any for downcasting
93 fn as_any_mut(&mut self) -> &mut dyn Any;
94}
95 
96/// Plugin context provides access to framework services
97pub struct PluginContext {
98 /// Widget registry
99 pub widget_registry: Arc<RwLock<WidgetRegistry>>,
100 /// Theme registry
101 pub theme_registry: Arc<RwLock<ThemeRegistry>>,
102 /// Event system
103 pub event_handlers: Arc<RwLock<Vec<Box<dyn EventHandler>>>>,
104 /// Plugin data storage
105 pub data_store: Arc<RwLock<HashMap<String, Box<dyn Any + Send + Sync>>>>,
106}
107 
108impl PluginContext {
109 /// Create a new plugin context
110 pub fn new() -> Self {
111 Self {
112 widget_registry: Arc::new(RwLock::new(WidgetRegistry::new())),
113 theme_registry: Arc::new(RwLock::new(ThemeRegistry::new())),
114 event_handlers: Arc::new(RwLock::new(Vec::new())),
115 data_store: Arc::new(RwLock::new(HashMap::new())),
116 }
117 }
118 
119 /// Store plugin data
120 pub fn store_data<T: Any + Send + Sync>(&self, key: String, data: T) {
121 if let Ok(mut store) = self.data_store.write() {
122 store.insert(key, Box::new(data));
123 }
124 }
125 
126 /// Retrieve plugin data
127 pub fn get_data<T: Any + Send + Sync>(&self, key: &str) -> Option<T>
128 where
129 T: Clone,
130 {
131 let store = self.data_store.read().ok()?;
132 store.get(key)?.downcast_ref::<T>().cloned()
133 }
134 
135 /// Register a widget factory
136 pub fn register_widget<W: Widget + 'static>(&self, name: String, factory: WidgetFactory) {
137 if let Ok(mut registry) = self.widget_registry.write() {
138 registry.register(name, factory);
139 }
140 }
141 
142 /// Register a theme
143 pub fn register_theme(&self, name: String, theme: Theme) {
144 if let Ok(mut registry) = self.theme_registry.write() {
145 registry.register(name, theme);
146 }
147 }
148 
149 /// Add an event handler
150 pub fn add_event_handler(&self, handler: Box<dyn EventHandler>) {
151 if let Ok(mut handlers) = self.event_handlers.write() {
152 handlers.push(handler);
153 }
154 }
155}
156 
157impl Default for PluginContext {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162 
163/// Widget factory function type
164pub type WidgetFactory = Box<dyn Fn() -> Box<dyn Widget> + Send + Sync>;
165 
166/// Widget registry for plugin-provided widgets
167pub struct WidgetRegistry {
168 factories: HashMap<String, WidgetFactory>,
169}
170 
171impl WidgetRegistry {
172 /// Create a new widget registry
173 pub fn new() -> Self {
174 Self {
175 factories: HashMap::new(),
176 }
177 }
178 
179 /// Register a widget factory
180 pub fn register(&mut self, name: String, factory: WidgetFactory) {
181 self.factories.insert(name, factory);
182 }
183 
184 /// Create a widget by name
185 pub fn create_widget(&self, name: &str) -> Option<Box<dyn Widget>> {
186 self.factories.get(name).map(|factory| factory())
187 }
188 
189 /// Get all registered widget names
190 pub fn get_widget_names(&self) -> Vec<String> {
191 self.factories.keys().cloned().collect()
192 }
193 
194 /// Check if a widget is registered
195 pub fn has_widget(&self, name: &str) -> bool {
196 self.factories.contains_key(name)
197 }
198}
199 
200impl Default for WidgetRegistry {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205 
206/// Theme registry for plugin-provided themes
207pub struct ThemeRegistry {
208 themes: HashMap<String, Theme>,
209}
210 
211impl ThemeRegistry {
212 /// Create a new theme registry
213 pub fn new() -> Self {
214 Self {
215 themes: HashMap::new(),
216 }
217 }
218 
219 /// Register a theme
220 pub fn register(&mut self, name: String, theme: Theme) {
221 self.themes.insert(name, theme);
222 }
223 
224 /// Get a theme by name
225 pub fn get_theme(&self, name: &str) -> Option<&Theme> {
226 self.themes.get(name)
227 }
228 
229 /// Get all registered theme names
230 pub fn get_theme_names(&self) -> Vec<String> {
231 self.themes.keys().cloned().collect()
232 }
233 
234 /// Check if a theme is registered
235 pub fn has_theme(&self, name: &str) -> bool {
236 self.themes.contains_key(name)
237 }
238}
239 
240impl Default for ThemeRegistry {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245 
246/// Plugin manager handles loading, activation, and lifecycle of plugins
247pub struct PluginManager {
248 plugins: HashMap<String, Box<dyn Plugin>>,
249 plugin_states: HashMap<String, PluginState>,
250 context: PluginContext,
251 load_order: Vec<String>,
252}
253 
254impl PluginManager {
255 /// Create a new plugin manager
256 pub fn new() -> Self {
257 Self {
258 plugins: HashMap::new(),
259 plugin_states: HashMap::new(),
260 context: PluginContext::new(),
261 load_order: Vec::new(),
262 }
263 }
264 
265 /// Register a plugin
266 pub fn register_plugin(&mut self, plugin: Box<dyn Plugin>) -> Result<()> {
267 let name = plugin.metadata().name.clone();
268 
269 // Check for duplicate names
270 if self.plugins.contains_key(&name) {
271 return Err(StratoError::PluginError {
272 message: format!("Plugin '{}' is already registered", name),
273 context: None,
274 });
275 }
276 
277 // Validate dependencies
278 self.validate_dependencies(plugin.metadata())?;
279 
280 self.plugin_states
281 .insert(name.clone(), PluginState::Unloaded);
282 self.plugins.insert(name.clone(), plugin);
283 self.load_order.push(name);
284 
285 Ok(())
286 }
287 
288 /// Load a plugin
289 pub fn load_plugin(&mut self, name: &str) -> Result<()> {
290 if !self.plugins.contains_key(name) {
291 return Err(StratoError::PluginError {
292 message: format!("Plugin '{}' not found", name),
293 context: None,
294 });
295 }
296 
297 // Check current state
298 if let Some(state) = self.plugin_states.get(name) {
299 match state {
300 PluginState::Loaded | PluginState::Active => {
301 return Ok(()); // Already loaded
302 }
303 PluginState::Loading => {
304 return Err(StratoError::PluginError {
305 message: format!("Plugin '{}' is already loading", name),
306 context: None,
307 });
308 }
309 _ => {}
310 }
311 }
312 
313 // Set loading state
314 self.plugin_states
315 .insert(name.to_string(), PluginState::Loading);
316 
317 // Load dependencies first
318 let dependencies = self.plugins[name].metadata().dependencies.clone();
319 for dep in dependencies {
320 self.load_plugin(&dep)?;
321 }
322 
323 // Initialize plugin
324 match self.plugins.get_mut(name) {
325 Some(plugin) => match plugin.initialize(&mut self.context) {
326 Ok(()) => {
327 self.plugin_states
328 .insert(name.to_string(), PluginState::Loaded);
329 tracing::info!("Plugin '{}' loaded successfully", name);
330 }
331 Err(e) => {
332 let error_msg = format!("Failed to initialize plugin '{}': {}", name, e);
333 self.plugin_states
334 .insert(name.to_string(), PluginState::Error(error_msg.clone()));
335 return Err(StratoError::PluginError {
336 message: error_msg,
337 context: None,
338 });
339 }
340 },
341 None => {
342 return Err(StratoError::PluginError {
343 message: format!("Plugin '{}' not found", name),
344 context: None,
345 });
346 }
347 }
348 
349 Ok(())
350 }
351 
352 /// Activate a plugin
353 pub fn activate_plugin(&mut self, name: &str) -> Result<()> {
354 // Ensure plugin is loaded
355 self.load_plugin(name)?;
356 
357 // Check current state
358 if let Some(PluginState::Active) = self.plugin_states.get(name) {
359 return Ok(()); // Already active
360 }
361 
362 // Activate plugin
363 match self.plugins.get_mut(name) {
364 Some(plugin) => match plugin.activate(&mut self.context) {
365 Ok(()) => {
366 self.plugin_states
367 .insert(name.to_string(), PluginState::Active);
368 tracing::info!("Plugin '{}' activated successfully", name);
369 }
370 Err(e) => {
371 let error_msg = format!("Failed to activate plugin '{}': {}", name, e);
372 self.plugin_states
373 .insert(name.to_string(), PluginState::Error(error_msg.clone()));
374 return Err(StratoError::PluginError {
375 message: error_msg,
376 context: None,
377 });
378 }
379 },
380 None => {
381 return Err(StratoError::PluginError {
382 message: format!("Plugin '{}' not found", name),
383 context: None,
384 });
385 }
386 }
387 
388 Ok(())
389 }
390 
391 /// Deactivate a plugin
392 pub fn deactivate_plugin(&mut self, name: &str) -> Result<()> {
393 if let Some(PluginState::Active) = self.plugin_states.get(name) {
394 match self.plugins.get_mut(name) {
395 Some(plugin) => {
396 plugin.deactivate(&mut self.context)?;
397 self.plugin_states
398 .insert(name.to_string(), PluginState::Loaded);
399 tracing::info!("Plugin '{}' deactivated", name);
400 }
401 None => {
402 return Err(StratoError::PluginError {
403 message: format!("Plugin '{}' not found", name),
404 context: None,
405 });
406 }
407 }
408 }
409 
410 Ok(())
411 }
412 
413 /// Unload a plugin
414 pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
415 // Deactivate first if active
416 self.deactivate_plugin(name)?;
417 
418 // Cleanup plugin
419 match self.plugins.get_mut(name) {
420 Some(plugin) => {
421 plugin.cleanup(&mut self.context)?;
422 self.plugin_states
423 .insert(name.to_string(), PluginState::Unloaded);
424 tracing::info!("Plugin '{}' unloaded", name);
425 }
426 None => {
427 return Err(StratoError::PluginError {
428 message: format!("Plugin '{}' not found", name),
429 context: None,
430 });
431 }
432 }
433 
434 Ok(())
435 }
436 
437 /// Load all registered plugins
438 pub fn load_all_plugins(&mut self) -> Result<()> {
439 let plugin_names: Vec<String> = self.load_order.clone();
440 
441 for name in plugin_names {
442 if let Err(e) = self.load_plugin(&name) {
443 tracing::error!("Failed to load plugin '{}': {}", name, e);
444 // Continue loading other plugins
445 }
446 }
447 
448 Ok(())
449 }
450 
451 /// Activate all loaded plugins
452 pub fn activate_all_plugins(&mut self) -> Result<()> {
453 let plugin_names: Vec<String> = self.load_order.clone();
454 
455 for name in plugin_names {
456 if let Some(PluginState::Loaded) = self.plugin_states.get(&name) {
457 if let Err(e) = self.activate_plugin(&name) {
458 tracing::error!("Failed to activate plugin '{}': {}", name, e);
459 // Continue activating other plugins
460 }
461 }
462 }
463 
464 Ok(())
465 }
466 
467 /// Get plugin state
468 pub fn get_plugin_state(&self, name: &str) -> Option<&PluginState> {
469 self.plugin_states.get(name)
470 }
471 
472 /// Get all plugin names
473 pub fn get_plugin_names(&self) -> Vec<String> {
474 self.plugins.keys().cloned().collect()
475 }
476 
477 /// Get plugin metadata
478 pub fn get_plugin_metadata(&self, name: &str) -> Option<&PluginMetadata> {
479 self.plugins.get(name).map(|p| p.metadata())
480 }
481 
482 /// Get plugin context
483 pub fn get_context(&self) -> &PluginContext {
484 &self.context
485 }
486 
487 /// Get mutable plugin context
488 pub fn get_context_mut(&mut self) -> &mut PluginContext {
489 &mut self.context
490 }
491 
492 /// Handle event through all active plugins
493 pub fn handle_event(&mut self, event: &Event) -> EventResult {
494 let plugin_names: Vec<String> = self.plugins.keys().cloned().collect();
495 
496 for name in plugin_names {
497 if let Some(PluginState::Active) = self.plugin_states.get(&name) {
498 if let Some(plugin) = self.plugins.get_mut(&name) {
499 match plugin.handle_event(event, &mut self.context) {
500 EventResult::Handled => return EventResult::Handled,
501 EventResult::Ignored => continue,
502 }
503 }
504 }
505 }
506 
507 EventResult::Ignored
508 }
509 
510 /// Validate plugin dependencies
511 fn validate_dependencies(&self, metadata: &PluginMetadata) -> Result<()> {
512 for dep in &metadata.dependencies {
513 if !self.plugins.contains_key(dep) {
514 return Err(StratoError::PluginError {
515 message: format!(
516 "Plugin '{}' depends on '{}' which is not registered",
517 metadata.name, dep
518 ),
519 context: None,
520 });
521 }
522 }
523 Ok(())
524 }
525}
526 
527impl Default for PluginManager {
528 fn default() -> Self {
529 Self::new()
530 }
531}
532 
533/// Convenience macro for creating plugin metadata
534#[macro_export]
535macro_rules! plugin_metadata {
536 (
537 name: $name:expr,
538 version: $version:expr,
539 description: $description:expr,
540 author: $author:expr,
541 $(dependencies: [$($dep:expr),*],)?
542 $(min_strato_version: $min_version:expr,)?
543 $(capabilities: [$($cap:expr),*],)?
544 ) => {
545 $crate::plugin::PluginMetadata {
546 name: $name.to_string(),
547 version: $version.to_string(),
548 description: $description.to_string(),
549 author: $author.to_string(),
550 dependencies: vec![$($($dep.to_string()),*)?],
551 min_strato_version: plugin_metadata!(@min_version $($min_version)?).to_string(),
552 capabilities: vec![$($($cap),*)?],
553 }
554 };
555 (@min_version) => { env!("CARGO_PKG_VERSION") };
556 (@min_version $version:expr) => { $version };
557}
558 
559#[cfg(test)]
560mod tests {
561 use super::*;
562 
563 struct TestPlugin {
564 metadata: PluginMetadata,
565 initialized: bool,
566 active: bool,
567 }
568 
569 impl TestPlugin {
570 fn new(name: &str) -> Self {
571 Self {
572 metadata: PluginMetadata {
573 name: name.to_string(),
574 version: "1.0.0".to_string(),
575 description: "Test plugin".to_string(),
576 author: "Test Author".to_string(),
577 dependencies: vec![],
578 min_strato_version: "0.1.0".to_string(),
579 capabilities: vec![PluginCapability::Widgets],
580 },
581 initialized: false,
582 active: false,
583 }
584 }
585 }
586 
587 impl Plugin for TestPlugin {
588 fn metadata(&self) -> &PluginMetadata {
589 &self.metadata
590 }
591 
592 fn initialize(&mut self, _context: &mut PluginContext) -> Result<()> {
593 self.initialized = true;
594 Ok(())
595 }
596 
597 fn activate(&mut self, _context: &mut PluginContext) -> Result<()> {
598 self.active = true;
599 Ok(())
600 }
601 
602 fn deactivate(&mut self, _context: &mut PluginContext) -> Result<()> {
603 self.active = false;
604 Ok(())
605 }
606 
607 fn cleanup(&mut self, _context: &mut PluginContext) -> Result<()> {
608 self.initialized = false;
609 self.active = false;
610 Ok(())
611 }
612 
613 fn as_any(&self) -> &dyn Any {
614 self
615 }
616 
617 fn as_any_mut(&mut self) -> &mut dyn Any {
618 self
619 }
620 }
621 
622 #[test]
623 fn test_plugin_manager_creation() {
624 let manager = PluginManager::new();
625 assert_eq!(manager.get_plugin_names().len(), 0);
626 }
627 
628 #[test]
629 fn test_plugin_registration() {
630 let mut manager = PluginManager::new();
631 let plugin = Box::new(TestPlugin::new("test"));
632 
633 assert!(manager.register_plugin(plugin).is_ok());
634 assert_eq!(manager.get_plugin_names().len(), 1);
635 assert!(manager.get_plugin_names().contains(&"test".to_string()));
636 }
637 
638 #[test]
639 fn test_plugin_loading() {
640 let mut manager = PluginManager::new();
641 let plugin = Box::new(TestPlugin::new("test"));
642 
643 manager.register_plugin(plugin).unwrap();
644 assert!(manager.load_plugin("test").is_ok());
645 
646 match manager.get_plugin_state("test") {
647 Some(PluginState::Loaded) => {}
648 _ => panic!("Plugin should be loaded"),
649 }
650 }
651 
652 #[test]
653 fn test_plugin_activation() {
654 let mut manager = PluginManager::new();
655 let plugin = Box::new(TestPlugin::new("test"));
656 
657 manager.register_plugin(plugin).unwrap();
658 assert!(manager.activate_plugin("test").is_ok());
659 
660 match manager.get_plugin_state("test") {
661 Some(PluginState::Active) => {}
662 _ => panic!("Plugin should be active"),
663 }
664 }
665 
666 #[test]
667 fn test_widget_registry() {
668 let mut registry = WidgetRegistry::new();
669 
670 // This would normally be a real widget factory
671 let factory: WidgetFactory = Box::new(|| {
672 // Return a mock widget
673 unimplemented!("Mock widget factory")
674 });
675 
676 registry.register("test_widget".to_string(), factory);
677 assert!(registry.has_widget("test_widget"));
678 assert_eq!(registry.get_widget_names().len(), 1);
679 }
680 
681 #[test]
682 fn test_plugin_metadata_macro() {
683 let metadata = plugin_metadata! {
684 name: "test_plugin",
685 version: "1.0.0",
686 description: "A test plugin",
687 author: "Test Author",
688 dependencies: ["dep1", "dep2"],
689 capabilities: [PluginCapability::Widgets, PluginCapability::Themes],
690 };
691 
692 assert_eq!(metadata.name, "test_plugin");
693 assert_eq!(metadata.version, "1.0.0");
694 assert_eq!(metadata.dependencies.len(), 2);
695 assert_eq!(metadata.capabilities.len(), 2);
696 }
697}
698