scirs2_core/validation/data/validator.rs
1//! Main validator implementation
2//!
3//! This module provides the core `Validator` struct that orchestrates all validation
4//! operations and manages caching, custom rules, and configuration.
5//!
6//! ## Features
7//!
8//! - **Schema-based validation**: Validate data against predefined schemas
9//! - **Custom validation rules**: Extend validation with custom business logic
10//! - **Caching**: Improve performance with result caching
11//! - **Array validation**: Specialized validation for scientific arrays
12//! - **Quality analysis**: Generate comprehensive data quality reports
13//!
14//! ## Examples
15//!
16//! ### Basic validation
17//!
18//! ```rust
19//! use scirs2_core::validation::data::{Validator, ValidationConfig, ValidationSchema, DataType};
20//!
21//! let config = ValidationConfig::default();
22//! let validator = Validator::new(config)?;
23//!
24//! let schema = ValidationSchema::new()
25//! .name("user_schema")
26//! .require_field("name", DataType::String)
27//! .require_field("age", DataType::Integer);
28//!
29//! #
30//! # {
31//! let data = serde_json::json!({
32//! "name": "John Doe",
33//! "age": 30
34//! });
35//!
36//! let result = validator.validate(&data, &schema)?;
37//! assert!(result.is_valid());
38//! # }
39//! # Ok::<(), Box<dyn std::error::Error>>(())
40//! ```
41//!
42//! ### Array validation
43//!
44//! ```rust
45//! use scirs2_core::validation::data::{Validator, ValidationConfig, ArrayValidationConstraints};
46//! use ::ndarray::Array2;
47//!
48//! let config = ValidationConfig::default();
49//! let validator = Validator::new(config.clone())?;
50//!
51//! let data = Array2::from_shape_vec((3, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])?;
52//!
53//! let constraints = ArrayValidationConstraints::new()
54//! .withshape(vec![3, 2])
55//! .check_numeric_quality();
56//!
57//! let result = validator.validate_ndarray(&data, &constraints, &config)?;
58//! assert!(result.is_valid());
59//! # Ok::<(), Box<dyn std::error::Error>>(())
60//! ```
61
62use crate::error::{CoreError, ErrorContext};
63use std::collections::{HashMap, HashSet};
64use std::sync::{Arc, RwLock};
65use std::time::Instant;
66
67use super::array_validation::ArrayValidator;
68use super::config::{ErrorSeverity, ValidationConfig, ValidationErrorType};
69use super::constraints::{ArrayValidationConstraints, Constraint};
70use super::errors::{ValidationError, ValidationResult, ValidationStats};
71use super::quality::{DataQualityReport, QualityAnalyzer};
72use super::schema::{DataType, ValidationSchema};
73
74// Core dependencies for array/matrix validation
75use ::ndarray::{ArrayBase, Data, Dimension, ScalarOperand};
76use num_traits::{Float, FromPrimitive};
77use std::fmt;
78
79use serde_json::Value as JsonValue;
80
81use std::collections::hash_map::DefaultHasher;
82use std::hash::{Hash, Hasher};
83
84/// Cache entry for validation results
85#[derive(Debug, Clone)]
86struct CacheEntry {
87 result: ValidationResult,
88 timestamp: Instant,
89 hit_count: usize,
90}
91
92/// Trait for custom validation rules
93pub trait ValidationRule {
94 /// Validate a value
95
96 fn validate(&self, value: &JsonValue, fieldpath: &str) -> Result<(), String>;
97
98 /// Get rule name
99 fn name(&self) -> &str;
100
101 /// Get rule description
102 fn description(&self) -> &str;
103}
104
105/// Main data validator with comprehensive validation capabilities
106pub struct Validator {
107 /// Validation configuration
108 config: ValidationConfig,
109 /// Validation result cache
110 cache: Arc<RwLock<HashMap<String, CacheEntry>>>,
111 /// Custom validation rules
112 custom_rules: HashMap<String, Box<dyn ValidationRule + Send + Sync>>,
113 /// Array validator
114 array_validator: ArrayValidator,
115 /// Quality analyzer
116 quality_analyzer: QualityAnalyzer,
117}
118
119impl Validator {
120 /// Create a new validator with configuration
121 ///
122 /// # Arguments
123 ///
124 /// * `config` - Validation configuration settings
125 ///
126 /// # Returns
127 ///
128 /// A new `Validator` instance or an error if initialization fails
129 ///
130 /// # Example
131 ///
132 /// ```rust
133 /// use scirs2_core::validation::data::{Validator, ValidationConfig};
134 ///
135 /// let mut config = ValidationConfig::default();
136 /// config.max_depth = 10;
137 /// config.strict_mode = true;
138 ///
139 /// let validator = Validator::new(config)?;
140 /// # Ok::<(), Box<dyn std::error::Error>>(())
141 /// ```
142 pub fn new(config: ValidationConfig) -> Result<Self, CoreError> {
143 Ok(Self {
144 config,
145 cache: Arc::new(RwLock::new(HashMap::new())),
146 custom_rules: HashMap::new(),
147 array_validator: ArrayValidator::new(),
148 quality_analyzer: QualityAnalyzer::new(),
149 })
150 }
151
152 /// Validate JSON data against a schema
153 ///
154 /// This method performs comprehensive validation of JSON data against a predefined schema,
155 /// including type checking, constraint validation, and custom rules.
156 ///
157 /// # Arguments
158 ///
159 /// * `data` - The JSON data to validate
160 /// * `schema` - The validation schema to apply
161 ///
162 /// # Returns
163 ///
164 /// A `ValidationResult` containing the validation outcome and any errors/warnings
165 ///
166 /// # Example
167 ///
168 /// ```rust
169 /// #
170 /// # {
171 /// use scirs2_core::validation::data::{Validator, ValidationSchema, DataType, Constraint, ValidationConfig};
172 ///
173 /// let validator = Validator::new(ValidationConfig::default())?;
174 ///
175 /// let schema = ValidationSchema::new()
176 /// .require_field("email", DataType::String)
177 /// .add_constraint("email", Constraint::Pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$".to_string()));
178 ///
179 /// let data = serde_json::json!({
180 /// "email": "user@example.com"
181 /// });
182 ///
183 /// let result = validator.validate(&data, &schema)?;
184 /// assert!(result.is_valid());
185 /// # }
186 /// # Ok::<(), Box<dyn std::error::Error>>(())
187 /// ```
188
189 pub fn validate(
190 &self,
191 data: &JsonValue,
192 schema: &ValidationSchema,
193 ) -> Result<ValidationResult, CoreError> {
194 let start_time = Instant::now();
195 let mut errors = Vec::new();
196 let mut warnings = Vec::new();
197 let mut stats = ValidationStats::default();
198
199 // Check cache if enabled
200 if self.config.enable_caching {
201 let cachekey = self.generate_cachekey(data, schema)?;
202 if let Some(mut cached_result) = self.get_cached_result(&cachekey)? {
203 // Update cache hit rate
204 let cache_hit_rate = self.calculate_cache_hit_rate()?;
205 cached_result.stats.set_cache_hit_rate(cache_hit_rate);
206 return Ok(cached_result);
207 }
208 }
209
210 // Validate each field in the schema
211 self.validate_fields(data, schema, "", &mut errors, &mut warnings, &mut stats, 0)?;
212
213 // Apply global constraints
214 self.validate_global_constraints(data, schema, &mut errors, &mut warnings, &mut stats)?;
215
216 // Check for additional fields if not allowed
217 if !schema.allow_additional_fields {
218 self.check_additional_fields(data, schema, &mut errors, &mut warnings)?;
219 }
220
221 let valid = errors.is_empty()
222 && !warnings
223 .iter()
224 .any(|w| w.severity == ErrorSeverity::Critical);
225 let duration = start_time.elapsed();
226
227 let mut result = ValidationResult {
228 valid,
229 errors,
230 warnings,
231 stats,
232 duration,
233 };
234
235 // Cache result if enabled
236 if self.config.enable_caching {
237 let cachekey = self.generate_cachekey(data, schema)?;
238 self.cache_result(&cachekey, result.clone())?;
239 }
240
241 // Update cache hit rate
242 if self.config.enable_caching {
243 let cache_hit_rate = self.calculate_cache_hit_rate()?;
244 result.stats.set_cache_hit_rate(cache_hit_rate);
245 }
246
247 Ok(result)
248 }
249
250 /// Validate ndarray with comprehensive checks
251 ///
252 /// Performs validation on scientific arrays including shape validation, numeric quality
253 /// checks, statistical constraints, and performance characteristics.
254 ///
255 /// # Arguments
256 ///
257 /// * `array` - The ndarray to validate
258 /// * `constraints` - Validation constraints to apply
259 /// * `config` - Validation configuration
260 ///
261 /// # Type Parameters
262 ///
263 /// * `S` - Storage type (must implement `Data`)
264 /// * `D` - Dimension type
265 /// * `S::Elem` - Element type (must be a floating-point type)
266 ///
267 /// # Returns
268 ///
269 /// A `ValidationResult` with detailed validation information
270 ///
271 /// # Example
272 ///
273 /// ```rust
274 /// use scirs2_core::validation::data::{Validator, ValidationConfig, ArrayValidationConstraints};
275 /// use ::ndarray::Array2;
276 ///
277 /// let validator = Validator::new(ValidationConfig::default())?;
278 ///
279 /// let data = Array2::from_shape_vec((3, 3), vec![
280 /// 1.0, 2.0, 3.0,
281 /// 4.0, 5.0, 6.0,
282 /// 7.0, 8.0, 9.0
283 /// ])?;
284 ///
285 /// let constraints = ArrayValidationConstraints::new()
286 /// .withshape(vec![3, 3])
287 /// .check_numeric_quality();
288 ///
289 /// let result = validator.validate_ndarray(&data, &constraints, &ValidationConfig::default())?;
290 /// assert!(result.is_valid());
291 /// # Ok::<(), Box<dyn std::error::Error>>(())
292 /// ```
293 pub fn validate_ndarray<S, D>(
294 &self,
295 array: &ArrayBase<S, D>,
296 constraints: &ArrayValidationConstraints,
297 config: &ValidationConfig,
298 ) -> Result<ValidationResult, CoreError>
299 where
300 S: Data,
301 D: Dimension,
302 S::Elem: Float + fmt::Debug + Send + Sync + ScalarOperand + FromPrimitive,
303 {
304 self.array_validator
305 .validate_ndarray(array, constraints, config)
306 }
307
308 /// Generate comprehensive data quality report
309 ///
310 /// Analyzes an array and generates a detailed quality report including completeness,
311 /// accuracy, consistency, and statistical properties.
312 ///
313 /// # Arguments
314 ///
315 /// * `array` - The array to analyze
316 /// * `fieldname` - Name of the field for reporting
317 ///
318 /// # Returns
319 ///
320 /// A `DataQualityReport` with quality metrics and recommendations
321 ///
322 /// # Example
323 ///
324 /// ```rust
325 /// use scirs2_core::validation::data::{Validator, ValidationConfig};
326 /// use ::ndarray::Array1;
327 ///
328 /// let validator = Validator::new(ValidationConfig::default())?;
329 ///
330 /// let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
331 /// let report = validator.generate_quality_report(&data, "measurements")?;
332 ///
333 /// println!("Quality score: {}", report.quality_score);
334 /// println!("Completeness: {}", report.metrics.completeness);
335 /// # Ok::<(), Box<dyn std::error::Error>>(())
336 /// ```
337 pub fn generate_quality_report<S, D>(
338 &self,
339 array: &ArrayBase<S, D>,
340 fieldname: &str,
341 ) -> Result<DataQualityReport, CoreError>
342 where
343 S: Data,
344 D: Dimension,
345 S::Elem: Float + fmt::Debug + ScalarOperand + Send + Sync + FromPrimitive,
346 {
347 self.quality_analyzer
348 .generate_quality_report(array, fieldname)
349 }
350
351 /// Add a custom validation rule
352 ///
353 /// Registers a custom validation rule that can be referenced in schemas.
354 ///
355 /// # Arguments
356 ///
357 /// * `name` - Unique name for the rule
358 /// * `rule` - The validation rule implementation
359 ///
360 /// # Example
361 ///
362 /// ```rust
363 /// use scirs2_core::validation::data::{Validator, ValidationConfig, ValidationRule};
364 /// use serde_json::Value as JsonValue;
365 ///
366 /// struct EmailRule;
367 ///
368 /// impl ValidationRule for EmailRule {
369 /// fn validate(&self, value: &JsonValue, fieldpath: &str) -> Result<(), String> {
370 /// if let Some(email) = value.as_str() {
371 /// if email.contains('@') {
372 /// Ok(())
373 /// } else {
374 /// Err(format!("{fieldpath}: invalid email format"))
375 /// }
376 /// } else {
377 /// Err(format!("{fieldpath}: expected string"))
378 /// }
379 /// }
380 ///
381 /// fn name(&self) -> &str { "email" }
382 /// fn description(&self) -> &str { "Validates email format" }
383 /// }
384 ///
385 /// let mut validator = Validator::new(ValidationConfig::default())?;
386 /// validator.add_custom_rule("email".to_string(), Box::new(EmailRule));
387 /// # Ok::<(), Box<dyn std::error::Error>>(())
388 /// ```
389 pub fn add_custom_rule(&mut self, name: String, rule: Box<dyn ValidationRule + Send + Sync>) {
390 self.custom_rules.insert(name, rule);
391 }
392
393 /// Clear validation cache
394 pub fn clear_cache(&self) -> Result<(), CoreError> {
395 let mut cache = self.cache.write().map_err(|_| {
396 CoreError::ComputationError(ErrorContext::new(
397 "Failed to acquire cache write lock".to_string(),
398 ))
399 })?;
400 cache.clear();
401 Ok(())
402 }
403
404 /// Get cache statistics
405 pub fn get_cache_stats(&self) -> Result<(usize, f64), CoreError> {
406 let cache = self.cache.read().map_err(|_| {
407 CoreError::ComputationError(ErrorContext::new(
408 "Failed to acquire cache read lock".to_string(),
409 ))
410 })?;
411
412 let size = cache.len();
413 let hit_rate = self.calculate_cache_hit_rate()?;
414
415 Ok((size, hit_rate))
416 }
417
418 /// Validate individual fields
419 fn validate_fields(
420 &self,
421 data: &JsonValue,
422 schema: &ValidationSchema,
423 fieldpath: &str,
424 errors: &mut Vec<ValidationError>,
425 warnings: &mut Vec<ValidationError>,
426 stats: &mut ValidationStats,
427 depth: usize,
428 ) -> Result<(), CoreError> {
429 if depth > self.config.max_depth {
430 errors.push(ValidationError {
431 errortype: ValidationErrorType::SchemaError,
432 fieldpath: fieldpath.to_string(),
433 message: "Maximum validation _depth exceeded".to_string(),
434 expected: None,
435 actual: None,
436 constraint: None,
437 severity: ErrorSeverity::Error,
438 context: HashMap::new(),
439 });
440 return Ok(());
441 }
442
443 let data_obj = match data {
444 JsonValue::Object(obj) => obj,
445 _ => {
446 errors.push(ValidationError {
447 errortype: ValidationErrorType::TypeMismatch,
448 fieldpath: fieldpath.to_string(),
449 message: "Expected object".to_string(),
450 expected: Some("object".to_string()),
451 actual: Some(self.get_value_type_name(data)),
452 constraint: None,
453 severity: ErrorSeverity::Error,
454 context: HashMap::new(),
455 });
456 return Ok(());
457 }
458 };
459
460 for (fieldname, field_def) in &schema.fields {
461 stats.add_field_validation();
462
463 let fieldpath = if depth == 0 {
464 fieldname.clone()
465 } else {
466 fieldname.to_string()
467 };
468
469 if let Some(field_value) = data_obj.get(fieldname) {
470 // Field exists, validate type and constraints
471 self.validate_field_type(field_value, &field_def.datatype, &fieldpath, errors)?;
472 self.validate_field_constraints(
473 field_value,
474 &field_def.constraints,
475 &fieldpath,
476 errors,
477 warnings,
478 stats,
479 )?;
480
481 // Validate custom rules
482 for rule_name in &field_def.validation_rules {
483 if let Some(rule) = self.custom_rules.get(rule_name) {
484 if let Err(ruleerror) = rule.validate(field_value, &fieldpath) {
485 errors.push(ValidationError {
486 errortype: ValidationErrorType::CustomRuleFailure,
487 fieldpath: fieldpath.clone(),
488 message: ruleerror,
489 expected: None,
490 actual: None,
491 constraint: Some(rule_name.clone()),
492 severity: ErrorSeverity::Error,
493 context: HashMap::new(),
494 });
495 }
496 }
497 }
498 } else if field_def.required {
499 // Required field is missing
500 errors.push(ValidationError {
501 errortype: ValidationErrorType::MissingRequiredField,
502 fieldpath,
503 message: format!("Required field '{}' is missing", fieldname),
504 expected: Some(format!("{:?}", field_def.datatype)),
505 actual: Some("missing".to_string()),
506 constraint: Some("required".to_string()),
507 severity: ErrorSeverity::Error,
508 context: HashMap::new(),
509 });
510 }
511 }
512
513 Ok(())
514 }
515
516 /// Validate field type
517 fn validate_field_type(
518 &self,
519 value: &JsonValue,
520 expected_type: &DataType,
521 fieldpath: &str,
522 errors: &mut Vec<ValidationError>,
523 ) -> Result<(), CoreError> {
524 let type_matches = match expected_type {
525 DataType::Boolean => value.is_boolean(),
526 DataType::Integer => value.is_i64(),
527 DataType::Float32 | DataType::Float64 => value.is_f64() || value.is_i64(),
528 DataType::String => value.is_string(),
529 DataType::Array(_) => value.is_array(),
530 DataType::Object => value.is_object(),
531 DataType::Null => value.is_null(),
532 _ => true, // Other types not yet implemented
533 };
534
535 if !type_matches {
536 errors.push(ValidationError {
537 errortype: ValidationErrorType::TypeMismatch,
538 fieldpath: fieldpath.to_string(),
539 message: format!(
540 "Type mismatch: expected {:?}, got {}",
541 expected_type,
542 self.get_value_type_name(value)
543 ),
544 expected: Some(format!("{expected_type:?}")),
545 actual: Some(self.get_value_type_name(value)),
546 constraint: Some("type".to_string()),
547 severity: ErrorSeverity::Error,
548 context: HashMap::new(),
549 });
550 }
551
552 Ok(())
553 }
554
555 /// Validate field constraints
556 #[allow(clippy::only_used_in_recursion)]
557 fn validate_field_constraints(
558 &self,
559 value: &JsonValue,
560 constraints: &[Constraint],
561 fieldpath: &str,
562 errors: &mut Vec<ValidationError>,
563 warnings: &mut Vec<ValidationError>,
564 stats: &mut ValidationStats,
565 ) -> Result<(), CoreError> {
566 for constraint in constraints {
567 stats.add_constraint_check();
568
569 match constraint {
570 Constraint::Range { min, max } => {
571 if let Some(num) = value.as_f64() {
572 if num < *min || num > *max {
573 errors.push(ValidationError {
574 errortype: ValidationErrorType::OutOfRange,
575 fieldpath: fieldpath.to_string(),
576 message: format!(
577 "Value {} is out of range [{}, {}]",
578 num, min, max
579 ),
580 expected: Some(format!("[{}, {}]", min, max)),
581 actual: Some(num.to_string()),
582 constraint: Some("range".to_string()),
583 severity: ErrorSeverity::Error,
584 context: HashMap::new(),
585 });
586 }
587 }
588 }
589 Constraint::Length { min, max } => {
590 if let Some(s) = value.as_str() {
591 let len = s.len();
592 if len < *min || len > *max {
593 errors.push(ValidationError {
594 errortype: ValidationErrorType::ConstraintViolation,
595 fieldpath: fieldpath.to_string(),
596 message: format!(
597 "String length {} is out of range [{}, {}]",
598 len, min, max
599 ),
600 expected: Some(format!("length in [{}, {}]", min, max)),
601 actual: Some(len.to_string()),
602 constraint: Some("length".to_string()),
603 severity: ErrorSeverity::Error,
604 context: HashMap::new(),
605 });
606 }
607 }
608 }
609 Constraint::NotNull => {
610 if value.is_null() {
611 errors.push(ValidationError {
612 errortype: ValidationErrorType::ConstraintViolation,
613 fieldpath: fieldpath.to_string(),
614 message: "Value cannot be null".to_string(),
615 expected: Some("non-null value".to_string()),
616 actual: Some("null".to_string()),
617 constraint: Some("not_null".to_string()),
618 severity: ErrorSeverity::Error,
619 context: HashMap::new(),
620 });
621 }
622 }
623 Constraint::Unique => {
624 if let Some(arr) = value.as_array() {
625 let mut seen = HashSet::new();
626 for item in arr {
627 let item_str = item.to_string();
628 if !seen.insert(item_str.clone()) {
629 errors.push(ValidationError {
630 errortype: ValidationErrorType::DuplicateValues,
631 fieldpath: fieldpath.to_string(),
632 message: item_str.to_string(),
633 expected: Some("unique values".to_string()),
634 actual: Some("duplicate found".to_string()),
635 constraint: Some("unique".to_string()),
636 severity: ErrorSeverity::Error,
637 context: HashMap::new(),
638 });
639 }
640 }
641 }
642 }
643 Constraint::Pattern(pattern) => {
644 if let Some(s) = value.as_str() {
645 #[cfg(feature = "regex")]
646 {
647 if let Ok(re) = regex::Regex::new(pattern) {
648 if !re.is_match(s) {
649 errors.push(ValidationError {
650 errortype: ValidationErrorType::InvalidFormat,
651 fieldpath: fieldpath.to_string(),
652 message: format!(
653 "Value '{}' does not match pattern '{}'",
654 s, pattern
655 ),
656 expected: Some(pattern.to_string()),
657 actual: Some(s.to_string()),
658 constraint: Some(pattern.to_string()),
659 severity: ErrorSeverity::Error,
660 context: HashMap::new(),
661 });
662 }
663 }
664 }
665 #[cfg(not(feature = "regex"))]
666 {
667 warnings.push(ValidationError {
668 errortype: ValidationErrorType::SchemaError,
669 fieldpath: fieldpath.to_string(),
670 message: "Pattern validation requires 'regex' feature".to_string(),
671 expected: None,
672 actual: None,
673 constraint: Some(pattern.to_string()),
674 severity: ErrorSeverity::Warning,
675 context: HashMap::new(),
676 });
677 }
678 }
679 }
680 Constraint::AllowedValues(allowed) => {
681 let value_str = match value {
682 JsonValue::String(s) => s.clone(),
683 _ => value.to_string(),
684 };
685 if !allowed.contains(&value_str) {
686 errors.push(ValidationError {
687 errortype: ValidationErrorType::ConstraintViolation,
688 fieldpath: fieldpath.to_string(),
689 message: format!(
690 "Value '{}' is not in allowed values: {:?}",
691 value_str, allowed
692 ),
693 expected: Some(format!("{allowed:?}")),
694 actual: Some(value_str),
695 constraint: Some("allowed_values".to_string()),
696 severity: ErrorSeverity::Error,
697 context: HashMap::new(),
698 });
699 }
700 }
701 Constraint::Precision { decimal_places } => {
702 if let Some(num) = value.as_f64() {
703 let num_str = num.to_string();
704 if let Some(dot_pos) = num_str.find('.') {
705 let actual_precision = num_str.len() - dot_pos - 1;
706 if actual_precision > *decimal_places {
707 errors.push(ValidationError {
708 errortype: ValidationErrorType::ConstraintViolation,
709 fieldpath: fieldpath.to_string(),
710 message: format!(
711 "Value {} has {} decimal places, expected at most {}",
712 num, actual_precision, decimal_places
713 ),
714 expected: Some(format!(
715 "max {} decimal places",
716 decimal_places
717 )),
718 actual: Some(format!("{} decimal places", actual_precision)),
719 constraint: Some("precision".to_string()),
720 severity: ErrorSeverity::Error,
721 context: HashMap::new(),
722 });
723 }
724 }
725 }
726 }
727 Constraint::ArraySize { min, max } => {
728 if let Some(arr) = value.as_array() {
729 let size = arr.len();
730 if size < *min || size > *max {
731 errors.push(ValidationError {
732 errortype: ValidationErrorType::ConstraintViolation,
733 fieldpath: fieldpath.to_string(),
734 message: format!(
735 "Array size {} is out of range [{}, {}]",
736 size, min, max
737 ),
738 expected: Some(format!("size in [{}, {}]", min, max)),
739 actual: Some(size.to_string()),
740 constraint: Some("array_size".to_string()),
741 severity: ErrorSeverity::Error,
742 context: HashMap::new(),
743 });
744 }
745 }
746 }
747 Constraint::ArrayElements(element_constraint) => {
748 if let Some(arr) = value.as_array() {
749 for (idx, element) in arr.iter().enumerate() {
750 let element_path = format!("{}[{}]", fieldpath, idx);
751 self.validate_field_constraints(
752 element,
753 &[(**element_constraint).clone()],
754 &element_path,
755 errors,
756 warnings,
757 stats,
758 )?;
759 }
760 }
761 }
762 Constraint::Custom(_rule_name) => {
763 // Custom constraint validation is handled separately in validate_fields
764 // This is just a placeholder for consistency
765 }
766 Constraint::Statistical(stats_constraints) => {
767 // Validate statistical properties of numeric arrays
768 if let Some(arr) = value.as_array() {
769 let mut numeric_values: Vec<f64> = Vec::new();
770
771 // Extract numeric values from array
772 for (idx, val) in arr.iter().enumerate() {
773 if let Some(num) = val.as_f64() {
774 numeric_values.push(num);
775 } else if let Some(num) = val.as_i64() {
776 numeric_values.push(num as f64);
777 } else {
778 errors.push(ValidationError {
779 errortype: ValidationErrorType::TypeMismatch,
780 fieldpath: format!("{}[{}]", fieldpath, idx),
781 message: format!("{val}"),
782 expected: Some("number".to_string()),
783 actual: Some(val.to_string()),
784 constraint: Some("statistical".to_string()),
785 severity: ErrorSeverity::Error,
786 context: HashMap::new(),
787 });
788 continue;
789 }
790 }
791
792 if numeric_values.is_empty() {
793 errors.push(ValidationError {
794 errortype: ValidationErrorType::ConstraintViolation,
795 fieldpath: fieldpath.to_string(),
796 message: "Statistical validation requires numeric values"
797 .to_string(),
798 expected: Some("numeric array".to_string()),
799 actual: Some("empty or non-numeric array".to_string()),
800 constraint: Some("statistical".to_string()),
801 severity: ErrorSeverity::Error,
802 context: HashMap::new(),
803 });
804 } else {
805 // Calculate statistics
806 let count = numeric_values.len() as f64;
807 let mean = numeric_values.iter().sum::<f64>() / count;
808
809 // Calculate standard deviation
810 let variance = numeric_values
811 .iter()
812 .map(|x| (x - mean).powi(2))
813 .sum::<f64>()
814 / count;
815 let std_dev = variance.sqrt();
816
817 // Check mean constraints
818 if let Some(min_mean) = stats_constraints.min_mean {
819 if mean < min_mean {
820 errors.push(ValidationError {
821 errortype: ValidationErrorType::ConstraintViolation,
822 fieldpath: fieldpath.to_string(),
823 message: format!(
824 "Mean {:.4} is less than minimum {:.4}",
825 mean, min_mean
826 ),
827 expected: Some(format!(":.4{min_mean}")),
828 actual: Some(format!(":.4{mean}")),
829 constraint: Some("statistical.min_mean".to_string()),
830 severity: ErrorSeverity::Error,
831 context: HashMap::new(),
832 });
833 }
834 }
835
836 if let Some(max_mean) = stats_constraints.max_mean {
837 if mean > max_mean {
838 errors.push(ValidationError {
839 errortype: ValidationErrorType::ConstraintViolation,
840 fieldpath: fieldpath.to_string(),
841 message: format!(
842 "Mean {:.4} exceeds maximum {:.4}",
843 mean, max_mean
844 ),
845 expected: Some(format!(":.4{max_mean}")),
846 actual: Some(format!(":.4{mean}")),
847 constraint: Some("statistical.max_mean".to_string()),
848 severity: ErrorSeverity::Error,
849 context: HashMap::new(),
850 });
851 }
852 }
853
854 // Check standard deviation constraints
855 if let Some(min_std) = stats_constraints.min_std {
856 if std_dev < min_std {
857 errors.push(ValidationError {
858 errortype: ValidationErrorType::ConstraintViolation,
859 fieldpath: fieldpath.to_string(),
860 message: format!(
861 "Standard deviation {:.4} is less than minimum {:.4}",
862 std_dev, min_std
863 ),
864 expected: Some(format!(":.4{min_std}")),
865 actual: Some(format!(":.4{std_dev}")),
866 constraint: Some("statistical.min_std".to_string()),
867 severity: ErrorSeverity::Error,
868 context: HashMap::new(),
869 });
870 }
871 }
872
873 if let Some(max_std) = stats_constraints.max_std {
874 if std_dev > max_std {
875 errors.push(ValidationError {
876 errortype: ValidationErrorType::ConstraintViolation,
877 fieldpath: fieldpath.to_string(),
878 message: format!(
879 "Standard deviation {:.4} exceeds maximum {:.4}",
880 std_dev, max_std
881 ),
882 expected: Some(format!(":.4{max_std}")),
883 actual: Some(format!(":.4{std_dev}")),
884 constraint: Some("statistical.max_std".to_string()),
885 severity: ErrorSeverity::Error,
886 context: HashMap::new(),
887 });
888 }
889 }
890
891 // Check distribution (if specified)
892 if let Some(expected_dist) = &stats_constraints.expected_distribution {
893 // For now, just add a warning - full distribution testing would require more complex analysis
894 warnings.push(ValidationError {
895 errortype: ValidationErrorType::SchemaError,
896 fieldpath: fieldpath.to_string(),
897 message: format!(
898 "Distribution testing for '{}' not yet implemented",
899 expected_dist
900 ),
901 expected: None,
902 actual: None,
903 constraint: Some("statistical.distribution".to_string()),
904 severity: ErrorSeverity::Warning,
905 context: HashMap::new(),
906 });
907 }
908 }
909 } else {
910 errors.push(ValidationError {
911 errortype: ValidationErrorType::TypeMismatch,
912 fieldpath: fieldpath.to_string(),
913 message: "Statistical constraints require an array of numeric values"
914 .to_string(),
915 expected: Some("numeric array".to_string()),
916 actual: Some(format!("{value}")),
917 constraint: Some("statistical".to_string()),
918 severity: ErrorSeverity::Error,
919 context: HashMap::new(),
920 });
921 }
922 }
923 Constraint::Temporal(time_constraints) => {
924 // Validate temporal data (array of timestamps)
925 if let Some(arr) = value.as_array() {
926 let mut timestamps: Vec<i64> = Vec::new();
927
928 // Extract timestamps from array
929 for (idx, val) in arr.iter().enumerate() {
930 if let Some(ts) = val.as_i64() {
931 timestamps.push(ts);
932 } else if let Some(ts) = val.as_f64() {
933 timestamps.push(ts as i64);
934 } else {
935 errors.push(ValidationError {
936 errortype: ValidationErrorType::TypeMismatch,
937 fieldpath: format!("{}[{}]", fieldpath, idx),
938 message: format!("{val}"),
939 expected: Some("timestamp (integer or float)".to_string()),
940 actual: Some(val.to_string()),
941 constraint: Some("temporal".to_string()),
942 severity: ErrorSeverity::Error,
943 context: HashMap::new(),
944 });
945 continue;
946 }
947 }
948
949 if timestamps.len() < 2 {
950 errors.push(ValidationError {
951 errortype: ValidationErrorType::ConstraintViolation,
952 fieldpath: fieldpath.to_string(),
953 message: "Temporal validation requires at least 2 timestamps"
954 .to_string(),
955 expected: Some("at least 2 timestamps".to_string()),
956 actual: Some(format!("{} timestamps", timestamps.len())),
957 constraint: Some("temporal".to_string()),
958 severity: ErrorSeverity::Error,
959 context: HashMap::new(),
960 });
961 } else {
962 // Check for monotonic ordering if required
963 if time_constraints.require_monotonic {
964 let mut _is_monotonic = true;
965 for i in 1..timestamps.len() {
966 if timestamps[i] < timestamps[i.saturating_sub(1)] {
967 _is_monotonic = false;
968 errors.push(ValidationError {
969 errortype: ValidationErrorType::ConstraintViolation,
970 fieldpath: fieldpath.to_string(),
971 message: format!(
972 "Timestamps not monotonic: {} comes after {}",
973 timestamps[i],
974 timestamps[i.saturating_sub(1)]
975 ),
976 expected: Some(
977 "monotonic increasing timestamps".to_string(),
978 ),
979 actual: Some("non-monotonic timestamps".to_string()),
980 constraint: Some("temporal.monotonic".to_string()),
981 severity: ErrorSeverity::Error,
982 context: HashMap::new(),
983 });
984 break;
985 }
986 }
987 }
988
989 // Check for duplicates if not allowed
990 if !time_constraints.allow_duplicates {
991 let mut seen = std::collections::HashSet::new();
992 for &ts in ×tamps {
993 if !seen.insert(ts) {
994 errors.push(ValidationError {
995 errortype: ValidationErrorType::ConstraintViolation,
996 fieldpath: fieldpath.to_string(),
997 message: format!("{ts}"),
998 expected: Some("unique timestamps".to_string()),
999 actual: Some("duplicate timestamps".to_string()),
1000 constraint: Some("temporal.unique".to_string()),
1001 severity: ErrorSeverity::Error,
1002 context: HashMap::new(),
1003 });
1004 break;
1005 }
1006 }
1007 }
1008
1009 // Check interval constraints
1010 for i in 1..timestamps.len() {
1011 let interval_ms =
1012 (timestamps[i] - timestamps[i.saturating_sub(1)]).abs();
1013 let interval = std::time::Duration::from_millis(interval_ms as u64);
1014
1015 if let Some(min_interval) = &time_constraints.min_interval {
1016 if interval < *min_interval {
1017 errors.push(ValidationError {
1018 errortype: ValidationErrorType::ConstraintViolation,
1019 fieldpath: fieldpath.to_string(),
1020 message: format!(
1021 "Interval {:?} is less than minimum {:?}",
1022 interval, min_interval
1023 ),
1024 expected: Some(format!(
1025 "min interval {:?}",
1026 min_interval
1027 )),
1028 actual: Some(format!("{:?}", interval)),
1029 constraint: Some("temporal.min_interval".to_string()),
1030 severity: ErrorSeverity::Error,
1031 context: HashMap::new(),
1032 });
1033 break;
1034 }
1035 }
1036
1037 if let Some(max_interval) = &time_constraints.max_interval {
1038 if interval > *max_interval {
1039 errors.push(ValidationError {
1040 errortype: ValidationErrorType::ConstraintViolation,
1041 fieldpath: fieldpath.to_string(),
1042 message: format!(
1043 "Interval {:?} exceeds maximum {:?}",
1044 interval, max_interval
1045 ),
1046 expected: Some(format!(
1047 "max interval {:?}",
1048 max_interval
1049 )),
1050 actual: Some(format!("{:?}", interval)),
1051 constraint: Some("temporal.max_interval".to_string()),
1052 severity: ErrorSeverity::Error,
1053 context: HashMap::new(),
1054 });
1055 break;
1056 }
1057 }
1058 }
1059 }
1060 } else {
1061 errors.push(ValidationError {
1062 errortype: ValidationErrorType::TypeMismatch,
1063 fieldpath: fieldpath.to_string(),
1064 message: "Temporal constraints require an array of timestamps"
1065 .to_string(),
1066 expected: Some("array of timestamps".to_string()),
1067 actual: Some(format!("{value}")),
1068 constraint: Some("temporal".to_string()),
1069 severity: ErrorSeverity::Error,
1070 context: HashMap::new(),
1071 });
1072 }
1073 }
1074 Constraint::Shape(shape_constraints) => {
1075 // Validate array/matrix shape properties
1076 if let Some(arr) = value.as_array() {
1077 // For JSON arrays, we can only validate 1D arrays directly
1078 // Multi-dimensional arrays would need to be nested arrays
1079 let mut shape = vec![arr.len()];
1080
1081 // Check if it's a nested array (2D)
1082 let mut is_2d = true;
1083 let mut inner_sizes = Vec::new();
1084 for elem in arr {
1085 if let Some(inner_arr) = elem.as_array() {
1086 inner_sizes.push(inner_arr.len());
1087 } else {
1088 is_2d = false;
1089 break;
1090 }
1091 }
1092
1093 if is_2d && !inner_sizes.is_empty() {
1094 // Check if all inner arrays have the same size
1095 let first_size = inner_sizes[0];
1096 if inner_sizes.iter().all(|&s| s == first_size) {
1097 shape = vec![arr.len(), first_size];
1098 } else {
1099 errors.push(ValidationError {
1100 errortype: ValidationErrorType::ShapeError,
1101 fieldpath: fieldpath.to_string(),
1102 message: "Jagged arrays are not supported - all rows must have the same length".to_string(),
1103 expected: Some("rectangular array".to_string()),
1104 actual: Some("jagged array".to_string()),
1105 constraint: Some(format!("{:?}", shape)),
1106 severity: ErrorSeverity::Error,
1107 context: HashMap::new(),
1108 });
1109 return Ok(());
1110 }
1111 }
1112
1113 // Validate dimensions
1114 if !shape_constraints.dimensions.is_empty() {
1115 let expected_dims = &shape_constraints.dimensions;
1116 if shape.len() != expected_dims.len() {
1117 errors.push(ValidationError {
1118 errortype: ValidationErrorType::ShapeError,
1119 fieldpath: fieldpath.to_string(),
1120 message: format!(
1121 "Array has {} dimensions, expected {}",
1122 shape.len(),
1123 expected_dims.len()
1124 ),
1125 expected: Some(format!("{} dimensions", expected_dims.len())),
1126 actual: Some(format!("{} dimensions", shape.len())),
1127 constraint: Some("shape.dimensions".to_string()),
1128 severity: ErrorSeverity::Error,
1129 context: HashMap::new(),
1130 });
1131 } else {
1132 // Check each dimension
1133 for (idx, (actual_dim, expected_dim)) in
1134 shape.iter().zip(expected_dims.iter()).enumerate()
1135 {
1136 if let Some(expected) = expected_dim {
1137 if actual_dim != expected {
1138 errors.push(ValidationError {
1139 errortype: ValidationErrorType::ShapeError,
1140 fieldpath: fieldpath.to_string(),
1141 message: format!(
1142 "Dimension {} has size {}, expected {}",
1143 idx, actual_dim, expected
1144 ),
1145 expected: Some(format!(
1146 "dimension {} = {}",
1147 idx, expected
1148 )),
1149 actual: Some(format!(
1150 "dimension {} = {}",
1151 idx, actual_dim
1152 )),
1153 constraint: Some(format!(
1154 "shape.dimension[{}]",
1155 idx
1156 )),
1157 severity: ErrorSeverity::Error,
1158 context: HashMap::new(),
1159 });
1160 }
1161 }
1162 }
1163 }
1164 }
1165
1166 // Check total element count
1167 let total_elements: usize = shape.iter().product();
1168
1169 if let Some(min_elements) = shape_constraints.min_elements {
1170 if total_elements < min_elements {
1171 errors.push(ValidationError {
1172 errortype: ValidationErrorType::ShapeError,
1173 fieldpath: fieldpath.to_string(),
1174 message: format!(
1175 "Array has {} elements, minimum required is {}",
1176 total_elements, min_elements
1177 ),
1178 expected: Some(format!(">= {} elements", min_elements)),
1179 actual: Some(format!("{} elements", total_elements)),
1180 constraint: Some("shape.min_elements".to_string()),
1181 severity: ErrorSeverity::Error,
1182 context: HashMap::new(),
1183 });
1184 }
1185 }
1186
1187 if let Some(max_elements) = shape_constraints.max_elements {
1188 if total_elements > max_elements {
1189 errors.push(ValidationError {
1190 errortype: ValidationErrorType::ShapeError,
1191 fieldpath: fieldpath.to_string(),
1192 message: format!(
1193 "Array has {} elements, maximum allowed is {}",
1194 total_elements, max_elements
1195 ),
1196 expected: Some(format!("<= {} elements", max_elements)),
1197 actual: Some(format!("{} elements", total_elements)),
1198 constraint: Some("shape.max_elements".to_string()),
1199 severity: ErrorSeverity::Error,
1200 context: HashMap::new(),
1201 });
1202 }
1203 }
1204
1205 // Check if square matrix is required (only for 2D arrays)
1206 if shape_constraints.require_square
1207 && shape.len() == 2
1208 && shape[0] != shape[1]
1209 {
1210 errors.push(ValidationError {
1211 errortype: ValidationErrorType::ShapeError,
1212 fieldpath: fieldpath.to_string(),
1213 message: format!(
1214 "Matrix must be square, but has shape {}x{}",
1215 shape[0], shape[1]
1216 ),
1217 expected: Some("square matrix".to_string()),
1218 actual: Some(format!("{}x{} matrix", shape[0], shape[1])),
1219 constraint: Some("shape.square".to_string()),
1220 severity: ErrorSeverity::Error,
1221 context: HashMap::new(),
1222 });
1223 }
1224 } else {
1225 errors.push(ValidationError {
1226 errortype: ValidationErrorType::TypeMismatch,
1227 fieldpath: fieldpath.to_string(),
1228 message: "Shape constraints require an array".to_string(),
1229 expected: Some("array".to_string()),
1230 actual: Some(format!("{value}")),
1231 constraint: Some("shape".to_string()),
1232 severity: ErrorSeverity::Error,
1233 context: HashMap::new(),
1234 });
1235 }
1236 }
1237 Constraint::And(constraints) => {
1238 // All constraints must pass
1239 for constraint in constraints {
1240 self.validate_field_constraints(
1241 value,
1242 std::slice::from_ref(constraint),
1243 fieldpath,
1244 errors,
1245 warnings,
1246 stats,
1247 )?;
1248 }
1249 }
1250 Constraint::Or(constraints) => {
1251 // At least one constraint must pass
1252 let mut temperrors = Vec::new();
1253 let mut any_passed = false;
1254
1255 for constraint in constraints {
1256 let mut constrainterrors = Vec::new();
1257 let mut constraintwarnings = Vec::new();
1258 self.validate_field_constraints(
1259 value,
1260 std::slice::from_ref(constraint),
1261 fieldpath,
1262 &mut constrainterrors,
1263 &mut constraintwarnings,
1264 stats,
1265 )?;
1266
1267 if constrainterrors.is_empty() {
1268 any_passed = true;
1269 break;
1270 } else {
1271 temperrors.extend(constrainterrors);
1272 }
1273 }
1274
1275 if !any_passed {
1276 errors.push(ValidationError {
1277 errortype: ValidationErrorType::ConstraintViolation,
1278 fieldpath: fieldpath.to_string(),
1279 message: format!(
1280 "None of the OR constraints passed: {} errors",
1281 temperrors.len()
1282 ),
1283 expected: Some("at least one constraint to pass".to_string()),
1284 actual: Some("all constraints failed".to_string()),
1285 constraint: Some("or".to_string()),
1286 severity: ErrorSeverity::Error,
1287 context: HashMap::new(),
1288 });
1289 }
1290 }
1291 Constraint::Not(constraint) => {
1292 // Constraint must not pass
1293 let mut temperrors = Vec::new();
1294 let mut temp_warnings = Vec::new();
1295 self.validate_field_constraints(
1296 value,
1297 &[*constraint.clone()],
1298 fieldpath,
1299 &mut temperrors,
1300 &mut temp_warnings,
1301 stats,
1302 )?;
1303
1304 if temperrors.is_empty() {
1305 errors.push(ValidationError {
1306 errortype: ValidationErrorType::ConstraintViolation,
1307 fieldpath: fieldpath.to_string(),
1308 message: "NOT constraint failed: inner constraint passed".to_string(),
1309 expected: Some("constraint to fail".to_string()),
1310 actual: Some("constraint passed".to_string()),
1311 constraint: Some("not".to_string()),
1312 severity: ErrorSeverity::Error,
1313 context: HashMap::new(),
1314 });
1315 }
1316 }
1317 Constraint::If {
1318 condition,
1319 then_constraint,
1320 else_constraint,
1321 } => {
1322 // Conditional constraint
1323 let mut conditionerrors = Vec::new();
1324 let mut condition_warnings = Vec::new();
1325 self.validate_field_constraints(
1326 value,
1327 &[*condition.clone()],
1328 fieldpath,
1329 &mut conditionerrors,
1330 &mut condition_warnings,
1331 stats,
1332 )?;
1333
1334 if conditionerrors.is_empty() {
1335 // Condition passed, apply then_constraint
1336 self.validate_field_constraints(
1337 value,
1338 &[*then_constraint.clone()],
1339 fieldpath,
1340 errors,
1341 warnings,
1342 stats,
1343 )?;
1344 } else if let Some(else_constraint) = else_constraint {
1345 // Condition failed, apply else_constraint
1346 self.validate_field_constraints(
1347 value,
1348 &[*else_constraint.clone()],
1349 fieldpath,
1350 errors,
1351 warnings,
1352 stats,
1353 )?;
1354 }
1355 }
1356 }
1357 }
1358 Ok(())
1359 }
1360
1361 /// Validate global constraints
1362 #[allow(clippy::ptr_arg)]
1363 fn validate_global_constraints(
1364 &self,
1365 data: &JsonValue,
1366 schema: &ValidationSchema,
1367 errors: &mut Vec<ValidationError>,
1368 warnings: &mut Vec<ValidationError>,
1369 stats: &mut ValidationStats,
1370 ) -> Result<(), CoreError> {
1371 // Global constraints would be implemented here
1372 Ok(())
1373 }
1374
1375 /// Check for additional fields
1376 #[allow(clippy::ptr_arg)]
1377 fn check_additional_fields(
1378 &self,
1379 data: &JsonValue,
1380 schema: &ValidationSchema,
1381 errors: &mut Vec<ValidationError>,
1382 warnings: &mut Vec<ValidationError>,
1383 ) -> Result<(), CoreError> {
1384 if let JsonValue::Object(obj) = data {
1385 for key in obj.keys() {
1386 if !schema.fields.contains_key(key) {
1387 errors.push(ValidationError {
1388 errortype: ValidationErrorType::SchemaError,
1389 fieldpath: key.clone(),
1390 message: format!("Additional field '{}' not allowed", key),
1391 expected: None,
1392 actual: Some(key.clone()),
1393 constraint: None,
1394 severity: ErrorSeverity::Warning,
1395 context: HashMap::new(),
1396 });
1397 }
1398 }
1399 }
1400 Ok(())
1401 }
1402
1403 /// Get the type name for a JSON value
1404 fn get_value_type_name(&self, value: &JsonValue) -> String {
1405 match value {
1406 JsonValue::Null => "null".to_string(),
1407 JsonValue::Bool(_) => "boolean".to_string(),
1408 JsonValue::Number(n) => {
1409 if n.is_i64() {
1410 "integer".to_string()
1411 } else {
1412 "number".to_string()
1413 }
1414 }
1415 JsonValue::String(_) => "string".to_string(),
1416 JsonValue::Array(_) => "array".to_string(),
1417 JsonValue::Object(_) => "object".to_string(),
1418 }
1419 }
1420
1421 /// Generate cache key for validation result
1422 fn generate_cachekey(
1423 &self,
1424 data: &JsonValue,
1425 schema: &ValidationSchema,
1426 ) -> Result<String, CoreError> {
1427 let mut hasher = DefaultHasher::new();
1428 data.to_string().hash(&mut hasher);
1429 schema.name.hash(&mut hasher);
1430 schema.version.hash(&mut hasher);
1431
1432 Ok(format!("{:x}", hasher.finish()))
1433 }
1434
1435 /// Get cached validation result
1436 fn get_cached_result(&self, cachekey: &str) -> Result<Option<ValidationResult>, CoreError> {
1437 let cache = self.cache.read().map_err(|_| {
1438 CoreError::ComputationError(ErrorContext::new(
1439 "Failed to acquire cache read lock".to_string(),
1440 ))
1441 })?;
1442
1443 if let Some(entry) = cache.get(cachekey) {
1444 // Check if cache entry is still valid (for now, always valid)
1445 return Ok(Some(entry.result.clone()));
1446 }
1447
1448 Ok(None)
1449 }
1450
1451 /// Cache validation result
1452 fn cache_result(&self, cachekey: &str, result: ValidationResult) -> Result<(), CoreError> {
1453 let mut cache = self.cache.write().map_err(|_| {
1454 CoreError::ComputationError(ErrorContext::new(
1455 "Failed to acquire cache write lock".to_string(),
1456 ))
1457 })?;
1458
1459 // Remove oldest entries if cache is full
1460 if cache.len() >= self.config.cache_size_limit {
1461 if let Some((oldest_key, _)) = cache
1462 .iter()
1463 .min_by_key(|(_, entry)| entry.timestamp)
1464 .map(|(k, v)| (k.clone(), v.clone()))
1465 {
1466 cache.remove(&oldest_key);
1467 }
1468 }
1469
1470 let entry = CacheEntry {
1471 result,
1472 timestamp: Instant::now(),
1473 hit_count: 0,
1474 };
1475
1476 cache.insert(cachekey.to_string(), entry);
1477 Ok(())
1478 }
1479
1480 /// Calculate cache hit rate
1481 fn calculate_cache_hit_rate(&self) -> Result<f64, CoreError> {
1482 let cache = self.cache.read().map_err(|_| {
1483 CoreError::ComputationError(ErrorContext::new(
1484 "Failed to acquire cache read lock".to_string(),
1485 ))
1486 })?;
1487
1488 if cache.is_empty() {
1489 return Ok(0.0);
1490 }
1491
1492 let total_hits: usize = cache.values().map(|entry| entry.hit_count).sum();
1493 let total_entries = cache.len();
1494
1495 Ok(total_hits as f64 / total_entries as f64)
1496 }
1497}
1498
1499impl Default for Validator {
1500 fn default() -> Self {
1501 Self::new(ValidationConfig::default()).expect("Operation failed")
1502 }
1503}
1504
1505#[cfg(test)]
1506#[path = "validator_tests.rs"]
1507mod tests;