vanguard-plugin 0.1.1

Plugin system for the Vanguard version manager
Documentation
#![deny(missing_docs)]
#![deny(clippy::all)]

//! Vanguard Plugin System
//!
//! This crate provides the core plugin interface and validation system for Vanguard.
//! It is completely decoupled from the CLI and provides a standardized way to create
//! and validate plugins.

use async_trait::async_trait;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use thiserror::Error;

mod config;
mod dylib;
/// Module containing plugin export helpers and macros for implementing plugins
pub mod exports;
mod loader;
mod registry;
mod validator;

pub use config::*;
pub use dylib::{get_dylib_name, get_dylib_path, DylibError, PluginLibrary};
pub use exports::{CreatePluginFn, CREATE_PLUGIN_SYMBOL};
pub use loader::{LoaderConfig, LoaderError, PluginLoader};
pub use registry::{PluginRegistry, PluginState, RegistryError};
pub use validator::*;

/// Plugin information used for storing in the registry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo {
    /// Name of the plugin
    pub name: String,
    /// Version of the plugin
    pub version: String,
    /// Description of the plugin
    pub description: String,
    /// Whether the plugin is enabled
    pub enabled: bool,
    /// Path to the plugin file
    pub path: String,
    /// When the plugin was installed
    pub install_date: String,
}

/// Result of plugin validation
#[derive(Debug)]
pub enum ValidationResult {
    /// Validation passed
    Passed,
    /// Validation failed with reason
    Failed(String),
}

/// Represents a platform that a plugin supports
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Platform {
    /// Operating system (e.g., "linux", "macos", "windows")
    pub os: String,
    /// CPU architecture (e.g., "x86_64", "aarch64")
    pub arch: String,
}

/// Metadata for a Vanguard plugin
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
    /// Name of the plugin
    pub name: String,
    /// Version of the plugin
    pub version: String,
    /// Description of the plugin
    pub description: String,
    /// Author of the plugin
    pub author: String,
    /// Minimum compatible Vanguard version
    pub min_vanguard_version: Option<String>,
    /// Maximum compatible Vanguard version
    pub max_vanguard_version: Option<String>,
    /// Plugin dependencies
    pub dependencies: Vec<PluginDependency>,
}

/// A dependency on another plugin
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginDependency {
    /// Name of the required plugin
    pub name: String,
    /// Version requirement for the plugin
    pub version: String,
}

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

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

    /// Plugin operation failed
    #[error("Plugin operation failed: {0}")]
    OperationFailed(String),

    /// Plugin is incompatible
    #[error("Plugin is incompatible: {0}")]
    Incompatible(String),
}

/// Trait that must be implemented by all Vanguard plugins
#[async_trait]
pub trait VanguardPlugin: Send + Sync + std::fmt::Debug {
    /// Get the plugin's metadata
    fn metadata(&self) -> &PluginMetadata;

    /// Validate the plugin's configuration and environment
    async fn validate(&self) -> ValidationResult;

    /// Initialize the plugin
    async fn initialize(&self) -> Result<(), String>;

    /// Clean up plugin resources
    async fn cleanup(&self) -> Result<(), String>;

    /// Check if plugin is compatible with given Vanguard version
    fn is_compatible_with(&self, vanguard_version: &Version) -> bool {
        let metadata = self.metadata();

        // Check minimum version (inclusive)
        if let Some(min_version) = &metadata.min_vanguard_version {
            if vanguard_version < &Version::parse(min_version).unwrap() {
                return false;
            }
        }

        // Check maximum version if specified (exclusive)
        if let Some(max_version) = &metadata.max_vanguard_version {
            if vanguard_version >= &Version::parse(max_version).unwrap() {
                return false;
            }
        }

        true
    }

    /// Get plugin configuration schema
    fn config_schema(&self) -> Option<serde_json::Value> {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    struct TestPlugin {
        metadata: PluginMetadata,
    }

    impl std::fmt::Debug for TestPlugin {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.debug_struct("TestPlugin")
                .field("metadata", &self.metadata)
                .finish()
        }
    }

    #[async_trait]
    impl VanguardPlugin for TestPlugin {
        fn metadata(&self) -> &PluginMetadata {
            &self.metadata
        }

        async fn validate(&self) -> ValidationResult {
            ValidationResult::Passed
        }

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

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

    fn create_test_plugin() -> TestPlugin {
        TestPlugin {
            metadata: PluginMetadata {
                name: "test-plugin".to_string(),
                version: "1.0.0".to_string(),
                description: "Test Plugin".to_string(),
                author: "Test Author".to_string(),
                min_vanguard_version: Some("0.1.0".to_string()),
                max_vanguard_version: Some("2.0.0".to_string()),
                dependencies: vec![PluginDependency {
                    name: "core".to_string(),
                    version: ">=1.0.0".to_string(),
                }],
            },
        }
    }

    #[test]
    fn test_plugin_metadata() {
        let plugin = create_test_plugin();
        assert_eq!(plugin.metadata().name, "test-plugin");
        assert_eq!(plugin.metadata().version, "1.0.0");
    }

    #[test]
    fn test_plugin_compatibility() {
        let plugin = create_test_plugin();

        // Test version compatibility
        assert!(plugin.is_compatible_with(&Version::new(0, 1, 0))); // Min version
        assert!(plugin.is_compatible_with(&Version::new(1, 0, 0))); // Compatible version
        assert!(!plugin.is_compatible_with(&Version::new(2, 0, 0))); // Max version (exclusive)
        assert!(!plugin.is_compatible_with(&Version::new(0, 0, 9))); // Too old
    }

    #[tokio::test]
    async fn test_plugin_lifecycle() {
        let plugin = create_test_plugin();

        // Test initialization
        assert!(plugin.initialize().await.is_ok());

        // Test validation
        assert!(matches!(plugin.validate().await, ValidationResult::Passed));

        // Test cleanup
        assert!(plugin.cleanup().await.is_ok());
    }
}