1mod loader;
4mod manager;
5mod manifest;
6mod sdk;
7mod search;
8
9use std::collections::HashMap;
10use std::sync::Arc;
11
12use crate::auth::{Permission, User};
13use crate::config::SettingsSchema;
14use crate::error::{Error, Result};
15use crate::event::{Event, EventBusManager};
16use crate::manager::{ManagedState, Manager, ManagerStatus, PlatformRequirements};
17use crate::platform::database::DatabaseArc;
18use crate::platform::filesystem::FileSystemArc;
19use async_trait::async_trait;
20use dioxus::prelude::*;
21use serde::{Deserialize, Serialize};
22use uuid::Uuid;
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct PluginInfo {
27 pub id: String,
28 pub name: String,
29 pub version: String,
30 pub description: String,
31 pub author: String,
32 pub license: String,
33 pub homepage: Option<String>,
34 pub repository: Option<String>,
35 pub minimum_core_version: String,
36 pub supported_platforms: Vec<Platform>,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub enum Platform {
42 Windows,
43 MacOS,
44 Linux,
45 IOS,
46 Android,
47 Web,
48 All,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PluginDependency {
54 pub plugin_id: String,
55 pub version_requirement: String, pub optional: bool,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct PluginConfig {
62 pub plugin_id: String,
63 pub version: String,
64 pub config_schema: serde_json::Value, pub default_values: serde_json::Value,
66 pub user_overrides: serde_json::Value,
67 pub validation_rules: Vec<ValidationRule>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ValidationRule {
73 pub field: String,
74 pub rule_type: ValidationType,
75 pub message: String,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub enum ValidationType {
81 Required,
82 MinLength(usize),
83 MaxLength(usize),
84 Pattern(String),
85 Range { min: f64, max: f64 },
86 Custom(String),
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct UIComponent {
92 pub id: String,
93 pub name: String,
94 pub component_type: ComponentType,
95 pub props: serde_json::Value,
96 pub required_permissions: Vec<Permission>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum ComponentType {
102 Page,
103 Widget,
104 Modal,
105 Sidebar,
106 Header,
107 Footer,
108 Menu,
109 Form,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub struct MenuItem {
115 pub id: String,
116 pub label: String,
117 pub icon: Option<String>,
118 pub route: Option<String>,
119 pub action: Option<String>,
120 pub required_permissions: Vec<Permission>,
121 pub order: i32,
122 pub children: Vec<MenuItem>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct ApiRoute {
128 pub path: String,
129 pub method: HttpMethod,
130 pub handler_id: String,
131 pub required_permissions: Vec<Permission>,
132 pub rate_limit: Option<RateLimit>,
133 pub documentation: ApiDocumentation,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub enum HttpMethod {
139 GET,
140 POST,
141 PUT,
142 DELETE,
143 PATCH,
144 HEAD,
145 OPTIONS,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct RateLimit {
151 pub requests_per_minute: u32,
152 pub burst_limit: u32,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ApiDocumentation {
158 pub summary: String,
159 pub description: String,
160 pub parameters: Vec<ApiParameter>,
161 pub responses: Vec<ApiResponse>,
162 pub examples: Vec<ApiExample>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct ApiParameter {
168 pub name: String,
169 pub parameter_type: ParameterType,
170 pub required: bool,
171 pub description: String,
172 pub example: Option<serde_json::Value>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub enum ParameterType {
178 Query,
179 Path,
180 Header,
181 Body,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct ApiExample {
187 pub name: String,
188 pub description: String,
189 pub request: serde_json::Value,
190 pub response: serde_json::Value,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ApiResponse {
196 pub status_code: u16,
197 pub description: String,
198 pub schema: Option<serde_json::Value>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct EventHandler {
204 pub event_type: String,
205 pub handler_id: String,
206 pub priority: i32,
207}
208
209#[derive(Clone, Debug)]
211pub struct PluginContext {
212 pub plugin_id: String,
213 pub config: PluginConfig,
214 pub api_client: PluginApiClient,
215 pub event_bus: Arc<EventBusManager>,
216 pub database: Option<PluginDatabase>,
217 pub file_system: PluginFileSystem,
218}
219
220#[derive(Debug, Clone)]
222pub struct PluginApiClient {
223 #[allow(dead_code)]
224 plugin_id: String,
225}
226
227impl PluginApiClient {
228 pub fn new(plugin_id: String) -> Self {
230 Self { plugin_id }
231 }
232
233 pub async fn get_config(&self, _key: &str) -> Result<Option<serde_json::Value>> {
235 Ok(None)
237 }
238
239 pub async fn set_config(&self, _key: &str, _value: serde_json::Value) -> Result<()> {
241 Ok(())
243 }
244
245 pub async fn get_current_user(&self) -> Result<Option<User>> {
247 Ok(None)
249 }
250
251 pub async fn check_permission(&self, _resource: &str, _action: &str) -> Result<bool> {
253 Ok(false)
255 }
256}
257
258#[derive(Clone, Debug)]
260pub struct PluginDatabase {
261 plugin_id: String,
262 provider: DatabaseArc,
263 permissions: DatabasePermissions,
264}
265
266#[derive(Debug, Clone)]
268pub struct DatabasePermissions {
269 pub can_create_tables: bool,
270 pub can_drop_tables: bool,
271 pub can_modify_schema: bool,
272 pub max_table_count: Option<u32>,
273 pub max_storage_size: Option<u64>,
274}
275
276impl PluginDatabase {
277 pub fn new(plugin_id: String, provider: DatabaseArc, permissions: DatabasePermissions) -> Self {
279 Self {
280 plugin_id,
281 provider,
282 permissions,
283 }
284 }
285
286 pub async fn execute(
288 &self,
289 query: &str,
290 params: &[serde_json::Value],
291 ) -> Result<crate::platform::database::QueryResult> {
292 if query.to_uppercase().contains("CREATE TABLE") && !self.permissions.can_create_tables {
294 return Err(Error::permission(
295 "database.create_table",
296 "Plugin not allowed to create tables",
297 ));
298 }
299
300 if query.to_uppercase().contains("DROP TABLE") && !self.permissions.can_drop_tables {
301 return Err(Error::permission(
302 "database.drop_table",
303 "Plugin not allowed to drop tables",
304 ));
305 }
306
307 let prefixed_query = self.add_table_prefix(query);
309
310 self.provider.execute(&prefixed_query, params).await
311 }
312
313 pub async fn query(
315 &self,
316 query: &str,
317 params: &[serde_json::Value],
318 ) -> Result<Vec<crate::platform::database::Row>> {
319 let prefixed_query = self.add_table_prefix(query);
320 self.provider.query(&prefixed_query, params).await
321 }
322
323 fn add_table_prefix(&self, query: &str) -> String {
324 query.replace("TABLE ", &format!("TABLE plugin_{}_ ", self.plugin_id))
326 }
327}
328
329#[derive(Clone, Debug)]
331pub struct PluginFileSystem {
332 #[allow(dead_code)]
333 plugin_id: String,
334 provider: FileSystemArc,
335 base_path: String,
336}
337
338impl PluginFileSystem {
339 pub fn new(plugin_id: String, provider: FileSystemArc) -> Self {
341 Self {
342 plugin_id: plugin_id.clone(),
343 provider,
344 base_path: format!("plugins/{}/", plugin_id),
345 }
346 }
347
348 pub async fn read_file(&self, path: &str) -> Result<Vec<u8>> {
350 let safe_path = self.make_safe_path(path)?;
351 self.provider.read_file(&safe_path).await
352 }
353
354 pub async fn write_file(&self, path: &str, data: &[u8]) -> Result<()> {
356 let safe_path = self.make_safe_path(path)?;
357 self.provider.write_file(&safe_path, data).await
358 }
359
360 fn make_safe_path(&self, path: &str) -> Result<String> {
361 if path.contains("..") || path.starts_with('/') {
363 return Err(Error::permission("file.access", "Invalid file path"));
364 }
365
366 Ok(format!("{}{}", self.base_path, path))
367 }
368}
369
370#[derive(Debug, Clone)]
372pub struct ResourceLimits {
373 pub max_memory_mb: u64,
374 pub max_cpu_time_ms: u64,
375 pub max_file_size_mb: u64,
376 pub max_network_requests_per_minute: u32,
377 pub max_database_queries_per_minute: u32,
378}
379
380impl Default for ResourceLimits {
381 fn default() -> Self {
382 Self {
383 max_memory_mb: 100,
384 max_cpu_time_ms: 5000,
385 max_file_size_mb: 10,
386 max_network_requests_per_minute: 60,
387 max_database_queries_per_minute: 100,
388 }
389 }
390}
391
392pub struct PluginSandbox {
394 #[allow(dead_code)]
395 resource_limits: ResourceLimits,
396 allowed_permissions: Vec<Permission>,
397}
398
399impl PluginSandbox {
400 pub fn new(resource_limits: ResourceLimits, allowed_permissions: Vec<Permission>) -> Self {
402 Self {
403 resource_limits,
404 allowed_permissions,
405 }
406 }
407
408 pub fn check_operation(&self, operation: &str, resource: &str) -> bool {
410 self.allowed_permissions.iter().any(|p| {
411 (p.resource == resource || p.resource == "*")
412 && (p.action == operation || p.action == "*")
413 })
414 }
415}
416
417#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
419#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
420pub trait Plugin: Send + Sync + std::fmt::Debug {
421 fn info(&self) -> PluginInfo;
423
424 fn required_dependencies(&self) -> Vec<PluginDependency>;
426
427 fn required_permissions(&self) -> Vec<Permission>;
429
430 async fn initialize(&mut self, context: PluginContext) -> Result<()>;
432
433 async fn shutdown(&mut self) -> Result<()>;
435
436 fn ui_components(&self) -> Vec<UIComponent>;
438
439 fn menu_items(&self) -> Vec<MenuItem>;
441
442 fn settings_schema(&self) -> Option<SettingsSchema>;
444
445 fn api_routes(&self) -> Vec<ApiRoute>;
447
448 fn event_handlers(&self) -> Vec<EventHandler>;
450
451 fn render_component(&self, component_id: &str, props: serde_json::Value) -> Result<VNode>;
453
454 async fn handle_api_request(&self, route_id: &str, request: ApiRequest) -> Result<ApiResponse>;
456
457 async fn handle_event(&self, handler_id: &str, event: &dyn Event) -> Result<()>;
459}
460
461#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
463#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
464pub trait PluginLoader: Send + Sync {
465 async fn load_plugin(&self, path: &str) -> Result<Box<dyn Plugin>>;
467
468 async fn validate_plugin(&self, plugin: &dyn Plugin) -> Result<ValidationResult>;
470
471 async fn unload_plugin(&self, plugin_id: &str) -> Result<()>;
473}
474
475#[derive(Debug, Clone)]
477pub struct ValidationResult {
478 pub is_valid: bool,
479 pub errors: Vec<String>,
480 pub warnings: Vec<String>,
481}
482
483#[derive(Debug, Clone)]
485pub struct ApiRequest {
486 pub method: String,
487 pub path: String,
488 pub headers: HashMap<String, String>,
489 pub query_params: HashMap<String, String>,
490 pub body: Option<serde_json::Value>,
491 pub user: Option<User>,
492}
493
494#[derive(Debug)]
496pub struct PluginRegistry {
497 plugins: HashMap<String, Box<dyn Plugin>>,
498 dependencies: HashMap<String, Vec<String>>,
499 load_order: Vec<String>,
500}
501
502impl Default for PluginRegistry {
503 fn default() -> Self {
504 Self::new()
505 }
506}
507
508impl PluginRegistry {
509 pub fn new() -> Self {
511 Self {
512 plugins: HashMap::new(),
513 dependencies: HashMap::new(),
514 load_order: Vec::new(),
515 }
516 }
517
518 pub fn register(&mut self, plugin: Box<dyn Plugin>) -> Result<()> {
520 let info = plugin.info();
521 let deps = plugin
522 .required_dependencies()
523 .into_iter()
524 .map(|d| d.plugin_id)
525 .collect();
526
527 if self.plugins.contains_key(&info.id) {
528 return Err(Error::plugin(&info.id, "Plugin already registered"));
529 }
530
531 self.plugins.insert(info.id.clone(), plugin);
532 self.dependencies.insert(info.id.clone(), deps);
533
534 self.calculate_load_order()?;
535
536 Ok(())
537 }
538
539 pub fn get(&self, plugin_id: &str) -> Option<&dyn Plugin> {
541 self.plugins.get(plugin_id).map(|p| p.as_ref())
542 }
543
544 pub fn list(&self) -> Vec<&str> {
546 self.plugins.keys().map(|s| s.as_str()).collect()
547 }
548
549 pub fn load_order(&self) -> &[String] {
551 &self.load_order
552 }
553
554 fn calculate_load_order(&mut self) -> Result<()> {
555 let mut order = Vec::new();
556 let mut visited = std::collections::HashSet::new();
557 let mut visiting = std::collections::HashSet::new();
558
559 for plugin_id in self.plugins.keys() {
560 if !visited.contains(plugin_id) {
561 self.visit_plugin(plugin_id, &mut order, &mut visited, &mut visiting)?;
562 }
563 }
564
565 self.load_order = order;
566 Ok(())
567 }
568
569 fn visit_plugin(
570 &self,
571 plugin_id: &str,
572 order: &mut Vec<String>,
573 visited: &mut std::collections::HashSet<String>,
574 visiting: &mut std::collections::HashSet<String>,
575 ) -> Result<()> {
576 if visiting.contains(plugin_id) {
577 return Err(Error::plugin(plugin_id, "Circular dependency detected"));
578 }
579
580 if visited.contains(plugin_id) {
581 return Ok(());
582 }
583
584 visiting.insert(plugin_id.to_string());
585
586 if let Some(deps) = self.dependencies.get(plugin_id) {
587 for dep in deps {
588 if !self.plugins.contains_key(dep) {
589 return Err(Error::plugin(
590 plugin_id,
591 format!("Missing dependency: {}", dep),
592 ));
593 }
594 self.visit_plugin(dep, order, visited, visiting)?;
595 }
596 }
597
598 visiting.remove(plugin_id);
599 visited.insert(plugin_id.to_string());
600 order.push(plugin_id.to_string());
601
602 Ok(())
603 }
604}
605
606pub struct DependencyResolver;
608
609impl Default for DependencyResolver {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615impl DependencyResolver {
616 pub fn new() -> Self {
618 Self
619 }
620
621 pub fn resolve(&self, plugin: &dyn Plugin, registry: &PluginRegistry) -> Result<Vec<String>> {
623 let deps = plugin.required_dependencies();
624 let mut resolved = Vec::new();
625
626 for dep in deps {
627 if registry.get(&dep.plugin_id).is_some() {
628 resolved.push(dep.plugin_id);
629 } else if !dep.optional {
630 return Err(Error::plugin(
631 &plugin.info().id,
632 format!("Required dependency not found: {}", dep.plugin_id),
633 ));
634 }
635 }
636
637 Ok(resolved)
638 }
639
640 pub fn check_version_compatibility(&self, required: &str, available: &str) -> bool {
642 required == available || required == "*"
644 }
645}
646
647pub struct PluginManager {
649 state: ManagedState,
650 registry: PluginRegistry,
651 loader: Box<dyn PluginLoader>,
652 #[allow(dead_code)]
653 sandbox: PluginSandbox,
654 api_provider: PluginApiProvider,
655 dependency_resolver: DependencyResolver,
656 plugin_contexts: HashMap<String, PluginContext>,
657}
658
659impl std::fmt::Debug for PluginManager {
660 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
661 f.debug_struct("PluginManager")
662 .field("registry", &self.registry)
663 .finish()
664 }
665}
666
667pub struct PluginApiProvider;
669
670impl PluginApiProvider {
671 pub fn new() -> Self {
673 Self
674 }
675
676 pub fn create_client(&self, plugin_id: String) -> PluginApiClient {
678 PluginApiClient::new(plugin_id)
679 }
680}
681
682impl Default for PluginApiProvider {
683 fn default() -> Self {
684 Self::new()
685 }
686}
687
688impl PluginManager {
689 pub fn new(loader: Box<dyn PluginLoader>) -> Self {
691 Self {
692 state: ManagedState::new(Uuid::new_v4(), "plugin_manager"),
693 registry: PluginRegistry::new(),
694 loader,
695 sandbox: PluginSandbox::new(ResourceLimits::default(), Vec::new()),
696 api_provider: PluginApiProvider::new(),
697 dependency_resolver: DependencyResolver::new(),
698 plugin_contexts: HashMap::new(),
699 }
700 }
701
702 pub async fn load_plugin(&mut self, path: &str) -> Result<()> {
704 let plugin = self.loader.load_plugin(path).await?;
705
706 let validation = self.loader.validate_plugin(plugin.as_ref()).await?;
708 if !validation.is_valid {
709 return Err(Error::plugin(
710 &plugin.info().id,
711 format!("Plugin validation failed: {:?}", validation.errors),
712 ));
713 }
714
715 let _resolved_deps = self
717 .dependency_resolver
718 .resolve(plugin.as_ref(), &self.registry)?;
719
720 let plugin_id = plugin.info().id.clone();
722 self.registry.register(plugin)?;
723
724 let context = self.create_plugin_context(&plugin_id).await?;
726 self.plugin_contexts.insert(plugin_id, context);
727
728 Ok(())
729 }
730
731 pub async fn unload_plugin(&mut self, plugin_id: &str) -> Result<()> {
733 if let Some(plugin) = self.registry.plugins.get_mut(plugin_id) {
734 plugin.shutdown().await?;
735 }
736
737 self.registry.plugins.remove(plugin_id);
738 self.plugin_contexts.remove(plugin_id);
739 self.loader.unload_plugin(plugin_id).await?;
740
741 Ok(())
742 }
743
744 pub async fn initialize_plugins(&mut self) -> Result<()> {
746 let load_order = self.registry.load_order().to_vec();
747
748 for plugin_id in load_order {
749 if let (Some(plugin), Some(context)) = (
750 self.registry.plugins.get_mut(&plugin_id),
751 self.plugin_contexts.get(&plugin_id).cloned(),
752 ) {
753 plugin.initialize(context).await.map_err(|e| {
754 Error::plugin(&plugin_id, format!("Plugin initialization failed: {}", e))
755 })?;
756 }
757 }
758
759 Ok(())
760 }
761
762 pub fn get_ui_components(&self) -> Vec<(String, UIComponent)> {
764 let mut components = Vec::new();
765
766 for (plugin_id, plugin) in &self.registry.plugins {
767 for component in plugin.ui_components() {
768 components.push((plugin_id.clone(), component));
769 }
770 }
771
772 components
773 }
774
775 pub fn get_menu_items(&self) -> Vec<(String, MenuItem)> {
777 let mut items = Vec::new();
778
779 for (plugin_id, plugin) in &self.registry.plugins {
780 for item in plugin.menu_items() {
781 items.push((plugin_id.clone(), item));
782 }
783 }
784
785 items
786 }
787
788 pub fn render_component(
790 &self,
791 plugin_id: &str,
792 component_id: &str,
793 props: serde_json::Value,
794 ) -> Result<VNode> {
795 let plugin = self
796 .registry
797 .get(plugin_id)
798 .ok_or_else(|| Error::plugin(plugin_id, "Plugin not found"))?;
799
800 plugin.render_component(component_id, props)
801 }
802
803 async fn create_plugin_context(&self, plugin_id: &str) -> Result<PluginContext> {
804 Ok(PluginContext {
807 plugin_id: plugin_id.to_string(),
808 config: PluginConfig {
809 plugin_id: plugin_id.to_string(),
810 version: "1.0.0".to_string(),
811 config_schema: serde_json::json!({}),
812 default_values: serde_json::json!({}),
813 user_overrides: serde_json::json!({}),
814 validation_rules: Vec::new(),
815 },
816 api_client: self.api_provider.create_client(plugin_id.to_string()),
817 event_bus: Arc::new(EventBusManager::new(crate::event::EventBusConfig::default())),
818 database: None,
819 file_system: PluginFileSystem {
820 plugin_id: plugin_id.to_string(),
821 provider: Arc::new(crate::platform::MockFileSystem::new()),
822 base_path: format!("plugins/{}/", plugin_id),
823 },
824 })
825 }
826}
827
828#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
829#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
830impl Manager for PluginManager {
831 fn name(&self) -> &str {
832 "plugin_manager"
833 }
834
835 fn id(&self) -> Uuid {
836 self.state.id()
837 }
838
839 async fn initialize(&mut self) -> Result<()> {
840 self.state
841 .set_state(crate::manager::ManagerState::Initializing)
842 .await;
843
844 self.initialize_plugins().await?;
846
847 self.state
848 .set_state(crate::manager::ManagerState::Running)
849 .await;
850 Ok(())
851 }
852
853 async fn shutdown(&mut self) -> Result<()> {
854 self.state
855 .set_state(crate::manager::ManagerState::ShuttingDown)
856 .await;
857
858 let mut load_order = self.registry.load_order().to_vec();
860 load_order.reverse();
861
862 for plugin_id in load_order {
863 if let Err(e) = self.unload_plugin(&plugin_id).await {
864 tracing::error!("Failed to unload plugin {}: {}", plugin_id, e);
865 }
866 }
867
868 self.state
869 .set_state(crate::manager::ManagerState::Shutdown)
870 .await;
871 Ok(())
872 }
873
874 async fn status(&self) -> ManagerStatus {
875 let mut status = self.state.status().await;
876
877 status.add_metadata(
878 "loaded_plugins",
879 serde_json::Value::from(self.registry.plugins.len()),
880 );
881 status.add_metadata(
882 "plugin_list",
883 serde_json::Value::Array(
884 self.registry
885 .list()
886 .into_iter()
887 .map(|s| serde_json::Value::String(s.to_string()))
888 .collect(),
889 ),
890 );
891
892 status
893 }
894
895 fn platform_requirements(&self) -> PlatformRequirements {
896 PlatformRequirements {
897 requires_filesystem: true,
898 requires_network: false,
899 requires_database: false,
900 requires_native_apis: false,
901 minimum_permissions: vec!["plugin.load".to_string(), "plugin.manage".to_string()],
902 }
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909
910 #[derive(Debug)]
911 struct TestPlugin {
912 info: PluginInfo,
913 }
914
915 impl TestPlugin {
916 fn new(id: String) -> Self {
917 Self {
918 info: PluginInfo {
919 id,
920 name: "Test Plugin".to_string(),
921 version: "1.0.0".to_string(),
922 description: "A test plugin".to_string(),
923 author: "Test Author".to_string(),
924 license: "MIT".to_string(),
925 homepage: None,
926 repository: None,
927 minimum_core_version: "1.0.0".to_string(),
928 supported_platforms: vec![Platform::All],
929 },
930 }
931 }
932 }
933
934 #[async_trait]
935 impl Plugin for TestPlugin {
936 fn info(&self) -> PluginInfo {
937 self.info.clone()
938 }
939
940 fn required_dependencies(&self) -> Vec<PluginDependency> {
941 Vec::new()
942 }
943
944 fn required_permissions(&self) -> Vec<Permission> {
945 Vec::new()
946 }
947
948 async fn initialize(&mut self, _context: PluginContext) -> Result<()> {
949 Ok(())
950 }
951
952 async fn shutdown(&mut self) -> Result<()> {
953 Ok(())
954 }
955
956 fn ui_components(&self) -> Vec<UIComponent> {
957 Vec::new()
958 }
959
960 fn menu_items(&self) -> Vec<MenuItem> {
961 Vec::new()
962 }
963
964 fn settings_schema(&self) -> Option<SettingsSchema> {
965 None
966 }
967
968 fn api_routes(&self) -> Vec<ApiRoute> {
969 Vec::new()
970 }
971
972 fn event_handlers(&self) -> Vec<EventHandler> {
973 Vec::new()
974 }
975
976 fn render_component(
977 &self,
978 _component_id: &str,
979 _props: serde_json::Value,
980 ) -> Result<VNode> {
981 Err(Error::plugin(
982 &self.info.id,
983 "Component rendering not implemented",
984 ))
985 }
986
987 async fn handle_api_request(
988 &self,
989 _route_id: &str,
990 _request: ApiRequest,
991 ) -> Result<ApiResponse> {
992 Err(Error::plugin(&self.info.id, "API handling not implemented"))
993 }
994
995 async fn handle_event(&self, _handler_id: &str, _event: &dyn Event) -> Result<()> {
996 Ok(())
997 }
998 }
999
1000 #[test]
1001 fn test_plugin_registry() {
1002 let mut registry = PluginRegistry::new();
1003 let plugin = Box::new(TestPlugin::new("test_plugin".to_string()));
1004
1005 registry.register(plugin).unwrap();
1006
1007 assert!(registry.get("test_plugin").is_some());
1008 assert_eq!(registry.list().len(), 1);
1009 }
1010
1011 #[test]
1012 fn test_dependency_resolution() {
1013 let resolver = DependencyResolver::new();
1014 let registry = PluginRegistry::new();
1015 let plugin = TestPlugin::new("test_plugin".to_string());
1016
1017 let resolved = resolver.resolve(&plugin, ®istry).unwrap();
1018 assert!(resolved.is_empty()); }
1020}