Skip to main content

scirs2_core/validation/
production.rs

1//! # Production-Level Input Validation and Sanitization
2//!
3//! This module provides comprehensive validation for all public APIs with security hardening,
4//! performance optimization, and robustness guarantees for production environments.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use std::collections::HashMap;
8use std::fmt;
9use std::time::{Duration, Instant};
10
11#[cfg(feature = "regex")]
12use regex;
13
14/// Maximum number of dimensions allowed for arrays
15const MAX_DIMENSIONS: usize = 5;
16
17/// Validation severity levels for different environments
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub enum ValidationLevel {
20    /// Minimal validation - development only
21    Development,
22    /// Standard validation - testing environment
23    Testing,
24    /// Strict validation - staging environment
25    Staging,
26    /// Maximum validation - production environment
27    Production,
28}
29
30/// Validation context with environment and performance tracking
31#[derive(Debug, Clone)]
32pub struct ValidationContext {
33    /// Current validation level
34    pub level: ValidationLevel,
35    /// Maximum validation time allowed
36    pub timeout: Duration,
37    /// Whether to collect validation metrics
38    pub collect_metrics: bool,
39    /// Custom validation rules
40    pub custom_rules: HashMap<String, String>,
41}
42
43impl Default for ValidationContext {
44    fn default() -> Self {
45        Self {
46            level: ValidationLevel::Production,
47            timeout: Duration::from_millis(100), // 100ms timeout for validation
48            collect_metrics: true,
49            custom_rules: HashMap::new(),
50        }
51    }
52}
53
54/// Validation result with detailed information
55#[derive(Debug, Clone)]
56pub struct ValidationResult {
57    /// Whether validation passed
58    pub is_valid: bool,
59    /// Validation errors if any
60    pub errors: Vec<ValidationError>,
61    /// Validation warnings
62    pub warnings: Vec<String>,
63    /// Performance metrics
64    pub metrics: ValidationMetrics,
65}
66
67/// Detailed validation error information
68#[derive(Debug, Clone)]
69pub struct ValidationError {
70    /// Error code for programmatic handling
71    pub code: String,
72    /// Human-readable error message
73    pub message: String,
74    /// Field or parameter that failed validation
75    pub field: Option<String>,
76    /// Suggested fix or workaround
77    pub suggestion: Option<String>,
78    /// Severity level
79    pub severity: ValidationSeverity,
80}
81
82/// Validation error severity
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
84pub enum ValidationSeverity {
85    /// Information only
86    Info,
87    /// Warning - operation can continue
88    Warning,
89    /// Error - operation should not continue
90    Error,
91    /// Critical - system integrity at risk
92    Critical,
93}
94
95/// Validation performance metrics
96#[derive(Debug, Clone)]
97pub struct ValidationMetrics {
98    /// Time taken for validation
99    pub duration: Duration,
100    /// Number of rules checked
101    pub rules_checked: usize,
102    /// Number of values validated
103    pub values_validated: usize,
104    /// Memory used during validation
105    pub memory_used: usize,
106    /// Whether timeout was hit
107    pub timed_out: bool,
108}
109
110impl Default for ValidationMetrics {
111    fn default() -> Self {
112        Self {
113            duration: Duration::ZERO,
114            rules_checked: 0,
115            values_validated: 0,
116            memory_used: 0,
117            timed_out: false,
118        }
119    }
120}
121
122/// Production-level validator with comprehensive security and performance features
123pub struct ProductionValidator {
124    /// Validation context
125    context: ValidationContext,
126    /// Validation cache for performance
127    cache: HashMap<String, ValidationResult>,
128    /// Metrics collector
129    metrics_collector: Option<Box<dyn ValidationMetricsCollector + Send + Sync>>,
130}
131
132/// Trait for collecting validation metrics
133pub trait ValidationMetricsCollector {
134    /// Record validation metrics
135    fn record_validation(&mut self, result: &ValidationResult);
136
137    /// Get aggregated metrics
138    fn get_metrics(&self) -> ValidationSummary;
139
140    /// Clear collected metrics
141    fn clear(&mut self);
142}
143
144/// Summary of validation metrics
145#[derive(Debug, Clone)]
146pub struct ValidationSummary {
147    /// Total validations performed
148    pub total_validations: usize,
149    /// Total validation time
150    pub total_duration: Duration,
151    /// Average validation time
152    pub average_duration: Duration,
153    /// Success rate (0.0 to 1.0)
154    pub success_rate: f64,
155    /// Most common error codes
156    pub commonerrors: HashMap<String, usize>,
157}
158
159impl ProductionValidator {
160    /// Create a new production validator with default settings
161    pub fn new() -> Self {
162        Self {
163            context: ValidationContext::default(),
164            cache: HashMap::new(),
165            metrics_collector: None,
166        }
167    }
168
169    /// Create a validator with custom context
170    pub fn with_context(context: ValidationContext) -> Self {
171        Self {
172            context,
173            cache: HashMap::new(),
174            metrics_collector: None,
175        }
176    }
177
178    /// Set metrics collector
179    pub fn with_metrics_collector(
180        mut self,
181        collector: Box<dyn ValidationMetricsCollector + Send + Sync>,
182    ) -> Self {
183        self.metrics_collector = Some(collector);
184        self
185    }
186
187    /// Validate numeric value with comprehensive checks
188    pub fn validate_numeric<T>(
189        &mut self,
190        value: T,
191        constraints: &NumericConstraints<T>,
192    ) -> ValidationResult
193    where
194        T: PartialOrd + Copy + fmt::Debug + fmt::Display,
195    {
196        let start_time = Instant::now();
197        let mut result = ValidationResult {
198            is_valid: true,
199            errors: Vec::new(),
200            warnings: Vec::new(),
201            metrics: ValidationMetrics::default(),
202        };
203
204        // Check timeout
205        if start_time.elapsed() > self.context.timeout {
206            result.metrics.timed_out = true;
207            result.is_valid = false;
208            result.errors.push(ValidationError {
209                code: "VALIDATION_TIMEOUT".to_string(),
210                message: "Validation timed out".to_string(),
211                field: None,
212                suggestion: Some("Increase validation timeout or simplify constraints".to_string()),
213                severity: ValidationSeverity::Error,
214            });
215            return result;
216        }
217
218        let mut rules_checked = 0;
219
220        // Range validation
221        if let Some(min) = constraints.min {
222            rules_checked += 1;
223            if value < min {
224                result.is_valid = false;
225                result.errors.push(ValidationError {
226                    code: "VALUE_TOO_SMALL".to_string(),
227                    message: format!("Value {value} is below minimum {min}"),
228                    field: constraints.fieldname.clone(),
229                    suggestion: Some(format!("{min}")),
230                    severity: ValidationSeverity::Error,
231                });
232            }
233        }
234
235        if let Some(max) = constraints.max {
236            rules_checked += 1;
237            if value > max {
238                result.is_valid = false;
239                result.errors.push(ValidationError {
240                    code: "VALUE_TOO_LARGE".to_string(),
241                    message: format!("Value {value} exceeds maximum {max}"),
242                    field: constraints.fieldname.clone(),
243                    suggestion: Some(format!("{max}")),
244                    severity: ValidationSeverity::Error,
245                });
246            }
247        }
248
249        // Custom validation rules
250        for rule in &constraints.custom_rules {
251            rules_checked += 1;
252            if let Err(error) = rule.validate(value) {
253                match constraints.allow_custom_rule_failures {
254                    true => result.warnings.push(error),
255                    false => {
256                        result.is_valid = false;
257                        result.errors.push(ValidationError {
258                            code: "CUSTOM_RULE_FAILED".to_string(),
259                            message: error,
260                            field: constraints.fieldname.clone(),
261                            suggestion: Some("Check custom validation rules".to_string()),
262                            severity: ValidationSeverity::Error,
263                        });
264                    }
265                }
266            }
267        }
268
269        // Performance warnings
270        if rules_checked > 10 {
271            result
272                .warnings
273                .push("High number of validation rules may impact performance".to_string());
274        }
275
276        result.metrics = ValidationMetrics {
277            duration: start_time.elapsed(),
278            rules_checked,
279            values_validated: 1,
280            memory_used: std::mem::size_of::<T>(),
281            timed_out: false,
282        };
283
284        // Record metrics
285        if let Some(collector) = &mut self.metrics_collector {
286            collector.record_validation(&result);
287        }
288
289        result
290    }
291
292    /// Validate array/collection with size and content constraints
293    pub fn validate_collection<T, I>(
294        &mut self,
295        collection: I,
296        constraints: &CollectionConstraints<T>,
297    ) -> ValidationResult
298    where
299        T: fmt::Debug,
300        I: IntoIterator<Item = T>,
301        I::IntoIter: ExactSizeIterator,
302    {
303        let start_time = Instant::now();
304        let mut result = ValidationResult {
305            is_valid: true,
306            errors: Vec::new(),
307            warnings: Vec::new(),
308            metrics: ValidationMetrics::default(),
309        };
310
311        let iter = collection.into_iter();
312        let size = iter.len();
313        let mut rules_checked = 0;
314        let mut values_validated = 0;
315
316        // Size validation
317        if let Some(minsize) = constraints.minsize {
318            rules_checked += 1;
319            if size < minsize {
320                result.is_valid = false;
321                result.errors.push(ValidationError {
322                    code: "COLLECTION_TOO_SMALL".to_string(),
323                    message: format!("Size {size} is below minimum {minsize}"),
324                    field: constraints.fieldname.clone(),
325                    suggestion: Some(format!("Provide at least {minsize} elements")),
326                    severity: ValidationSeverity::Error,
327                });
328            }
329        }
330
331        if let Some(maxsize) = constraints.maxsize {
332            rules_checked += 1;
333            if size > maxsize {
334                result.is_valid = false;
335                result.errors.push(ValidationError {
336                    code: "COLLECTION_TOO_LARGE".to_string(),
337                    message: format!("Size {size} exceeds maximum {maxsize}"),
338                    field: constraints.fieldname.clone(),
339                    suggestion: Some(format!("Limit collection to {maxsize} elements")),
340                    severity: ValidationSeverity::Error,
341                });
342            }
343        }
344
345        // Content validation (with timeout protection)
346        if let Some(validator) = &constraints.element_validator {
347            for (index, element) in iter.enumerate() {
348                // Check timeout
349                if start_time.elapsed() > self.context.timeout {
350                    result.metrics.timed_out = true;
351                    result.warnings.push(format!(
352                        "Validation timed out after checking {index} elements"
353                    ));
354                    break;
355                }
356
357                values_validated += 1;
358                rules_checked += 1;
359
360                if let Err(error) = validator.validate(&element) {
361                    result.is_valid = false;
362                    result.errors.push(ValidationError {
363                        code: "ELEMENT_VALIDATION_FAILED".to_string(),
364                        message: format!("Index {index}: {error}"),
365                        field: constraints.fieldname.clone(),
366                        suggestion: Some("Check element constraints".to_string()),
367                        severity: ValidationSeverity::Error,
368                    });
369                }
370            }
371        }
372
373        result.metrics = ValidationMetrics {
374            duration: start_time.elapsed(),
375            rules_checked,
376            values_validated,
377            memory_used: size * std::mem::size_of::<T>(),
378            timed_out: start_time.elapsed() > self.context.timeout,
379        };
380
381        // Record metrics
382        if let Some(collector) = &mut self.metrics_collector {
383            collector.record_validation(&result);
384        }
385
386        result
387    }
388
389    /// Validate string with comprehensive security checks
390    pub fn validate_string(
391        &mut self,
392        value: &str,
393        constraints: &StringConstraints,
394    ) -> ValidationResult {
395        let start_time = Instant::now();
396        let mut result = ValidationResult {
397            is_valid: true,
398            errors: Vec::new(),
399            warnings: Vec::new(),
400            metrics: ValidationMetrics::default(),
401        };
402
403        let mut rules_checked = 0;
404
405        // Length validation
406        if let Some(min_length) = constraints.min_length {
407            rules_checked += 1;
408            if value.len() < min_length {
409                result.is_valid = false;
410                result.errors.push(ValidationError {
411                    code: "STRING_TOO_SHORT".to_string(),
412                    message: format!(
413                        "String length {} is less than minimum {}",
414                        value.len(),
415                        min_length
416                    ),
417                    field: constraints.fieldname.clone(),
418                    suggestion: Some(format!("Provide at least {min_length} characters")),
419                    severity: ValidationSeverity::Error,
420                });
421            }
422        }
423
424        if let Some(max_length) = constraints.max_length {
425            rules_checked += 1;
426            if value.len() > max_length {
427                result.is_valid = false;
428                result.errors.push(ValidationError {
429                    code: "STRING_TOO_LONG".to_string(),
430                    message: format!(
431                        "String length {} exceeds maximum {}",
432                        value.len(),
433                        max_length
434                    ),
435                    field: constraints.fieldname.clone(),
436                    suggestion: Some(format!("Limit string to {max_length} characters")),
437                    severity: ValidationSeverity::Error,
438                });
439            }
440        }
441
442        // Security validation
443        if constraints.check_injection_attacks {
444            rules_checked += 1;
445            if self.contains_injection_patterns(value) {
446                result.is_valid = false;
447                result.errors.push(ValidationError {
448                    code: "POTENTIAL_INJECTION_ATTACK".to_string(),
449                    message: "String contains potential injection attack patterns".to_string(),
450                    field: constraints.fieldname.clone(),
451                    suggestion: Some("Sanitize input or use parameterized queries".to_string()),
452                    severity: ValidationSeverity::Critical,
453                });
454            }
455        }
456
457        // Character set validation
458        if let Some(allowed_chars) = &constraints.allowed_characters {
459            rules_checked += 1;
460            for ch in value.chars() {
461                if !allowed_chars.contains(&ch) {
462                    result.is_valid = false;
463                    result.errors.push(ValidationError {
464                        code: "INVALID_CHARACTER".to_string(),
465                        message: format!("Character '{ch}' is not allowed"),
466                        field: constraints.fieldname.clone(),
467                        suggestion: Some("Use only allowed characters".to_string()),
468                        severity: ValidationSeverity::Error,
469                    });
470                    break; // Only report first invalid character
471                }
472            }
473        }
474
475        // Pattern validation
476        #[cfg(feature = "regex")]
477        if let Some(pattern) = &constraints.pattern {
478            rules_checked += 1;
479            if !pattern.is_match(value) {
480                result.is_valid = false;
481                result.errors.push(ValidationError {
482                    code: "PATTERN_MISMATCH".to_string(),
483                    message: "String does not match required pattern".to_string(),
484                    field: constraints.fieldname.clone(),
485                    suggestion: Some("Check string format requirements".to_string()),
486                    severity: ValidationSeverity::Error,
487                });
488            }
489        }
490
491        result.metrics = ValidationMetrics {
492            duration: start_time.elapsed(),
493            rules_checked,
494            values_validated: 1,
495            memory_used: value.len(),
496            timed_out: false,
497        };
498
499        // Record metrics
500        if let Some(collector) = &mut self.metrics_collector {
501            collector.record_validation(&result);
502        }
503
504        result
505    }
506
507    /// Check for common injection attack patterns
508    fn contains_injection_patterns(&self, value: &str) -> bool {
509        let dangerous_patterns = [
510            // SQL injection patterns
511            "'; DROP TABLE",
512            "' OR '1'='1",
513            "UNION SELECT",
514            "'; --",
515            // Script injection patterns
516            "<script",
517            "javascript:",
518            "onload=",
519            "onerror=",
520            // Command injection patterns
521            "; rm -rf",
522            "| rm -rf",
523            "&& rm -rf",
524            "$(rm -rf)",
525            // Path traversal patterns
526            "../",
527            "..\\",
528            "%2e%2e%2f",
529            "%2e%2e\\",
530        ];
531
532        let value_lower = value.to_lowercase();
533        dangerous_patterns
534            .iter()
535            .any(|pattern| value_lower.contains(&pattern.to_lowercase()))
536    }
537
538    /// Clear validation cache
539    pub fn clear_cache(&mut self) {
540        self.cache.clear();
541    }
542
543    /// Get validation statistics
544    pub fn get_metrics(&self) -> Option<ValidationSummary> {
545        self.metrics_collector
546            .as_ref()
547            .map(|collector| collector.get_metrics())
548    }
549}
550
551impl Default for ProductionValidator {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557/// Constraints for numeric validation
558#[derive(Debug)]
559pub struct NumericConstraints<T> {
560    /// Minimum allowed value
561    pub min: Option<T>,
562    /// Maximum allowed value
563    pub max: Option<T>,
564    /// Field name for error reporting
565    pub fieldname: Option<String>,
566    /// Custom validation rules
567    pub custom_rules: Vec<Box<dyn NumericRule<T>>>,
568    /// Whether to allow custom rule failures as warnings
569    pub allow_custom_rule_failures: bool,
570}
571
572impl<T: Clone> Clone for NumericConstraints<T> {
573    fn clone(&self) -> Self {
574        Self {
575            min: self.min.clone(),
576            max: self.max.clone(),
577            fieldname: self.fieldname.clone(),
578            custom_rules: Vec::new(), // Cannot clone trait objects, start with empty vec
579            allow_custom_rule_failures: self.allow_custom_rule_failures,
580        }
581    }
582}
583
584/// Trait for custom numeric validation rules
585pub trait NumericRule<T>: std::fmt::Debug {
586    /// Validate the value
587    fn validate(&self, value: T) -> Result<(), String>;
588}
589
590/// Constraints for collection validation
591#[derive(Debug)]
592pub struct CollectionConstraints<T> {
593    /// Minimum collection size
594    pub minsize: Option<usize>,
595    /// Maximum collection size
596    pub maxsize: Option<usize>,
597    /// Element validator
598    pub element_validator: Option<Box<dyn ElementValidator<T>>>,
599    /// Field name for error reporting
600    pub fieldname: Option<String>,
601}
602
603/// Trait for validating collection elements
604pub trait ElementValidator<T>: std::fmt::Debug {
605    /// Validate an element
606    fn validate(&self, element: &T) -> Result<(), String>;
607}
608
609/// Constraints for string validation
610#[derive(Debug)]
611pub struct StringConstraints {
612    /// Minimum string length
613    pub min_length: Option<usize>,
614    /// Maximum string length
615    pub max_length: Option<usize>,
616    /// Allowed character set
617    pub allowed_characters: Option<std::collections::HashSet<char>>,
618    /// Regular expression pattern
619    #[cfg(feature = "regex")]
620    pub pattern: Option<regex::Regex>,
621    /// Whether to check for injection attacks
622    pub check_injection_attacks: bool,
623    /// Field name for error reporting
624    pub fieldname: Option<String>,
625}
626
627/// Default metrics collector implementation
628pub struct DefaultMetricsCollector {
629    /// Collected validation results
630    results: Vec<ValidationResult>,
631}
632
633impl DefaultMetricsCollector {
634    /// Create a new metrics collector
635    pub fn new() -> Self {
636        Self {
637            results: Vec::new(),
638        }
639    }
640}
641
642impl ValidationMetricsCollector for DefaultMetricsCollector {
643    fn record_validation(&mut self, result: &ValidationResult) {
644        self.results.push(result.clone());
645    }
646
647    fn get_metrics(&self) -> ValidationSummary {
648        let total_validations = self.results.len();
649
650        if total_validations == 0 {
651            return ValidationSummary {
652                total_validations: 0,
653                total_duration: Duration::ZERO,
654                average_duration: Duration::ZERO,
655                success_rate: 0.0,
656                commonerrors: HashMap::new(),
657            };
658        }
659
660        let total_duration: Duration = self.results.iter().map(|r| r.metrics.duration).sum();
661
662        let average_duration = total_duration / total_validations as u32;
663
664        let successful_validations = self.results.iter().filter(|r| r.is_valid).count();
665
666        let success_rate = successful_validations as f64 / total_validations as f64;
667
668        let mut commonerrors = HashMap::new();
669        for result in &self.results {
670            for error in &result.errors {
671                *commonerrors.entry(error.code.clone()).or_insert(0) += 1;
672            }
673        }
674
675        ValidationSummary {
676            total_validations,
677            total_duration,
678            average_duration,
679            success_rate,
680            commonerrors,
681        }
682    }
683
684    fn clear(&mut self) {
685        self.results.clear();
686    }
687}
688
689impl Default for DefaultMetricsCollector {
690    fn default() -> Self {
691        Self::new()
692    }
693}
694
695/// Convenience functions for common validation scenarios
696/// Validate a positive number
697#[allow(dead_code)]
698pub fn validate_positive<T>(value: T, fieldname: Option<String>) -> CoreResult<T>
699where
700    T: PartialOrd + Copy + fmt::Debug + fmt::Display + Default,
701{
702    let mut validator = ProductionValidator::new();
703    let constraints = NumericConstraints {
704        min: Some(T::default()), // Zero or equivalent
705        max: None,
706        fieldname,
707        custom_rules: Vec::new(),
708        allow_custom_rule_failures: false,
709    };
710
711    let result = validator.validate_numeric(value, &constraints);
712    if result.is_valid {
713        Ok(value)
714    } else {
715        Err(CoreError::ValidationError(ErrorContext::new(format!(
716            "Validation failed: {:?}",
717            result.errors
718        ))))
719    }
720}
721
722/// Validate array dimensions
723#[allow(dead_code)]
724pub fn validate_dimensions(dims: &[usize]) -> CoreResult<()> {
725    if dims.is_empty() {
726        return Err(CoreError::ValidationError(ErrorContext::new(
727            "Array must have at least one dimension",
728        )));
729    }
730
731    if dims.len() > MAX_DIMENSIONS {
732        return Err(CoreError::ValidationError(ErrorContext::new(format!(
733            "Array has {} dimensions, maximum allowed is {}",
734            dims.len(),
735            MAX_DIMENSIONS
736        ))));
737    }
738
739    for (i, &dim) in dims.iter().enumerate() {
740        if dim == 0 {
741            return Err(CoreError::ValidationError(ErrorContext::new(format!(
742                "Dimension {i} has size 0, which is not allowed"
743            ))));
744        }
745
746        // Prevent integer overflow in total size calculation
747        if dim > usize::MAX / 1024 {
748            return Err(CoreError::ValidationError(ErrorContext::new(format!(
749                "Dimension {i} size {dim} is too large"
750            ))));
751        }
752    }
753
754    // Check total size doesn't overflow
755    let total_size = dims.iter().try_fold(1usize, |acc, &dim| {
756        acc.checked_mul(dim).ok_or_else(|| {
757            CoreError::ValidationError(ErrorContext::new("Array total size would overflow"))
758        })
759    })?;
760
761    // Reasonable maximum total size (1GB of f64 values)
762    const MAX_TOTAL_SIZE: usize = 1024 * 1024 * 1024 / 8;
763    if total_size > MAX_TOTAL_SIZE {
764        return Err(CoreError::ValidationError(ErrorContext::new(format!(
765            "Array total size {total_size} exceeds maximum allowed size {MAX_TOTAL_SIZE}"
766        ))));
767    }
768
769    Ok(())
770}
771
772/// Validate file path for security
773#[allow(dead_code)]
774pub fn validate_file_path(path: &str) -> CoreResult<()> {
775    // Check for path traversal attempts
776    if path.contains("..") {
777        return Err(CoreError::ValidationError(ErrorContext::new(
778            "Path traversal detected in file path",
779        )));
780    }
781
782    // Check for null bytes
783    if path.contains('\0') {
784        return Err(CoreError::ValidationError(ErrorContext::new(
785            "Null byte detected in file path",
786        )));
787    }
788
789    // Check length
790    if path.len() > 4096 {
791        return Err(CoreError::ValidationError(ErrorContext::new(
792            "File path too long",
793        )));
794    }
795
796    // Check for dangerous patterns
797    let dangerous_patterns = ["/dev/", "/proc/", "/sys/", "//", "\\\\"];
798    for pattern in &dangerous_patterns {
799        if path.contains(pattern) {
800            return Err(CoreError::ValidationError(ErrorContext::new(format!(
801                "Dangerous pattern '{pattern}' detected in file path"
802            ))));
803        }
804    }
805
806    Ok(())
807}
808
809#[cfg(test)]
810mod tests {
811    use super::*;
812    use std::collections::HashSet;
813
814    #[test]
815    fn test_numeric_validation() {
816        let mut validator = ProductionValidator::new();
817        let constraints = NumericConstraints {
818            min: Some(0.0),
819            max: Some(100.0),
820            fieldname: Some("test_value".to_string()),
821            custom_rules: Vec::new(),
822            allow_custom_rule_failures: false,
823        };
824
825        // Valid value
826        let result = validator.validate_numeric(50.0, &constraints);
827        assert!(result.is_valid);
828        assert!(result.errors.is_empty());
829
830        // Too small
831        let result = validator.validate_numeric(-10.0, &constraints);
832        assert!(!result.is_valid);
833        assert!(result.errors.iter().any(|e| e.code == "VALUE_TOO_SMALL"));
834
835        // Too large
836        let result = validator.validate_numeric(150.0, &constraints);
837        assert!(!result.is_valid);
838        assert!(result.errors.iter().any(|e| e.code == "VALUE_TOO_LARGE"));
839    }
840
841    #[test]
842    fn test_string_validation() {
843        let mut validator = ProductionValidator::new();
844        let mut allowed_chars = HashSet::new();
845        allowed_chars.extend("abcdefghijklmnopqrstuvwxyz0123456789".chars());
846
847        let constraints = StringConstraints {
848            min_length: Some(3),
849            max_length: Some(20),
850            allowed_characters: Some(allowed_chars),
851            #[cfg(feature = "regex")]
852            pattern: None,
853            check_injection_attacks: true,
854            fieldname: Some("username".to_string()),
855        };
856
857        // Valid string
858        let result = validator.validate_string("validuser123", &constraints);
859        assert!(result.is_valid);
860
861        // Too short
862        let result = validator.validate_string("ab", &constraints);
863        assert!(!result.is_valid);
864
865        // Invalid character
866        let result = validator.validate_string("user@name", &constraints);
867        assert!(!result.is_valid);
868
869        // Injection attack
870        let result = validator.validate_string("'; DROP TABLE users; --", &constraints);
871        assert!(!result.is_valid);
872        assert!(result
873            .errors
874            .iter()
875            .any(|e| e.code == "POTENTIAL_INJECTION_ATTACK"));
876    }
877
878    #[test]
879    fn test_array_dimensions_validation() {
880        // Valid dimensions
881        assert!(validate_dimensions(&[10, 20, 30]).is_ok());
882
883        // Empty dimensions
884        assert!(validate_dimensions(&[]).is_err());
885
886        // Zero dimension
887        assert!(validate_dimensions(&[10, 0, 30]).is_err());
888
889        // Too many dimensions
890        assert!(validate_dimensions(&[1, 2, 3, 4, 5, 6]).is_err());
891    }
892
893    #[test]
894    fn test_file_path_validation() {
895        // Valid path
896        assert!(validate_file_path("/home/user/data.txt").is_ok());
897
898        // Path traversal
899        assert!(validate_file_path("../../../etc/passwd").is_err());
900
901        // Null byte
902        assert!(validate_file_path("/home/user\0/data.txt").is_err());
903
904        // Dangerous pattern
905        assert!(validate_file_path("/dev/null").is_err());
906    }
907
908    #[test]
909    fn test_metrics_collection() {
910        let mut validator = ProductionValidator::new()
911            .with_metrics_collector(Box::new(DefaultMetricsCollector::new()));
912
913        let constraints = NumericConstraints {
914            min: Some(0),
915            max: Some(100),
916            fieldname: None,
917            custom_rules: Vec::new(),
918            allow_custom_rule_failures: false,
919        };
920
921        // Perform several validations
922        validator.validate_numeric(50, &constraints);
923        validator.validate_numeric(150, &constraints); // This will fail
924        validator.validate_numeric(25, &constraints);
925
926        let metrics = validator.get_metrics().expect("Operation failed");
927        assert_eq!(metrics.total_validations, 3);
928        assert_eq!(metrics.success_rate, 2.0 / 3.0);
929        assert!(metrics.commonerrors.contains_key("VALUE_TOO_LARGE"));
930    }
931}