use crate::{PluginCapabilities, PluginError, PluginId, PluginVersion, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use super::schema::ConfigSchema;
use semver;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest {
pub manifest_version: String,
pub plugin: PluginInfo,
pub capabilities: PluginCapabilities,
pub dependencies: Vec<PluginDependency>,
pub config_schema: Option<ConfigSchema>,
pub metadata: HashMap<String, serde_json::Value>,
}
impl PluginManifest {
pub fn new(plugin: PluginInfo) -> Self {
Self {
manifest_version: "1.0".to_string(),
plugin,
capabilities: PluginCapabilities::default(),
dependencies: Vec::new(),
config_schema: None,
metadata: HashMap::new(),
}
}
pub fn validate(&self) -> Result<()> {
if self.manifest_version != "1.0" {
return Err(PluginError::config_error(&format!(
"Unsupported manifest version: {}",
self.manifest_version
)));
}
self.plugin.validate()?;
for dep in &self.dependencies {
dep.validate()?;
}
if let Some(schema) = &self.config_schema {
schema.validate()?;
}
Ok(())
}
pub fn id(&self) -> &PluginId {
&self.plugin.id
}
pub fn version(&self) -> &PluginVersion {
&self.plugin.version
}
pub fn supports_type(&self, plugin_type: &str) -> bool {
self.plugin.types.contains(&plugin_type.to_string())
}
pub fn display_name(&self) -> &str {
&self.plugin.name
}
pub fn description(&self) -> Option<&str> {
self.plugin.description.as_deref()
}
pub fn author(&self) -> Option<&PluginAuthor> {
self.plugin.author.as_ref()
}
pub fn has_capability(&self, capability: &str) -> bool {
self.capabilities.has_capability(capability)
}
pub fn types(&self) -> &[String] {
&self.plugin.types
}
pub fn dependencies(&self) -> &[PluginDependency] {
&self.dependencies
}
pub fn requires_config(&self) -> bool {
self.config_schema.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo {
pub id: PluginId,
pub name: String,
pub version: PluginVersion,
pub description: Option<String>,
pub author: Option<PluginAuthor>,
pub types: Vec<String>,
pub homepage: Option<String>,
pub repository: Option<String>,
pub license: Option<String>,
pub keywords: Vec<String>,
}
impl PluginInfo {
pub fn new(id: PluginId, name: String, version: PluginVersion) -> Self {
Self {
id,
name,
version,
description: None,
author: None,
types: Vec::new(),
homepage: None,
repository: None,
license: None,
keywords: Vec::new(),
}
}
pub fn validate(&self) -> Result<()> {
if self.name.trim().is_empty() {
return Err(PluginError::config_error("Plugin name cannot be empty"));
}
if self.types.is_empty() {
return Err(PluginError::config_error("Plugin must specify at least one type"));
}
for plugin_type in &self.types {
if plugin_type.trim().is_empty() {
return Err(PluginError::config_error("Plugin type cannot be empty"));
}
}
Ok(())
}
pub fn matches_keywords(&self, keywords: &[String]) -> bool {
if keywords.is_empty() {
return true;
}
keywords.iter().any(|keyword| {
self.keywords.iter().any(|plugin_keyword| {
plugin_keyword.to_lowercase().contains(&keyword.to_lowercase())
})
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginAuthor {
pub name: String,
pub email: Option<String>,
pub url: Option<String>,
}
impl PluginAuthor {
pub fn new(name: String) -> Self {
Self {
name,
email: None,
url: None,
}
}
pub fn validate(&self) -> Result<()> {
if self.name.trim().is_empty() {
return Err(PluginError::config_error("Author name cannot be empty"));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginDependency {
pub id: PluginId,
pub version: String,
pub optional: bool,
}
impl PluginDependency {
pub fn new(id: PluginId, version: String) -> Self {
Self {
id,
version,
optional: false,
}
}
pub fn optional(id: PluginId, version: String) -> Self {
Self {
id,
version,
optional: true,
}
}
pub fn validate(&self) -> Result<()> {
if self.version.trim().is_empty() {
return Err(PluginError::config_error(&format!(
"Dependency {} version cannot be empty",
self.id
)));
}
Ok(())
}
pub fn satisfies_version(&self, version: &PluginVersion) -> bool {
if self.version == "*" {
return true;
}
let req_str = if self.version.starts_with(|c: char| c.is_ascii_digit()) {
format!("={}", self.version)
} else {
self.version.clone()
};
let req = match semver::VersionReq::parse(&req_str) {
Ok(req) => req,
Err(_) => return false, };
let semver_version = match version.to_semver() {
Ok(v) => v,
Err(_) => return false, };
req.matches(&semver_version)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_compiles() {
}
}