1use std::collections::HashMap;
7use std::time::Duration;
8
9use super::config::{ErrorSeverity, ValidationErrorType};
10use crate::error::{CoreError, ErrorContext, ErrorLocation};
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ValidationError {
17 pub errortype: ValidationErrorType,
19 pub fieldpath: String,
21 pub message: String,
23 pub expected: Option<String>,
25 pub actual: Option<String>,
27 pub constraint: Option<String>,
29 pub severity: ErrorSeverity,
31 pub context: HashMap<String, String>,
33}
34
35impl ValidationError {
36 pub fn new(errortype: ValidationErrorType, fieldpath: &str, message: &str) -> Self {
38 Self {
39 errortype,
40 fieldpath: fieldpath.to_string(),
41 message: message.to_string(),
42 expected: None,
43 actual: None,
44 constraint: None,
45 severity: ErrorSeverity::Error,
46 context: HashMap::new(),
47 }
48 }
49
50 pub fn with_expected(mut self, expected: &str) -> Self {
52 self.expected = Some(expected.to_string());
53 self
54 }
55
56 pub fn with_actual(mut self, actual: &str) -> Self {
58 self.actual = Some(actual.to_string());
59 self
60 }
61
62 pub fn with_constraint(mut self, constraint: &str) -> Self {
64 self.constraint = Some(constraint.to_string());
65 self
66 }
67
68 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
70 self.severity = severity;
71 self
72 }
73
74 pub fn with_context(mut self, key: &str, value: &str) -> Self {
76 self.context.insert(key.to_string(), value.to_string());
77 self
78 }
79
80 pub fn formatted_message(&self) -> String {
82 let mut message = format!("{}, {}", self.fieldpath, self.message);
83
84 if let Some(expected) = &self.expected {
85 message.push_str(&format!(" (expected: {expected})"));
86 }
87
88 if let Some(actual) = &self.actual {
89 message.push_str(&format!(" (actual: {actual})"));
90 }
91
92 if let Some(constraint) = &self.constraint {
93 message.push_str(&format!(" (constraint: {constraint})"));
94 }
95
96 message
97 }
98}
99
100impl From<ValidationError> for CoreError {
102 fn from(err: ValidationError) -> Self {
103 match err.errortype {
105 ValidationErrorType::MissingRequiredField => CoreError::ValidationError(
106 ErrorContext::new(err.formatted_message())
107 .with_location(ErrorLocation::new(file!(), line!())),
108 ),
109 ValidationErrorType::TypeMismatch => CoreError::TypeError(
110 ErrorContext::new(err.formatted_message())
111 .with_location(ErrorLocation::new(file!(), line!())),
112 ),
113 ValidationErrorType::ConstraintViolation => CoreError::ValueError(
114 ErrorContext::new(err.formatted_message())
115 .with_location(ErrorLocation::new(file!(), line!())),
116 ),
117 ValidationErrorType::OutOfRange => CoreError::DomainError(
118 ErrorContext::new(err.formatted_message())
119 .with_location(ErrorLocation::new(file!(), line!())),
120 ),
121 ValidationErrorType::InvalidFormat => CoreError::ValidationError(
122 ErrorContext::new(err.formatted_message())
123 .with_location(ErrorLocation::new(file!(), line!())),
124 ),
125 ValidationErrorType::InvalidArraySize => CoreError::ValidationError(
126 ErrorContext::new(err.formatted_message())
127 .with_location(ErrorLocation::new(file!(), line!())),
128 ),
129 ValidationErrorType::DuplicateValues => CoreError::ValidationError(
130 ErrorContext::new(err.formatted_message())
131 .with_location(ErrorLocation::new(file!(), line!())),
132 ),
133 ValidationErrorType::IntegrityFailure => CoreError::ValidationError(
134 ErrorContext::new(err.formatted_message())
135 .with_location(ErrorLocation::new(file!(), line!())),
136 ),
137 ValidationErrorType::CustomRuleFailure => CoreError::ValidationError(
138 ErrorContext::new(err.formatted_message())
139 .with_location(ErrorLocation::new(file!(), line!())),
140 ),
141 ValidationErrorType::SchemaError => CoreError::ValidationError(
142 ErrorContext::new(err.formatted_message())
143 .with_location(ErrorLocation::new(file!(), line!())),
144 ),
145 ValidationErrorType::ShapeError => CoreError::ShapeError(
146 ErrorContext::new(err.formatted_message())
147 .with_location(ErrorLocation::new(file!(), line!())),
148 ),
149 ValidationErrorType::InvalidNumeric => CoreError::ValueError(
150 ErrorContext::new(err.formatted_message())
151 .with_location(ErrorLocation::new(file!(), line!())),
152 ),
153 ValidationErrorType::StatisticalViolation => CoreError::ValidationError(
154 ErrorContext::new(err.formatted_message())
155 .with_location(ErrorLocation::new(file!(), line!())),
156 ),
157 ValidationErrorType::Performance => CoreError::ComputationError(
158 ErrorContext::new(err.formatted_message())
159 .with_location(ErrorLocation::new(file!(), line!())),
160 ),
161 ValidationErrorType::IntegrityError => CoreError::ValidationError(
162 ErrorContext::new(err.formatted_message())
163 .with_location(ErrorLocation::new(file!(), line!())),
164 ),
165 ValidationErrorType::TypeConversion => CoreError::TypeError(
166 ErrorContext::new(err.formatted_message())
167 .with_location(ErrorLocation::new(file!(), line!())),
168 ),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175pub struct ValidationStats {
176 pub fields_validated: usize,
178 pub constraints_checked: usize,
180 pub elements_processed: usize,
182 pub cache_hit_rate: f64,
184 pub memory_used: Option<usize>,
186}
187
188impl ValidationStats {
189 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn add_field_validation(&mut self) {
196 self.fields_validated += 1;
197 }
198
199 pub fn add_constraint_check(&mut self) {
201 self.constraints_checked += 1;
202 }
203
204 pub fn add_constraint_checks(&mut self, count: usize) {
206 self.constraints_checked += count;
207 }
208
209 pub fn add_elements_processed(&mut self, count: usize) {
211 self.elements_processed += count;
212 }
213
214 pub fn set_cache_hit_rate(&mut self, rate: f64) {
216 self.cache_hit_rate = rate;
217 }
218
219 pub fn set_memory_used(&mut self, bytes: usize) {
221 self.memory_used = Some(bytes);
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ValidationResult {
228 pub valid: bool,
230 pub errors: Vec<ValidationError>,
232 pub warnings: Vec<ValidationError>,
234 pub stats: ValidationStats,
236 pub duration: Duration,
238}
239
240impl ValidationResult {
241 pub fn new() -> Self {
243 Self {
244 valid: true,
245 errors: Vec::new(),
246 warnings: Vec::new(),
247 stats: ValidationStats::new(),
248 duration: Duration::from_secs(0),
249 }
250 }
251
252 pub fn is_valid(&self) -> bool {
254 self.valid && self.errors.is_empty()
255 }
256
257 pub fn has_warnings(&self) -> bool {
259 !self.warnings.is_empty()
260 }
261
262 pub fn errors(&self) -> &[ValidationError] {
264 &self.errors
265 }
266
267 pub fn warnings(&self) -> &[ValidationError] {
269 &self.warnings
270 }
271
272 pub fn adderror(&mut self, error: ValidationError) {
274 self.valid = false;
275 self.errors.push(error);
276 }
277
278 pub fn adderrors(&mut self, mut errors: Vec<ValidationError>) {
280 if !errors.is_empty() {
281 self.valid = false;
282 self.errors.append(&mut errors);
283 }
284 }
285
286 pub fn add_warning(&mut self, warning: ValidationError) {
288 self.warnings.push(warning);
289 }
290
291 pub fn add_warnings(&mut self, mut warnings: Vec<ValidationError>) {
293 self.warnings.append(&mut warnings);
294 }
295
296 pub fn set_duration(&mut self, duration: Duration) {
298 self.duration = duration;
299 }
300
301 pub fn error_count_by_severity(&self, severity: ErrorSeverity) -> usize {
303 self.errors
304 .iter()
305 .filter(|e| e.severity == severity)
306 .count()
307 }
308
309 pub fn warning_count_by_severity(&self, severity: ErrorSeverity) -> usize {
311 self.warnings
312 .iter()
313 .filter(|w| w.severity == severity)
314 .count()
315 }
316
317 pub fn errors_for_field(&self, fieldpath: &str) -> Vec<&ValidationError> {
319 self.errors
320 .iter()
321 .filter(|e| e.fieldpath == fieldpath)
322 .collect()
323 }
324
325 pub fn warnings_for_field(&self, fieldpath: &str) -> Vec<&ValidationError> {
327 self.warnings
328 .iter()
329 .filter(|w| w.fieldpath == fieldpath)
330 .collect()
331 }
332
333 pub fn summary(&self) -> String {
335 if self.is_valid() && !self.has_warnings() {
336 "Validation passed successfully".to_string()
337 } else if self.is_valid() && self.has_warnings() {
338 format!("Validation passed with {} warning(s)", self.warnings.len())
339 } else {
340 format!(
341 "Validation failed with {} error(s) and {} warning(s)",
342 self.errors.len(),
343 self.warnings.len()
344 )
345 }
346 }
347
348 pub fn detailed_report(&self) -> String {
350 let mut report = self.summary();
351
352 if !self.errors.is_empty() {
353 report.push_str("\n\nErrors:");
354 for (i, error) in self.errors.iter().enumerate() {
355 report.push_str(&format!("{}. {}", i + 1, error.formatted_message()));
356 }
357 }
358
359 if !self.warnings.is_empty() {
360 report.push_str("\n\nWarnings:");
361 for (i, warning) in self.warnings.iter().enumerate() {
362 report.push_str(&format!("{}. {}", i + 1, warning.formatted_message()));
363 }
364 }
365
366 report.push_str("\n\nStatistics:");
367 report.push_str(&format!(
368 "\n Fields validated: {}",
369 self.stats.fields_validated
370 ));
371 report.push_str(&format!(
372 "\n Constraints checked: {}",
373 self.stats.constraints_checked
374 ));
375 report.push_str(&format!(
376 "\n Elements processed: {}",
377 self.stats.elements_processed
378 ));
379 report.push_str(&format!("\n Duration: {:?}", self.duration));
380
381 if self.stats.cache_hit_rate > 0.0 {
382 report.push_str(&format!(
383 "\n Cache hit rate: {:.2}%",
384 self.stats.cache_hit_rate * 100.0
385 ));
386 }
387
388 if let Some(memory) = self.stats.memory_used {
389 report.push_str(&format!("\n Memory used: {} bytes", memory));
390 }
391
392 report
393 }
394}
395
396impl Default for ValidationResult {
397 fn default() -> Self {
398 Self::new()
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn test_validationerror() {
408 let error = ValidationError::new(
409 ValidationErrorType::TypeMismatch,
410 "test_field",
411 "Type mismatch error",
412 )
413 .with_expected("string")
414 .with_actual("integer")
415 .with_constraint("type_check")
416 .with_severity(ErrorSeverity::Error)
417 .with_context("line", "42");
418
419 assert_eq!(error.errortype, ValidationErrorType::TypeMismatch);
420 assert_eq!(error.fieldpath, "test_field");
421 assert_eq!(error.message, "Type mismatch error");
422 assert_eq!(error.expected, Some("string".to_string()));
423 assert_eq!(error.actual, Some("integer".to_string()));
424 assert_eq!(error.constraint, Some("type_check".to_string()));
425 assert_eq!(error.severity, ErrorSeverity::Error);
426 assert_eq!(error.context.get("line"), Some(&"42".to_string()));
427
428 let formatted = error.formatted_message();
429 assert!(formatted.contains("test_field"));
430 assert!(formatted.contains("Type mismatch error"));
431 assert!(formatted.contains("expected: string"));
432 assert!(formatted.contains("actual: integer"));
433 }
434
435 #[test]
436 fn test_validation_stats() {
437 let mut stats = ValidationStats::new();
438
439 stats.add_field_validation();
440 stats.add_constraint_checks(5);
441 stats.add_elements_processed(100);
442 stats.set_cache_hit_rate(0.85);
443 stats.set_memory_used(1024);
444
445 assert_eq!(stats.fields_validated, 1);
446 assert_eq!(stats.constraints_checked, 5);
447 assert_eq!(stats.elements_processed, 100);
448 assert_eq!(stats.cache_hit_rate, 0.85);
449 assert_eq!(stats.memory_used, Some(1024));
450 }
451
452 #[test]
453 fn test_validation_result() {
454 let mut result = ValidationResult::new();
455
456 assert!(result.is_valid());
458 assert!(!result.has_warnings());
459 assert_eq!(result.errors().len(), 0);
460 assert_eq!(result.warnings().len(), 0);
461
462 let error =
464 ValidationError::new(ValidationErrorType::TypeMismatch, "field1", "Error message");
465 result.adderror(error);
466
467 assert!(!result.is_valid());
468 assert_eq!(result.errors().len(), 1);
469
470 let warning = ValidationError::new(
472 ValidationErrorType::Performance,
473 "field2",
474 "Warning message",
475 )
476 .with_severity(ErrorSeverity::Warning);
477 result.add_warning(warning);
478
479 assert!(result.has_warnings());
480 assert_eq!(result.warnings().len(), 1);
481
482 let field1errors = result.errors_for_field("field1");
484 assert_eq!(field1errors.len(), 1);
485
486 let field2_warnings = result.warnings_for_field("field2");
487 assert_eq!(field2_warnings.len(), 1);
488
489 let summary = result.summary();
491 assert!(summary.contains("failed"));
492 assert!(summary.contains("1 error"));
493 assert!(summary.contains("1 warning"));
494
495 let report = result.detailed_report();
497 assert!(report.contains("Errors:"));
498 assert!(report.contains("Warnings:"));
499 assert!(report.contains("Statistics:"));
500 }
501
502 #[test]
503 fn testerror_severity_counting() {
504 let mut result = ValidationResult::new();
505
506 let criticalerror = ValidationError::new(
507 ValidationErrorType::IntegrityFailure,
508 "field1",
509 "Critical error",
510 )
511 .with_severity(ErrorSeverity::Critical);
512
513 let warning = ValidationError::new(ValidationErrorType::Performance, "field2", "Warning")
514 .with_severity(ErrorSeverity::Warning);
515
516 result.adderror(criticalerror);
517 result.add_warning(warning);
518
519 assert_eq!(result.error_count_by_severity(ErrorSeverity::Critical), 1);
520 assert_eq!(result.error_count_by_severity(ErrorSeverity::Error), 0);
521 assert_eq!(result.warning_count_by_severity(ErrorSeverity::Warning), 1);
522 }
523
524 #[test]
525 fn test_successful_validation_result() {
526 let result = ValidationResult::new();
527
528 assert!(result.is_valid());
529 assert!(!result.has_warnings());
530
531 let summary = result.summary();
532 assert_eq!(summary, "Validation passed successfully");
533 }
534}