use super::{PartialValidationResult, Priority, ValidationResult, ValidationSummary, Validator};
use dashmap::DashMap;
use std::time::Instant;
pub struct ValidationEngine {
validators: Vec<Box<dyn Validator>>,
cache: Option<DashMap<String, Vec<ValidationResult>>>,
cache_enabled: bool,
}
impl ValidationEngine {
pub fn new() -> Self {
Self {
validators: Vec::new(),
cache: Some(DashMap::new()),
cache_enabled: true,
}
}
pub fn without_cache() -> Self {
Self {
validators: Vec::new(),
cache: None,
cache_enabled: false,
}
}
pub fn add_validator(&mut self, validator: Box<dyn Validator>) {
self.validators.push(validator);
if let Some(cache) = &self.cache {
cache.clear();
}
}
pub fn validate(&self, input: &str) -> ValidationSummary {
let start_time = Instant::now();
if let Some(cached) = self.get_cached_results(input) {
let mut summary = ValidationSummary::new(input.to_string(), cached);
summary.metadata.validation_time_ms = start_time.elapsed().as_millis() as u64;
return summary;
}
let results: Vec<ValidationResult> = self
.validators
.iter()
.map(|validator| validator.validate(input))
.collect();
self.cache_results(input, &results);
let mut summary = ValidationSummary::new(input.to_string(), results);
summary.metadata.validation_time_ms = start_time.elapsed().as_millis() as u64;
summary
}
pub fn partial_validate(&self, input: &str, cursor_pos: usize) -> PartialValidationResult {
let mut first_error_pos: Option<usize> = None;
let mut can_continue = true;
let mut suggestions = Vec::new();
for validator in &self.validators {
let result = validator.partial_validate(input, cursor_pos);
if let Some(pos) = result.first_error_pos {
first_error_pos = Some(match first_error_pos {
Some(existing) => existing.min(pos),
None => pos,
});
}
if !result.can_continue {
can_continue = false;
}
if let Some(suggestion) = result.suggestion {
suggestions.push(suggestion);
}
}
PartialValidationResult {
first_error_pos,
can_continue,
suggestion: if suggestions.is_empty() {
None
} else {
Some(suggestions.join("; "))
},
}
}
pub fn get_display_errors(
&self,
input: &str,
max_errors: Option<usize>,
) -> Vec<ValidationResult> {
let summary = self.validate(input);
self.filter_display_errors(summary.validation_results, max_errors)
}
fn filter_display_errors(
&self,
mut results: Vec<ValidationResult>,
max_errors: Option<usize>,
) -> Vec<ValidationResult> {
results.sort_by(|a, b| {
a.priority
.cmp(&b.priority)
.then_with(|| a.rule_name.cmp(&b.rule_name))
});
let failed: Vec<_> = results.into_iter().filter(|r| !r.passed).collect();
let mut display_errors = Vec::new();
let mut critical_high_count = 0;
let mut medium_count = 0;
let mut low_count = 0;
for error in failed {
let should_include = match error.priority {
Priority::Critical | Priority::High => {
critical_high_count += 1;
true }
Priority::Medium => {
medium_count += 1;
medium_count <= 3 }
Priority::Low => {
low_count += 1;
critical_high_count == 0 && low_count <= 2
}
};
if should_include {
display_errors.push(error);
if let Some(max) = max_errors {
if display_errors.len() >= max {
break;
}
}
}
}
display_errors
}
fn get_cached_results(&self, input: &str) -> Option<Vec<ValidationResult>> {
if !self.cache_enabled {
return None;
}
self.cache.as_ref()?.get(input).map(|entry| entry.clone())
}
fn cache_results(&self, input: &str, results: &[ValidationResult]) {
if let Some(cache) = &self.cache {
cache.insert(input.to_string(), results.to_vec());
}
}
pub fn clear_cache(&self) {
if let Some(cache) = &self.cache {
cache.clear();
}
}
pub fn validator_count(&self) -> usize {
self.validators.len()
}
pub fn get_potential_error_messages(&self) -> Vec<String> {
let mut messages = Vec::new();
for validator in &self.validators {
let test_inputs = vec![
"", "x", "this is a very long input string that will likely fail most validators and show their error messages",
"invalid-format-123!@#", ];
for test_input in test_inputs {
let result = validator.validate(test_input);
if !result.passed {
if let Some(message) = &result.message {
if !messages.contains(message) {
messages.push(message.clone());
}
}
}
}
}
if !messages.is_empty() {
messages.push("Additional validation context may appear here".to_string());
}
messages
}
}
impl Default for ValidationEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::validation::rules::basic::{
MaxLengthValidator, MinLengthValidator, RequiredValidator,
};
#[test]
fn test_empty_validation_engine() {
let engine = ValidationEngine::new();
let summary = engine.validate("test");
assert!(summary.valid);
assert!(summary.validation_results.is_empty());
assert!(summary.error.is_none());
}
#[test]
fn test_single_validator() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
let summary = engine.validate("hello");
assert!(summary.valid);
assert_eq!(summary.validation_results.len(), 1);
assert!(summary.validation_results[0].passed);
let summary = engine.validate("");
assert!(!summary.valid);
assert_eq!(summary.validation_results.len(), 1);
assert!(!summary.validation_results[0].passed);
assert!(summary.error.is_some());
}
#[test]
fn test_multiple_validators() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
engine.add_validator(Box::new(MinLengthValidator::new(5)));
engine.add_validator(Box::new(MaxLengthValidator::new(10)));
let summary = engine.validate("hello");
assert!(summary.valid);
assert_eq!(summary.validation_results.len(), 3);
assert!(summary.validation_results.iter().all(|r| r.passed));
let summary = engine.validate("hi");
assert!(!summary.valid);
assert_eq!(summary.validation_results.len(), 3);
assert!(summary.validation_results[0].passed); assert!(summary.validation_results[1].passed); assert!(!summary.validation_results[2].passed); }
#[test]
fn test_priority_sorting() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(MinLengthValidator::new(10))); engine.add_validator(Box::new(RequiredValidator::new()));
let summary = engine.validate("");
assert!(!summary.valid);
assert_eq!(summary.validation_results[0].rule_name, "required");
assert_eq!(summary.validation_results[1].rule_name, "min_length");
}
#[test]
fn test_error_display_filtering() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
engine.add_validator(Box::new(MinLengthValidator::new(5)));
let errors = engine.get_display_errors("", Some(1));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].rule_name, "required");
assert_eq!(errors[0].priority, Priority::Critical);
}
#[test]
fn test_partial_validation() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(MaxLengthValidator::new(5)));
let result = engine.partial_validate("hello", 5);
assert!(result.first_error_pos.is_none());
assert!(result.can_continue);
let result = engine.partial_validate("hello world", 8);
assert!(result.first_error_pos.is_some());
assert_eq!(result.first_error_pos.unwrap(), 5);
}
#[test]
fn test_validation_performance_timing() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
let summary = engine.validate("test");
assert!(summary.metadata.validation_time_ms < 1000); }
#[test]
fn test_get_potential_error_messages() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
engine.add_validator(Box::new(MinLengthValidator::new(5)));
let messages = engine.get_potential_error_messages();
assert!(!messages.is_empty());
assert!(messages.iter().any(|msg| msg.contains("required")));
assert!(messages.iter().any(|msg| msg.contains("Minimum length")));
}
#[test]
fn test_validation_engine_without_cache() {
let engine = ValidationEngine::without_cache();
let summary = engine.validate("test");
assert!(summary.valid);
}
#[test]
fn test_validation_caching() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
let summary1 = engine.validate("test");
let time1 = summary1.metadata.validation_time_ms;
let summary2 = engine.validate("test");
let time2 = summary2.metadata.validation_time_ms;
assert_eq!(summary1.valid, summary2.valid);
assert_eq!(
summary1.validation_results.len(),
summary2.validation_results.len()
);
assert!(time2 <= time1 + 1); }
#[test]
fn test_cache_invalidation() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
let _summary1 = engine.validate("test");
engine.add_validator(Box::new(MinLengthValidator::new(5)));
let summary2 = engine.validate("testing");
assert!(summary2.valid);
assert_eq!(summary2.validation_results.len(), 2);
}
#[test]
fn test_clear_cache() {
let mut engine = ValidationEngine::new();
engine.add_validator(Box::new(RequiredValidator::new()));
let _summary = engine.validate("test");
engine.clear_cache();
let summary = engine.validate("test");
assert!(summary.valid);
}
#[test]
fn test_validator_count() {
let mut engine = ValidationEngine::new();
assert_eq!(engine.validator_count(), 0);
engine.add_validator(Box::new(RequiredValidator::new()));
assert_eq!(engine.validator_count(), 1);
engine.add_validator(Box::new(MinLengthValidator::new(5)));
assert_eq!(engine.validator_count(), 2);
}
}