vanguard-plugin-sdk 0.1.4

SDK for developing Vanguard plugins
Documentation
//! Vanguard Plugin SDK
//!
//! This crate provides tools and utilities for developing Vanguard plugins.
//! It includes macros and helper functions to make plugin development easier
//! and safer.

mod template;

pub use template::{generate_plugin, PluginOptions, TemplateError, TemplateResult};
use thiserror::Error;
// Re-export these for use in macros
pub use vanguard_plugin::{PluginMetadata, ValidationResult, VanguardPlugin};

/// Errors that can occur during plugin development
#[derive(Error, Debug)]
pub enum PluginError {
    /// Plugin validation failed
    #[error("Validation failed: {0}")]
    ValidationFailed(String),

    /// Plugin initialization failed
    #[error("Initialization failed: {0}")]
    InitializationFailed(String),

    /// Plugin configuration error
    #[error("Configuration error: {0}")]
    ConfigError(String),

    /// IO error
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
}

/// Result type for plugin operations
pub type PluginResult<T> = Result<T, PluginError>;

/// Helper trait for building plugin metadata
pub trait PluginMetadataBuilder {
    /// Set the plugin name
    fn name(self, name: impl Into<String>) -> Self;
    /// Set the plugin version
    fn version(self, version: impl Into<String>) -> Self;
    /// Set the plugin description
    fn description(self, description: impl Into<String>) -> Self;
    /// Set the plugin author
    fn author(self, author: impl Into<String>) -> Self;
    /// Set the minimum Vanguard version required
    fn min_vanguard_version(self, version: impl Into<String>) -> Self;
    /// Set the maximum Vanguard version supported
    fn max_vanguard_version(self, version: impl Into<String>) -> Self;
    /// Build the metadata
    fn build(self) -> PluginMetadata;
}

/// Helper struct for building plugin metadata
#[derive(Debug, Default)]
pub struct MetadataBuilder {
    name: Option<String>,
    version: Option<String>,
    description: Option<String>,
    author: Option<String>,
    min_vanguard_version: Option<String>,
    max_vanguard_version: Option<String>,
}

impl PluginMetadataBuilder for MetadataBuilder {
    fn name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    fn version(mut self, version: impl Into<String>) -> Self {
        self.version = Some(version.into());
        self
    }

    fn description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }

    fn author(mut self, author: impl Into<String>) -> Self {
        self.author = Some(author.into());
        self
    }

    fn min_vanguard_version(mut self, version: impl Into<String>) -> Self {
        self.min_vanguard_version = Some(version.into());
        self
    }

    fn max_vanguard_version(mut self, version: impl Into<String>) -> Self {
        self.max_vanguard_version = Some(version.into());
        self
    }

    fn build(self) -> PluginMetadata {
        PluginMetadata {
            name: self.name.unwrap_or_else(|| "unnamed".to_string()),
            version: self.version.unwrap_or_else(|| "0.1.0".to_string()),
            description: self
                .description
                .unwrap_or_else(|| "No description".to_string()),
            author: self.author.unwrap_or_else(|| "Unknown".to_string()),
            min_vanguard_version: self.min_vanguard_version,
            max_vanguard_version: self.max_vanguard_version,
            dependencies: vec![],
        }
    }
}

/// Creates a new plugin metadata builder
pub fn metadata() -> MetadataBuilder {
    MetadataBuilder::default()
}

/// Helper macro for implementing the VanguardPlugin trait
#[macro_export]
macro_rules! plugin {
    ($plugin:ident, $config:ident) => {
        #[async_trait::async_trait]
        impl $crate::VanguardPlugin for $plugin {
            fn metadata(&self) -> &$crate::PluginMetadata {
                &self.metadata
            }

            async fn validate(&self) -> $crate::ValidationResult {
                if let Some(config) = &self.config {
                    if let Err(e) = config.validate() {
                        return $crate::ValidationResult::Failed(e.to_string());
                    }
                }
                $crate::ValidationResult::Passed
            }

            async fn initialize(&self) -> Result<(), String> {
                Ok(())
            }

            async fn cleanup(&self) -> Result<(), String> {
                Ok(())
            }

            fn config_schema(&self) -> Option<serde_json::Value> {
                Some($config::schema())
            }
        }

        #[cfg(not(test))]
        #[no_mangle]
        pub extern "C" fn create_plugin() -> *mut $plugin {
            Box::into_raw(Box::new($plugin::new()))
        }

        #[cfg(not(test))]
        #[no_mangle]
        pub unsafe extern "C" fn destroy_plugin(plugin: *mut $plugin) {
            if !plugin.is_null() {
                let _ = Box::from_raw(plugin);
            }
        }

        #[cfg(test)]
        #[allow(private_interfaces)]
        #[no_mangle]
        pub extern "C" fn create_plugin() -> *mut $plugin {
            Box::into_raw(Box::new($plugin::new()))
        }

        #[cfg(test)]
        #[allow(private_interfaces)]
        #[no_mangle]
        pub unsafe extern "C" fn destroy_plugin(plugin: *mut $plugin) {
            if !plugin.is_null() {
                let _ = Box::from_raw(plugin);
            }
        }
    };
}

/// Helper macro for implementing plugin configuration
#[macro_export]
macro_rules! plugin_config {
    ($config:ident, $schema:expr) => {
        impl $config {
            pub fn schema() -> serde_json::Value {
                $schema
            }

            pub fn validate(&self) -> Result<(), String> {
                Ok(())
            }
        }
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::{Deserialize, Serialize};

    #[derive(Debug, Serialize, Deserialize)]
    struct TestConfig {
        value: String,
    }

    plugin_config!(
        TestConfig,
        serde_json::json!({
            "type": "object",
            "properties": {
                "value": {
                    "type": "string"
                }
            }
        })
    );

    #[derive(Debug)]
    struct TestPlugin {
        metadata: PluginMetadata,
        config: Option<TestConfig>,
    }

    impl TestPlugin {
        fn new() -> Self {
            Self {
                metadata: metadata()
                    .name("test")
                    .version("1.0.0")
                    .description("Test plugin")
                    .author("Test Author")
                    .build(),
                config: None,
            }
        }
    }

    plugin!(TestPlugin, TestConfig);

    #[tokio::test]
    async fn test_plugin_metadata() {
        let plugin = TestPlugin::new();
        assert_eq!(plugin.metadata().name, "test");
        assert_eq!(plugin.metadata().version, "1.0.0");
    }

    #[tokio::test]
    async fn test_plugin_validation() {
        let plugin = TestPlugin::new();
        assert!(matches!(plugin.validate().await, ValidationResult::Passed));
    }
}

// Re-export the command module
pub mod command;
pub use command::{Command, CommandContext, CommandHandler, CommandResult, VanguardCommand};

// Update the plugin! macro to include the new command handler methods
#[doc(hidden)]
pub fn _update_plugin_macro() {
    // This function is never called, it's just a note to update the plugin! macro
    // to handle commands in a future update
}

/// Helper macro for implementing CommandHandler trait
#[macro_export]
macro_rules! command_handler {
    ($plugin:ident) => {
        #[async_trait::async_trait]
        impl $crate::command::CommandHandler for $plugin {
            fn get_commands(&self) -> Vec<$crate::command::Command> {
                Vec::new()
            }

            async fn handle_command(
                &self,
                _command: &$crate::command::VanguardCommand,
                _ctx: &$crate::command::CommandContext,
            ) -> $crate::command::CommandResult {
                $crate::command::CommandResult::NotHandled
            }
        }
    };

    ($plugin:ident, $commands:expr) => {
        #[async_trait::async_trait]
        impl $crate::command::CommandHandler for $plugin {
            fn get_commands(&self) -> Vec<$crate::command::Command> {
                $commands
            }

            async fn handle_command(
                &self,
                _command: &$crate::command::VanguardCommand,
                _ctx: &$crate::command::CommandContext,
            ) -> $crate::command::CommandResult {
                $crate::command::CommandResult::NotHandled
            }
        }
    };

    ($plugin:ident, $commands:expr, $handler:expr) => {
        #[async_trait::async_trait]
        impl $crate::command::CommandHandler for $plugin {
            fn get_commands(&self) -> Vec<$crate::command::Command> {
                $commands
            }

            async fn handle_command(
                &self,
                command: &$crate::command::VanguardCommand,
                ctx: &$crate::command::CommandContext,
            ) -> $crate::command::CommandResult {
                ($handler)(self, command, ctx).await
            }
        }
    };
}