use crate::packs::metadata::load_pack_metadata;
use ggen_utils::error::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResult {
pub pack_id: String,
pub valid: bool,
pub score: f64,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub checks: Vec<ValidationCheck>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationCheck {
pub name: String,
pub passed: bool,
pub message: String,
}
pub fn validate_pack(pack_id: &str) -> Result<ValidationResult> {
let pack = load_pack_metadata(pack_id)?;
let mut errors = Vec::new();
let mut warnings = Vec::new();
let mut checks = Vec::new();
let has_name = !pack.name.is_empty();
checks.push(ValidationCheck {
name: "has_name".to_string(),
passed: has_name,
message: if has_name {
"Pack has a name".to_string()
} else {
errors.push("Pack name is empty".to_string());
"Pack name is empty".to_string()
},
});
let has_description = !pack.description.is_empty();
checks.push(ValidationCheck {
name: "has_description".to_string(),
passed: has_description,
message: if has_description {
"Pack has a description".to_string()
} else {
errors.push("Pack description is empty".to_string());
"Pack description is empty".to_string()
},
});
let has_packages = !pack.packages.is_empty();
checks.push(ValidationCheck {
name: "has_packages".to_string(),
passed: has_packages,
message: if has_packages {
format!("Pack has {} package(s)", pack.packages.len())
} else {
warnings.push("Pack has no packages".to_string());
"Pack has no packages".to_string()
},
});
let has_valid_version = is_valid_semver(&pack.version);
checks.push(ValidationCheck {
name: "valid_version".to_string(),
passed: has_valid_version,
message: if has_valid_version {
format!("Valid version: {}", pack.version)
} else {
warnings.push(format!("Invalid semver version: {}", pack.version));
format!("Invalid semver version: {}", pack.version)
},
});
let has_content = !pack.templates.is_empty() || !pack.packages.is_empty();
checks.push(ValidationCheck {
name: "has_content".to_string(),
passed: has_content,
message: if has_content {
format!(
"Pack has {} template(s) and {} package(s)",
pack.templates.len(),
pack.packages.len()
)
} else {
errors.push("Pack has no templates or packages".to_string());
"Pack has no templates or packages".to_string()
},
});
let passed_checks = checks.iter().filter(|c| c.passed).count();
let total_checks = checks.len();
let score = if total_checks > 0 {
(passed_checks as f64 / total_checks as f64) * 100.0
} else {
0.0
};
let valid = errors.is_empty();
Ok(ValidationResult {
pack_id: pack_id.to_string(),
valid,
score,
errors,
warnings,
checks,
})
}
fn is_valid_semver(version: &str) -> bool {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() != 3 {
return false;
}
parts.iter().all(|p| p.parse::<u32>().is_ok())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_valid_semver() {
assert!(is_valid_semver("1.0.0"));
assert!(is_valid_semver("2.5.10"));
assert!(!is_valid_semver("1.0"));
assert!(!is_valid_semver("1.0.0.0"));
assert!(!is_valid_semver("abc"));
}
#[test]
fn test_validate_pack_handles_missing_pack() {
let result = validate_pack("nonexistent-pack");
assert!(result.is_err());
}
}