scirs2_core/validation/data/
errors.rs

1//! Error types and validation results
2//!
3//! This module provides error types, validation results, and related structures
4//! for reporting validation outcomes and issues.
5
6use 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/// Validation error information
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ValidationError {
17    /// Error type
18    pub errortype: ValidationErrorType,
19    /// Field path where error occurred
20    pub fieldpath: String,
21    /// Error message
22    pub message: String,
23    /// Expected value/type
24    pub expected: Option<String>,
25    /// Actual value found
26    pub actual: Option<String>,
27    /// Constraint that was violated
28    pub constraint: Option<String>,
29    /// Error severity
30    pub severity: ErrorSeverity,
31    /// Additional context
32    pub context: HashMap<String, String>,
33}
34
35impl ValidationError {
36    /// Create a new validation error
37    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    /// Set expected value
51    pub fn with_expected(mut self, expected: &str) -> Self {
52        self.expected = Some(expected.to_string());
53        self
54    }
55
56    /// Set actual value
57    pub fn with_actual(mut self, actual: &str) -> Self {
58        self.actual = Some(actual.to_string());
59        self
60    }
61
62    /// Set constraint
63    pub fn with_constraint(mut self, constraint: &str) -> Self {
64        self.constraint = Some(constraint.to_string());
65        self
66    }
67
68    /// Set severity
69    pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
70        self.severity = severity;
71        self
72    }
73
74    /// Add context information
75    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    /// Get formatted error message
81    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
100/// Convert ValidationError to CoreError
101impl From<ValidationError> for CoreError {
102    fn from(err: ValidationError) -> Self {
103        // Choose the appropriate CoreError variant based on ValidationErrorType
104        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/// Validation statistics
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175pub struct ValidationStats {
176    /// Number of fields validated
177    pub fields_validated: usize,
178    /// Number of constraints checked
179    pub constraints_checked: usize,
180    /// Number of elements processed (for arrays)
181    pub elements_processed: usize,
182    /// Cache hit rate (if caching enabled)
183    pub cache_hit_rate: f64,
184    /// Memory usage during validation
185    pub memory_used: Option<usize>,
186}
187
188impl ValidationStats {
189    /// Create new validation statistics
190    pub fn new() -> Self {
191        Self::default()
192    }
193
194    /// Add field validation
195    pub fn add_field_validation(&mut self) {
196        self.fields_validated += 1;
197    }
198
199    /// Add constraint check
200    pub fn add_constraint_check(&mut self) {
201        self.constraints_checked += 1;
202    }
203
204    /// Add multiple constraint checks
205    pub fn add_constraint_checks(&mut self, count: usize) {
206        self.constraints_checked += count;
207    }
208
209    /// Add element processing
210    pub fn add_elements_processed(&mut self, count: usize) {
211        self.elements_processed += count;
212    }
213
214    /// Set cache hit rate
215    pub fn set_cache_hit_rate(&mut self, rate: f64) {
216        self.cache_hit_rate = rate;
217    }
218
219    /// Set memory usage
220    pub fn set_memory_used(&mut self, bytes: usize) {
221        self.memory_used = Some(bytes);
222    }
223}
224
225/// Validation result
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ValidationResult {
228    /// Whether validation passed
229    pub valid: bool,
230    /// Validation errors
231    pub errors: Vec<ValidationError>,
232    /// Validation warnings
233    pub warnings: Vec<ValidationError>,
234    /// Validation statistics
235    pub stats: ValidationStats,
236    /// Processing time
237    pub duration: Duration,
238}
239
240impl ValidationResult {
241    /// Create a new validation result
242    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    /// Check if validation passed
253    pub fn is_valid(&self) -> bool {
254        self.valid && self.errors.is_empty()
255    }
256
257    /// Check if there are warnings
258    pub fn has_warnings(&self) -> bool {
259        !self.warnings.is_empty()
260    }
261
262    /// Get all errors
263    pub fn errors(&self) -> &[ValidationError] {
264        &self.errors
265    }
266
267    /// Get all warnings
268    pub fn warnings(&self) -> &[ValidationError] {
269        &self.warnings
270    }
271
272    /// Add an error
273    pub fn adderror(&mut self, error: ValidationError) {
274        self.valid = false;
275        self.errors.push(error);
276    }
277
278    /// Add multiple errors
279    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    /// Add a warning
287    pub fn add_warning(&mut self, warning: ValidationError) {
288        self.warnings.push(warning);
289    }
290
291    /// Add multiple warnings
292    pub fn add_warnings(&mut self, mut warnings: Vec<ValidationError>) {
293        self.warnings.append(&mut warnings);
294    }
295
296    /// Set processing duration
297    pub fn set_duration(&mut self, duration: Duration) {
298        self.duration = duration;
299    }
300
301    /// Get error count by severity
302    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    /// Get warning count by severity
310    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    /// Get errors by field path
318    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    /// Get warnings by field path
326    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    /// Get summary string
334    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    /// Get detailed report
349    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        // Test initial state
457        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        // Add error
463        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        // Add warning
471        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        // Test field filtering
483        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        // Test summary
490        let summary = result.summary();
491        assert!(summary.contains("failed"));
492        assert!(summary.contains("1 error"));
493        assert!(summary.contains("1 warning"));
494
495        // Test detailed report
496        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}