vanguard_plugin_sdk/
lib.rs1mod template;
8
9pub use template::{generate_plugin, PluginOptions, TemplateError, TemplateResult};
10use thiserror::Error;
11pub use vanguard_plugin::{PluginMetadata, ValidationResult, VanguardPlugin};
13
14#[derive(Error, Debug)]
16pub enum PluginError {
17 #[error("Validation failed: {0}")]
19 ValidationFailed(String),
20
21 #[error("Initialization failed: {0}")]
23 InitializationFailed(String),
24
25 #[error("Configuration error: {0}")]
27 ConfigError(String),
28
29 #[error("IO error: {0}")]
31 IoError(#[from] std::io::Error),
32}
33
34pub type PluginResult<T> = Result<T, PluginError>;
36
37pub trait PluginMetadataBuilder {
39 fn name(self, name: impl Into<String>) -> Self;
41 fn version(self, version: impl Into<String>) -> Self;
43 fn description(self, description: impl Into<String>) -> Self;
45 fn author(self, author: impl Into<String>) -> Self;
47 fn min_vanguard_version(self, version: impl Into<String>) -> Self;
49 fn max_vanguard_version(self, version: impl Into<String>) -> Self;
51 fn build(self) -> PluginMetadata;
53}
54
55#[derive(Debug, Default)]
57pub struct MetadataBuilder {
58 name: Option<String>,
59 version: Option<String>,
60 description: Option<String>,
61 author: Option<String>,
62 min_vanguard_version: Option<String>,
63 max_vanguard_version: Option<String>,
64}
65
66impl PluginMetadataBuilder for MetadataBuilder {
67 fn name(mut self, name: impl Into<String>) -> Self {
68 self.name = Some(name.into());
69 self
70 }
71
72 fn version(mut self, version: impl Into<String>) -> Self {
73 self.version = Some(version.into());
74 self
75 }
76
77 fn description(mut self, description: impl Into<String>) -> Self {
78 self.description = Some(description.into());
79 self
80 }
81
82 fn author(mut self, author: impl Into<String>) -> Self {
83 self.author = Some(author.into());
84 self
85 }
86
87 fn min_vanguard_version(mut self, version: impl Into<String>) -> Self {
88 self.min_vanguard_version = Some(version.into());
89 self
90 }
91
92 fn max_vanguard_version(mut self, version: impl Into<String>) -> Self {
93 self.max_vanguard_version = Some(version.into());
94 self
95 }
96
97 fn build(self) -> PluginMetadata {
98 PluginMetadata {
99 name: self.name.unwrap_or_else(|| "unnamed".to_string()),
100 version: self.version.unwrap_or_else(|| "0.1.0".to_string()),
101 description: self
102 .description
103 .unwrap_or_else(|| "No description".to_string()),
104 author: self.author.unwrap_or_else(|| "Unknown".to_string()),
105 min_vanguard_version: self.min_vanguard_version,
106 max_vanguard_version: self.max_vanguard_version,
107 dependencies: vec![],
108 }
109 }
110}
111
112pub fn metadata() -> MetadataBuilder {
114 MetadataBuilder::default()
115}
116
117#[macro_export]
119macro_rules! plugin {
120 ($plugin:ident, $config:ident) => {
121 #[async_trait::async_trait]
122 impl $crate::VanguardPlugin for $plugin {
123 fn metadata(&self) -> &$crate::PluginMetadata {
124 &self.metadata
125 }
126
127 async fn validate(&self) -> $crate::ValidationResult {
128 if let Some(config) = &self.config {
129 if let Err(e) = config.validate() {
130 return $crate::ValidationResult::Failed(e.to_string());
131 }
132 }
133 $crate::ValidationResult::Passed
134 }
135
136 async fn initialize(&self) -> Result<(), String> {
137 Ok(())
138 }
139
140 async fn cleanup(&self) -> Result<(), String> {
141 Ok(())
142 }
143
144 fn config_schema(&self) -> Option<serde_json::Value> {
145 Some($config::schema())
146 }
147 }
148
149 #[cfg(not(test))]
150 #[no_mangle]
151 pub extern "C" fn create_plugin() -> *mut $plugin {
152 Box::into_raw(Box::new($plugin::new()))
153 }
154
155 #[cfg(not(test))]
156 #[no_mangle]
157 pub unsafe extern "C" fn destroy_plugin(plugin: *mut $plugin) {
158 if !plugin.is_null() {
159 let _ = Box::from_raw(plugin);
160 }
161 }
162
163 #[cfg(test)]
164 #[allow(private_interfaces)]
165 #[no_mangle]
166 pub extern "C" fn create_plugin() -> *mut $plugin {
167 Box::into_raw(Box::new($plugin::new()))
168 }
169
170 #[cfg(test)]
171 #[allow(private_interfaces)]
172 #[no_mangle]
173 pub unsafe extern "C" fn destroy_plugin(plugin: *mut $plugin) {
174 if !plugin.is_null() {
175 let _ = Box::from_raw(plugin);
176 }
177 }
178 };
179}
180
181#[macro_export]
183macro_rules! plugin_config {
184 ($config:ident, $schema:expr) => {
185 impl $config {
186 pub fn schema() -> serde_json::Value {
187 $schema
188 }
189
190 pub fn validate(&self) -> Result<(), String> {
191 Ok(())
192 }
193 }
194 };
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use serde::{Deserialize, Serialize};
201
202 #[derive(Debug, Serialize, Deserialize)]
203 struct TestConfig {
204 value: String,
205 }
206
207 plugin_config!(
208 TestConfig,
209 serde_json::json!({
210 "type": "object",
211 "properties": {
212 "value": {
213 "type": "string"
214 }
215 }
216 })
217 );
218
219 #[derive(Debug)]
220 struct TestPlugin {
221 metadata: PluginMetadata,
222 config: Option<TestConfig>,
223 }
224
225 impl TestPlugin {
226 fn new() -> Self {
227 Self {
228 metadata: metadata()
229 .name("test")
230 .version("1.0.0")
231 .description("Test plugin")
232 .author("Test Author")
233 .build(),
234 config: None,
235 }
236 }
237 }
238
239 plugin!(TestPlugin, TestConfig);
240
241 #[tokio::test]
242 async fn test_plugin_metadata() {
243 let plugin = TestPlugin::new();
244 assert_eq!(plugin.metadata().name, "test");
245 assert_eq!(plugin.metadata().version, "1.0.0");
246 }
247
248 #[tokio::test]
249 async fn test_plugin_validation() {
250 let plugin = TestPlugin::new();
251 assert!(matches!(plugin.validate().await, ValidationResult::Passed));
252 }
253}