use std::collections::HashMap;
use tracing::{debug, warn};
use crate::module::registry::manifest::ModuleManifest;
use crate::module::traits::ModuleError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationResult {
Valid,
Invalid(Vec<String>),
}
pub struct ManifestValidator {
required_fields: Vec<&'static str>,
max_manifest_size: usize,
}
impl ManifestValidator {
pub fn new() -> Self {
Self {
required_fields: vec!["name", "version", "entry_point"],
max_manifest_size: 64 * 1024, }
}
pub fn validate(&self, manifest: &ModuleManifest) -> ValidationResult {
let mut errors = Vec::new();
if manifest.name.is_empty() {
errors.push("Module name cannot be empty".to_string());
}
if manifest.version.is_empty() {
errors.push("Module version cannot be empty".to_string());
} else if !self.is_valid_version(&manifest.version) {
errors.push(format!(
"Invalid version format: {} (expected semantic versioning)",
manifest.version
));
}
if manifest.entry_point.is_empty() {
errors.push("Entry point cannot be empty".to_string());
}
if !self.is_valid_name(&manifest.name) {
errors.push(format!(
"Invalid module name: {} (must be alphanumeric with dashes/underscores)",
manifest.name
));
}
if let Err(cap_errors) = self.validate_capabilities(&manifest.capabilities) {
errors.extend(cap_errors);
}
if let Err(dep_errors) = self.validate_dependencies(&manifest.dependencies) {
errors.extend(dep_errors);
}
if errors.is_empty() {
debug!("Manifest validation passed for module: {}", manifest.name);
ValidationResult::Valid
} else {
warn!(
"Manifest validation failed for module {}: {:?}",
manifest.name, errors
);
ValidationResult::Invalid(errors)
}
}
#[inline]
fn is_valid_name(&self, name: &str) -> bool {
if name.is_empty() || name.len() > 64 {
return false;
}
if !name.chars().next().is_some_and(|c| c.is_alphanumeric()) {
return false;
}
name.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
}
#[inline]
fn is_valid_version(&self, version: &str) -> bool {
if version.is_empty() {
return false;
}
let (base, _build) = if let Some(pos) = version.find('+') {
(&version[..pos], &version[pos + 1..])
} else {
(version, "")
};
let (version_part, _prerelease) = if let Some(pos) = base.find('-') {
(&base[..pos], &base[pos + 1..])
} else {
(base, "")
};
let nums: Vec<&str> = version_part.split('.').collect();
if nums.len() < 2 || nums.len() > 3 {
return false;
}
nums.iter().all(|n| {
!n.is_empty() && n.chars().all(|c| c.is_ascii_digit()) && n.parse::<u32>().is_ok()
})
}
fn validate_capabilities(&self, capabilities: &[String]) -> Result<(), Vec<String>> {
use crate::module::security::permissions::parse_permission_string;
let mut errors = Vec::new();
for cap in capabilities {
if parse_permission_string(cap).is_none() {
errors.push(format!("Unknown capability: {cap}"));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn validate_dependencies(
&self,
dependencies: &HashMap<String, String>,
) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for (dep_name, dep_version) in dependencies {
if !self.is_valid_name(dep_name) {
errors.push(format!("Invalid dependency name: {dep_name}"));
}
if !self.is_valid_version_or_range(dep_version) {
errors.push(format!(
"Invalid dependency version format: {dep_version} (for dependency: {dep_name})"
));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn is_valid_version_or_range(&self, version: &str) -> bool {
if version.starts_with(">=")
|| version.starts_with("<=")
|| version.starts_with("==")
|| version.starts_with("^")
|| version.starts_with("~")
{
let version_part = version.trim_start_matches(|c: char| {
c == '>' || c == '=' || c == '^' || c == '~' || c == '<'
});
return self.is_valid_version(version_part);
}
self.is_valid_version(version)
}
}
impl Default for ManifestValidator {
fn default() -> Self {
Self::new()
}
}
pub fn validate_module_signature(
_manifest: &ModuleManifest,
_binary_path: &std::path::Path,
) -> Result<(), ModuleError> {
debug!("Module signature validation skipped (not implemented)");
Ok(())
}