vanguard_plugin_sdk/
lib.rs

1//! Vanguard Plugin SDK
2//!
3//! This crate provides tools and utilities for developing Vanguard plugins.
4//! It includes macros and helper functions to make plugin development easier
5//! and safer.
6
7mod template;
8
9pub use template::{generate_plugin, PluginOptions, TemplateError, TemplateResult};
10use thiserror::Error;
11// Re-export these for use in macros
12pub use vanguard_plugin::{PluginMetadata, ValidationResult, VanguardPlugin};
13
14/// Errors that can occur during plugin development
15#[derive(Error, Debug)]
16pub enum PluginError {
17    /// Plugin validation failed
18    #[error("Validation failed: {0}")]
19    ValidationFailed(String),
20
21    /// Plugin initialization failed
22    #[error("Initialization failed: {0}")]
23    InitializationFailed(String),
24
25    /// Plugin configuration error
26    #[error("Configuration error: {0}")]
27    ConfigError(String),
28
29    /// IO error
30    #[error("IO error: {0}")]
31    IoError(#[from] std::io::Error),
32}
33
34/// Result type for plugin operations
35pub type PluginResult<T> = Result<T, PluginError>;
36
37/// Helper trait for building plugin metadata
38pub trait PluginMetadataBuilder {
39    /// Set the plugin name
40    fn name(self, name: impl Into<String>) -> Self;
41    /// Set the plugin version
42    fn version(self, version: impl Into<String>) -> Self;
43    /// Set the plugin description
44    fn description(self, description: impl Into<String>) -> Self;
45    /// Set the plugin author
46    fn author(self, author: impl Into<String>) -> Self;
47    /// Set the minimum Vanguard version required
48    fn min_vanguard_version(self, version: impl Into<String>) -> Self;
49    /// Set the maximum Vanguard version supported
50    fn max_vanguard_version(self, version: impl Into<String>) -> Self;
51    /// Build the metadata
52    fn build(self) -> PluginMetadata;
53}
54
55/// Helper struct for building plugin metadata
56#[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
112/// Creates a new plugin metadata builder
113pub fn metadata() -> MetadataBuilder {
114    MetadataBuilder::default()
115}
116
117/// Helper macro for implementing the VanguardPlugin trait
118#[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/// Helper macro for implementing plugin configuration
182#[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
255// Re-export the command module
256pub mod command;
257pub use command::{Command, CommandContext, CommandHandler, CommandResult, VanguardCommand};
258
259// Update the plugin! macro to include the new command handler methods
260#[doc(hidden)]
261pub fn _update_plugin_macro() {
262    // This function is never called, it's just a note to update the plugin! macro
263    // to handle commands in a future update
264}
265
266/// Helper macro for implementing CommandHandler trait
267#[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}