#![deny(missing_docs)]
#![deny(clippy::all)]
use async_trait::async_trait;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use thiserror::Error;
mod config;
mod dylib;
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::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo {
pub name: String,
pub version: String,
pub description: String,
pub enabled: bool,
pub path: String,
pub install_date: String,
}
#[derive(Debug)]
pub enum ValidationResult {
Passed,
Failed(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Platform {
pub os: String,
pub arch: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginMetadata {
pub name: String,
pub version: String,
pub description: String,
pub author: String,
pub min_vanguard_version: Option<String>,
pub max_vanguard_version: Option<String>,
pub dependencies: Vec<PluginDependency>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginDependency {
pub name: String,
pub version: String,
}
#[derive(Error, Debug)]
pub enum PluginError {
#[error("Plugin validation failed: {0}")]
ValidationFailed(String),
#[error("Plugin initialization failed: {0}")]
InitializationFailed(String),
#[error("Plugin operation failed: {0}")]
OperationFailed(String),
#[error("Plugin is incompatible: {0}")]
Incompatible(String),
}
#[async_trait]
pub trait VanguardPlugin: Send + Sync + std::fmt::Debug {
fn metadata(&self) -> &PluginMetadata;
async fn validate(&self) -> ValidationResult;
async fn initialize(&self) -> Result<(), String>;
async fn cleanup(&self) -> Result<(), String>;
fn is_compatible_with(&self, vanguard_version: &Version) -> bool {
let metadata = self.metadata();
if let Some(min_version) = &metadata.min_vanguard_version {
if vanguard_version < &Version::parse(min_version).unwrap() {
return false;
}
}
if let Some(max_version) = &metadata.max_vanguard_version {
if vanguard_version >= &Version::parse(max_version).unwrap() {
return false;
}
}
true
}
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();
assert!(plugin.is_compatible_with(&Version::new(0, 1, 0))); assert!(plugin.is_compatible_with(&Version::new(1, 0, 0))); assert!(!plugin.is_compatible_with(&Version::new(2, 0, 0))); assert!(!plugin.is_compatible_with(&Version::new(0, 0, 9))); }
#[tokio::test]
async fn test_plugin_lifecycle() {
let plugin = create_test_plugin();
assert!(plugin.initialize().await.is_ok());
assert!(matches!(plugin.validate().await, ValidationResult::Passed));
assert!(plugin.cleanup().await.is_ok());
}
}