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}
254
255pub mod command;
257pub use command::{Command, CommandContext, CommandHandler, CommandResult, VanguardCommand};
258
259#[doc(hidden)]
261pub fn _update_plugin_macro() {
262 }
265
266#[macro_export]
268macro_rules! command_handler {
269 ($plugin:ident) => {
270 #[async_trait::async_trait]
271 impl $crate::command::CommandHandler for $plugin {
272 fn get_commands(&self) -> Vec<$crate::command::Command> {
273 Vec::new()
274 }
275
276 async fn handle_command(
277 &self,
278 _command: &$crate::command::VanguardCommand,
279 _ctx: &$crate::command::CommandContext,
280 ) -> $crate::command::CommandResult {
281 $crate::command::CommandResult::NotHandled
282 }
283 }
284 };
285
286 ($plugin:ident, $commands:expr) => {
287 #[async_trait::async_trait]
288 impl $crate::command::CommandHandler for $plugin {
289 fn get_commands(&self) -> Vec<$crate::command::Command> {
290 $commands
291 }
292
293 async fn handle_command(
294 &self,
295 _command: &$crate::command::VanguardCommand,
296 _ctx: &$crate::command::CommandContext,
297 ) -> $crate::command::CommandResult {
298 $crate::command::CommandResult::NotHandled
299 }
300 }
301 };
302
303 ($plugin:ident, $commands:expr, $handler:expr) => {
304 #[async_trait::async_trait]
305 impl $crate::command::CommandHandler for $plugin {
306 fn get_commands(&self) -> Vec<$crate::command::Command> {
307 $commands
308 }
309
310 async fn handle_command(
311 &self,
312 command: &$crate::command::VanguardCommand,
313 ctx: &$crate::command::CommandContext,
314 ) -> $crate::command::CommandResult {
315 ($handler)(self, command, ctx).await
316 }
317 }
318 };
319}