use super::{PythonValidator, RezValidator, ValidationIssue, ValidationResult, Validator};
use crate::core::Result;
use std::sync::Arc;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct ValidationConfig {
pub enable_python_validation: bool,
pub enable_rez_validation: bool,
pub max_issues_per_file: usize,
pub include_style_warnings: bool,
pub include_info_messages: bool,
}
impl Default for ValidationConfig {
fn default() -> Self {
Self {
enable_python_validation: true,
enable_rez_validation: true,
max_issues_per_file: 100,
include_style_warnings: true,
include_info_messages: false,
}
}
}
pub struct ValidationEngine {
config: ValidationConfig,
python_validator: Option<Arc<PythonValidator>>,
rez_validator: Option<Arc<RezValidator>>,
}
impl ValidationEngine {
pub fn new() -> Result<Self> {
Self::with_config(ValidationConfig::default())
}
pub fn with_config(config: ValidationConfig) -> Result<Self> {
let python_validator = if config.enable_python_validation {
Some(Arc::new(PythonValidator::new()?))
} else {
None
};
let rez_validator = if config.enable_rez_validation {
Some(Arc::new(RezValidator::new()?))
} else {
None
};
Ok(Self {
config,
python_validator,
rez_validator,
})
}
pub fn validate_file(&self, content: &str, file_path: &str) -> Result<ValidationResult> {
let start_time = Instant::now();
let mut all_issues = Vec::new();
if let Some(validator) = &self.python_validator {
match validator.validate(content, file_path) {
Ok(mut issues) => {
all_issues.append(&mut issues);
}
Err(e) => {
eprintln!("Python validation failed: {}", e);
}
}
}
if let Some(validator) = &self.rez_validator {
match validator.validate(content, file_path) {
Ok(mut issues) => {
all_issues.append(&mut issues);
}
Err(e) => {
eprintln!("Rez validation failed: {}", e);
}
}
}
all_issues = self.filter_issues(all_issues);
all_issues.sort_by(|a, b| {
b.severity
.cmp(&a.severity)
.then_with(|| a.line.cmp(&b.line))
.then_with(|| a.column.cmp(&b.column))
});
if all_issues.len() > self.config.max_issues_per_file {
all_issues.truncate(self.config.max_issues_per_file);
all_issues.push(
ValidationIssue::new(
super::Severity::Warning,
1,
1,
1,
format!(
"Too many issues found. Showing first {} issues.",
self.config.max_issues_per_file
),
"V001",
)
.with_suggestion("Fix the most critical issues first"),
);
}
let validation_time = start_time.elapsed().as_millis() as u64;
Ok(ValidationResult::new(
file_path,
all_issues,
validation_time,
))
}
pub fn validate_files(&self, files: &[(String, String)]) -> Result<Vec<ValidationResult>> {
let mut results = Vec::new();
for (file_path, content) in files {
match self.validate_file(content, file_path) {
Ok(result) => results.push(result),
Err(e) => {
eprintln!("Failed to validate {}: {}", file_path, e);
let error_issue = ValidationIssue::new(
super::Severity::Critical,
1,
1,
1,
format!("Validation failed: {}", e),
"V999",
);
results.push(ValidationResult::new(file_path, vec![error_issue], 0));
}
}
}
Ok(results)
}
fn filter_issues(&self, issues: Vec<ValidationIssue>) -> Vec<ValidationIssue> {
issues
.into_iter()
.filter(|issue| match issue.severity {
super::Severity::Info => self.config.include_info_messages,
super::Severity::Warning => self.config.include_style_warnings,
super::Severity::Error | super::Severity::Critical => true,
})
.collect()
}
pub fn get_summary_stats(&self, results: &[ValidationResult]) -> ValidationSummary {
let total_files = results.len();
let mut files_with_errors = 0;
let mut files_with_warnings = 0;
let mut total_issues = 0;
let mut total_critical = 0;
let mut total_errors = 0;
let mut total_warnings = 0;
let mut total_info = 0;
let mut total_validation_time = 0;
for result in results {
total_issues += result.stats.total_issues;
total_critical += result.stats.critical_count;
total_errors += result.stats.error_count;
total_warnings += result.stats.warning_count;
total_info += result.stats.info_count;
total_validation_time += result.stats.validation_time_ms;
if result.has_errors() {
files_with_errors += 1;
} else if result.stats.warning_count > 0 {
files_with_warnings += 1;
}
}
ValidationSummary {
total_files,
files_with_errors,
files_with_warnings,
total_issues,
total_critical,
total_errors,
total_warnings,
total_info,
total_validation_time_ms: total_validation_time,
}
}
pub fn update_config(&mut self, config: ValidationConfig) -> Result<()> {
if config.enable_python_validation && self.python_validator.is_none() {
self.python_validator = Some(Arc::new(PythonValidator::new()?));
} else if !config.enable_python_validation {
self.python_validator = None;
}
if config.enable_rez_validation && self.rez_validator.is_none() {
self.rez_validator = Some(Arc::new(RezValidator::new()?));
} else if !config.enable_rez_validation {
self.rez_validator = None;
}
self.config = config;
Ok(())
}
pub fn config(&self) -> &ValidationConfig {
&self.config
}
}
#[derive(Debug, Clone)]
pub struct ValidationSummary {
pub total_files: usize,
pub files_with_errors: usize,
pub files_with_warnings: usize,
pub total_issues: usize,
pub total_critical: usize,
pub total_errors: usize,
pub total_warnings: usize,
pub total_info: usize,
pub total_validation_time_ms: u64,
}
impl ValidationSummary {
pub fn has_errors(&self) -> bool {
self.total_critical > 0 || self.total_errors > 0
}
pub fn has_issues(&self) -> bool {
self.total_issues > 0
}
pub fn average_validation_time_ms(&self) -> f64 {
if self.total_files > 0 {
self.total_validation_time_ms as f64 / self.total_files as f64
} else {
0.0
}
}
}
impl Default for ValidationEngine {
fn default() -> Self {
Self::new().expect("Failed to create ValidationEngine")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_engine_creation() {
let engine = ValidationEngine::new();
assert!(engine.is_ok());
}
#[test]
fn test_validation_engine_with_config() {
let config = ValidationConfig {
enable_python_validation: false,
enable_rez_validation: true,
..Default::default()
};
let engine = ValidationEngine::with_config(config);
assert!(engine.is_ok());
let engine = engine.unwrap();
assert!(engine.python_validator.is_none());
assert!(engine.rez_validator.is_some());
}
#[test]
fn test_file_validation() {
let engine = ValidationEngine::new().unwrap();
let content = r#"
name = "test"
version = "1.0.0"
description = "Test package"
"#;
let result = engine.validate_file(content, "test.py");
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.file_path, "test.py");
assert!(result.stats.validation_time_ms > 0);
}
#[test]
fn test_multiple_file_validation() {
let engine = ValidationEngine::new().unwrap();
let files = vec![
(
"test1.py".to_string(),
"name = \"test1\"\nversion = \"1.0.0\"".to_string(),
),
(
"test2.py".to_string(),
"name = \"test2\"\nversion = \"2.0.0\"".to_string(),
),
];
let results = engine.validate_files(&files);
assert!(results.is_ok());
let results = results.unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_validation_summary() {
let engine = ValidationEngine::new().unwrap();
let files = vec![
(
"test1.py".to_string(),
"name = \"test1\"\nversion = \"1.0.0\"".to_string(),
),
("test2.py".to_string(), "invalid syntax here".to_string()),
];
let results = engine.validate_files(&files).unwrap();
let summary = engine.get_summary_stats(&results);
assert_eq!(summary.total_files, 2);
assert!(summary.total_validation_time_ms > 0);
}
}