1#![deny(missing_docs)]
2#![deny(clippy::all)]
3
4use async_trait::async_trait;
11use semver::Version;
12use serde::{Deserialize, Serialize};
13use std::fmt::Debug;
14use thiserror::Error;
15
16mod config;
17mod dylib;
18pub mod exports;
20mod loader;
21mod registry;
22mod validator;
23
24pub use config::*;
25pub use dylib::{get_dylib_name, get_dylib_path, DylibError, PluginLibrary};
26pub use exports::{CreatePluginFn, CREATE_PLUGIN_SYMBOL};
27pub use loader::{LoaderConfig, LoaderError, PluginLoader};
28pub use registry::{PluginRegistry, PluginState, RegistryError};
29pub use validator::*;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct PluginInfo {
34 pub name: String,
36 pub version: String,
38 pub description: String,
40 pub enabled: bool,
42 pub path: String,
44 pub install_date: String,
46}
47
48#[derive(Debug)]
50pub enum ValidationResult {
51 Passed,
53 Failed(String),
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
59pub struct Platform {
60 pub os: String,
62 pub arch: String,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct PluginMetadata {
69 pub name: String,
71 pub version: String,
73 pub description: String,
75 pub author: String,
77 pub min_vanguard_version: Option<String>,
79 pub max_vanguard_version: Option<String>,
81 pub dependencies: Vec<PluginDependency>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct PluginDependency {
88 pub name: String,
90 pub version: String,
92}
93
94#[derive(Error, Debug)]
96pub enum PluginError {
97 #[error("Plugin validation failed: {0}")]
99 ValidationFailed(String),
100
101 #[error("Plugin initialization failed: {0}")]
103 InitializationFailed(String),
104
105 #[error("Plugin operation failed: {0}")]
107 OperationFailed(String),
108
109 #[error("Plugin is incompatible: {0}")]
111 Incompatible(String),
112}
113
114#[async_trait]
116pub trait VanguardPlugin: Send + Sync + std::fmt::Debug {
117 fn metadata(&self) -> &PluginMetadata;
119
120 async fn validate(&self) -> ValidationResult;
122
123 async fn initialize(&self) -> Result<(), String>;
125
126 async fn cleanup(&self) -> Result<(), String>;
128
129 fn is_compatible_with(&self, vanguard_version: &Version) -> bool {
131 let metadata = self.metadata();
132
133 if let Some(min_version) = &metadata.min_vanguard_version {
135 if vanguard_version < &Version::parse(min_version).unwrap() {
136 return false;
137 }
138 }
139
140 if let Some(max_version) = &metadata.max_vanguard_version {
142 if vanguard_version >= &Version::parse(max_version).unwrap() {
143 return false;
144 }
145 }
146
147 true
148 }
149
150 fn config_schema(&self) -> Option<serde_json::Value> {
152 None
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 struct TestPlugin {
161 metadata: PluginMetadata,
162 }
163
164 impl std::fmt::Debug for TestPlugin {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 f.debug_struct("TestPlugin")
167 .field("metadata", &self.metadata)
168 .finish()
169 }
170 }
171
172 #[async_trait]
173 impl VanguardPlugin for TestPlugin {
174 fn metadata(&self) -> &PluginMetadata {
175 &self.metadata
176 }
177
178 async fn validate(&self) -> ValidationResult {
179 ValidationResult::Passed
180 }
181
182 async fn initialize(&self) -> Result<(), String> {
183 Ok(())
184 }
185
186 async fn cleanup(&self) -> Result<(), String> {
187 Ok(())
188 }
189 }
190
191 fn create_test_plugin() -> TestPlugin {
192 TestPlugin {
193 metadata: PluginMetadata {
194 name: "test-plugin".to_string(),
195 version: "1.0.0".to_string(),
196 description: "Test Plugin".to_string(),
197 author: "Test Author".to_string(),
198 min_vanguard_version: Some("0.1.0".to_string()),
199 max_vanguard_version: Some("2.0.0".to_string()),
200 dependencies: vec![PluginDependency {
201 name: "core".to_string(),
202 version: ">=1.0.0".to_string(),
203 }],
204 },
205 }
206 }
207
208 #[test]
209 fn test_plugin_metadata() {
210 let plugin = create_test_plugin();
211 assert_eq!(plugin.metadata().name, "test-plugin");
212 assert_eq!(plugin.metadata().version, "1.0.0");
213 }
214
215 #[test]
216 fn test_plugin_compatibility() {
217 let plugin = create_test_plugin();
218
219 assert!(plugin.is_compatible_with(&Version::new(0, 1, 0))); assert!(plugin.is_compatible_with(&Version::new(1, 0, 0))); assert!(!plugin.is_compatible_with(&Version::new(2, 0, 0))); assert!(!plugin.is_compatible_with(&Version::new(0, 0, 9))); }
225
226 #[tokio::test]
227 async fn test_plugin_lifecycle() {
228 let plugin = create_test_plugin();
229
230 assert!(plugin.initialize().await.is_ok());
232
233 assert!(matches!(plugin.validate().await, ValidationResult::Passed));
235
236 assert!(plugin.cleanup().await.is_ok());
238 }
239}