memscope_rs/export/
schema_validator.rs

1//! Schema validator for unsafe/FFI analysis data
2//!
3//! This module provides a schema validator for unsafe/FFI analysis data.
4//! It validates JSON data against the unsafe/FFI analysis schema.
5
6use crate::core::types::{TrackingError, TrackingResult};
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9use std::collections::HashMap;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12/// Validation result containing errors, warnings, and metrics
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ValidationResult {
15    /// Whether validation passed
16    pub is_valid: bool,
17    /// List of validation errors
18    pub errors: Vec<ValidationError>,
19    /// List of validation warnings
20    pub warnings: Vec<ValidationWarning>,
21    /// Validation metrics
22    pub validation_metrics: ValidationMetrics,
23    /// Data integrity hash
24    pub integrity_hash: String,
25    /// Validation timestamp
26    pub validation_timestamp: u128,
27}
28
29/// Validation error details
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ValidationError {
32    /// Error code
33    pub code: String,
34    /// Human-readable error message
35    pub message: String,
36    /// JSON path where error occurred
37    pub path: String,
38    /// Error severity level
39    pub severity: ErrorSeverity,
40    /// Suggested fix for the error
41    pub suggested_fix: Option<String>,
42}
43
44/// Validation warning details
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ValidationWarning {
47    /// Warning code
48    pub warning_code: String,
49    /// Human-readable warning message
50    pub message: String,
51    /// JSON path where warning occurred
52    pub path: String,
53    /// Suggested action
54    pub suggestion: Option<String>,
55}
56
57/// Error severity levels
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub enum ErrorSeverity {
60    /// Critical - validation fails
61    Critical,
62    /// Warning - validation passes with warnings
63    Warning,
64    /// Info - informational messages
65    Info,
66}
67
68/// Schema version information
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct SchemaVersion {
71    /// Version string (e.g., "2.0")
72    pub version: String,
73    /// Version components (major, minor, patch)
74    pub components: (u32, u32, u32),
75    /// Whether this version is supported
76    pub is_supported: bool,
77    /// Compatibility level with current version
78    pub compatibility: CompatibilityLevel,
79    /// Backward compatibility information
80    pub backward_compatible_with: Vec<String>,
81    /// Forward compatibility information
82    pub forward_compatible_with: Vec<String>,
83}
84
85/// Schema compatibility levels
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub enum CompatibilityLevel {
88    /// Fully compatible
89    FullyCompatible,
90    /// Backward compatible
91    BackwardCompatible,
92    /// Forward compatible
93    ForwardCompatible,
94    /// Incompatible
95    Incompatible,
96}
97
98/// Configuration for schema validation
99#[derive(Debug, Clone)]
100pub struct SchemaValidatorConfig {
101    /// Strict validation mode (fail on warnings)
102    pub strict_mode: bool,
103    /// Enable data integrity checking
104    pub enable_integrity_check: bool,
105    /// Enable backward compatibility checking
106    pub enable_backward_compatibility: bool,
107    /// Maximum allowed schema version
108    pub max_schema_version: String,
109    /// Custom validation rules
110    pub custom_rules: Vec<CustomValidationRule>,
111}
112
113/// Custom validation rule
114#[derive(Debug, Clone)]
115pub struct CustomValidationRule {
116    /// Rule name
117    pub name: String,
118    /// JSONPath pattern to match
119    pub path_pattern: String,
120    /// Validation function
121    pub validator: fn(&Value) -> Result<(), String>,
122}
123
124impl Default for SchemaValidatorConfig {
125    fn default() -> Self {
126        Self {
127            strict_mode: false,
128            enable_integrity_check: true,
129            enable_backward_compatibility: true,
130            max_schema_version: "2.0".to_string(),
131            custom_rules: Vec::new(),
132        }
133    }
134}
135
136/// JSON Schema validator for unsafe/FFI analysis data
137pub struct SchemaValidator {
138    /// Validation configuration
139    config: SchemaValidatorConfig,
140    /// Supported schema versions
141    supported_versions: HashMap<String, SchemaVersion>,
142    /// Schema definitions
143    schema_definitions: HashMap<String, Value>,
144}
145
146impl SchemaValidator {
147    /// Create a new schema validator with default configuration
148    pub fn new() -> Self {
149        Self::with_config(SchemaValidatorConfig::default())
150    }
151
152    /// Create a new schema validator with custom configuration
153    pub fn with_config(config: SchemaValidatorConfig) -> Self {
154        let mut validator = Self {
155            config,
156            supported_versions: HashMap::new(),
157            schema_definitions: HashMap::new(),
158        };
159
160        validator.initialize_schemas();
161        validator
162    }
163
164    /// Validate JSON data against the unsafe/FFI analysis schema
165    pub fn validate_unsafe_ffi_analysis(&self, data: &Value) -> TrackingResult<ValidationResult> {
166        let mut errors = Vec::new();
167        let mut warnings = Vec::new();
168        let mut validation_metrics = ValidationMetrics::default();
169
170        // Extract and validate metadata
171        let metadata = self.extract_metadata(data, &mut errors)?;
172
173        let schema_version = metadata
174            .get("schema_version")
175            .and_then(|v| v.as_str())
176            .unwrap_or("1.0");
177
178        // Validate schema version
179        self.validate_schema_version(schema_version, &mut errors, &mut warnings)?;
180
181        // Validate main document structure
182        self.validate_main_structure(data, &mut errors, &mut warnings, &mut validation_metrics)?;
183
184        // Validate unsafe analysis section
185        if let Some(unsafe_analysis) = data.get("unsafe_analysis") {
186            self.validate_unsafe_analysis(unsafe_analysis, &mut errors, &mut warnings)?;
187        }
188
189        // Validate FFI analysis section
190        if let Some(ffi_analysis) = data.get("ffi_analysis") {
191            self.validate_ffi_analysis(ffi_analysis, &mut errors, &mut warnings)?;
192        }
193
194        // Validate boundary analysis section
195        if let Some(boundary_analysis) = data.get("boundary_analysis") {
196            self.validate_boundary_analysis(boundary_analysis, &mut errors, &mut warnings)?;
197        }
198
199        // Validate safety violations section
200        if let Some(safety_violations) = data.get("safety_violations") {
201            self.validate_safety_violations(safety_violations, &mut errors, &mut warnings)?;
202        }
203
204        // Apply custom validation rules
205        self.apply_custom_rules(data, &mut errors, &mut warnings)?;
206
207        // Calculate data integrity hash
208        let integrity_hash = if self.config.enable_integrity_check {
209            self.calculate_integrity_hash(data)?
210        } else {
211            "disabled".to_string()
212        };
213
214        // Determine if validation passed
215        let is_valid = errors.is_empty() && (!self.config.strict_mode || warnings.is_empty());
216
217        Ok(ValidationResult {
218            is_valid,
219            errors,
220            warnings,
221            validation_metrics,
222            integrity_hash,
223            validation_timestamp: SystemTime::now()
224                .duration_since(UNIX_EPOCH)
225                .unwrap_or_default()
226                .as_nanos(),
227        })
228    }
229
230    /// Extract metadata from JSON data
231    fn extract_metadata<'a>(
232        &self,
233        data: &'a Value,
234        errors: &mut Vec<ValidationError>,
235    ) -> TrackingResult<&'a Map<String, Value>> {
236        match data.get("metadata") {
237            Some(Value::Object(metadata)) => Ok(metadata),
238            Some(_) => {
239                errors.push(ValidationError {
240                    code: "INVALID_METADATA_TYPE".to_string(),
241                    message: "Metadata must be an object".to_string(),
242                    path: "metadata".to_string(),
243                    severity: ErrorSeverity::Critical,
244                    suggested_fix: Some("Ensure metadata is a JSON object".to_string()),
245                });
246                Err(TrackingError::ValidationError(
247                    "Invalid metadata type".to_string(),
248                ))
249            }
250            None => {
251                errors.push(ValidationError {
252                    code: "MISSING_METADATA".to_string(),
253                    message: "Required metadata section is missing".to_string(),
254                    path: "metadata".to_string(),
255                    severity: ErrorSeverity::Critical,
256                    suggested_fix: Some("Add metadata section with required fields".to_string()),
257                });
258                Err(TrackingError::ValidationError(
259                    "Missing metadata".to_string(),
260                ))
261            }
262        }
263    }
264
265    /// Validate schema version
266    fn validate_schema_version(
267        &self,
268        version: &str,
269        errors: &mut Vec<ValidationError>,
270        warnings: &mut Vec<ValidationWarning>,
271    ) -> TrackingResult<()> {
272        if let Some(schema_version) = self.supported_versions.get(version) {
273            if !schema_version.is_supported {
274                errors.push(ValidationError {
275                    code: "UNSUPPORTED_SCHEMA_VERSION".to_string(),
276                    message: format!("Schema version {version} is not supported"),
277                    path: "metadata.schema_version".to_string(),
278                    severity: ErrorSeverity::Critical,
279                    suggested_fix: Some("Use a supported schema version".to_string()),
280                });
281            }
282        } else {
283            errors.push(ValidationError {
284                code: "UNKNOWN_SCHEMA_VERSION".to_string(),
285                message: format!("Unknown schema version: {version}"),
286                path: "metadata.schema_version".to_string(),
287                severity: ErrorSeverity::Critical,
288                suggested_fix: Some("Use a supported schema version".to_string()),
289            });
290        }
291
292        // Check version compatibility
293        if self.config.enable_backward_compatibility {
294            self.check_backward_compatibility(version, warnings)?;
295        }
296
297        Ok(())
298    }
299
300    /// Calculate data integrity hash
301    fn calculate_integrity_hash(&self, data: &Value) -> TrackingResult<String> {
302        // Convert to canonical JSON string for consistent hashing
303        let canonical_json = serde_json::to_string(data)
304            .map_err(|e| TrackingError::SerializationError(e.to_string()))?;
305
306        let hash = self.simple_hash(&canonical_json);
307        Ok(format!("{hash:x}"))
308    }
309
310    /// Verify data integrity using provided hash
311    pub fn verify_integrity(&self, data: &Value, expected_hash: &str) -> TrackingResult<bool> {
312        let calculated_hash = self.calculate_integrity_hash(data)?;
313        Ok(calculated_hash == expected_hash)
314    }
315
316    /// Get supported schema versions
317    pub fn get_supported_versions(&self) -> &HashMap<String, SchemaVersion> {
318        &self.supported_versions
319    }
320
321    /// Add custom validation rule
322    pub fn add_custom_rule(&mut self, rule: CustomValidationRule) {
323        self.config.custom_rules.push(rule);
324    }
325}
326
327// Private implementation methods
328impl SchemaValidator {
329    /// Initialize schema definitions and supported versions
330    fn initialize_schemas(&mut self) {
331        // Define supported schema versions
332        self.supported_versions.insert(
333            "1.0".to_string(),
334            SchemaVersion {
335                version: "1.0".to_string(),
336                components: (1, 0, 0),
337                is_supported: true,
338                compatibility: CompatibilityLevel::BackwardCompatible,
339                backward_compatible_with: vec![],
340                forward_compatible_with: vec!["2.0".to_string()],
341            },
342        );
343
344        self.supported_versions.insert(
345            "2.0".to_string(),
346            SchemaVersion {
347                version: "2.0".to_string(),
348                components: (2, 0, 0),
349                is_supported: true,
350                compatibility: CompatibilityLevel::FullyCompatible,
351                backward_compatible_with: vec!["1.0".to_string()],
352                forward_compatible_with: vec![],
353            },
354        );
355
356        // Initialize schema definitions
357        self.initialize_v2_schema();
358    }
359
360    /// Initialize v2.0 schema definition
361    fn initialize_v2_schema(&mut self) {
362        let schema = serde_json::json!({
363            "$schema": "http://json-schema.org/draft-07/schema#",
364            "title": "Unsafe/FFI Memory Analysis Schema v2.0",
365            "type": "object",
366            "required": ["metadata", "unsafe_analysis", "ffi_analysis"],
367            "properties": {
368                "metadata": {
369                    "type": "object",
370                    "required": ["analysis_type", "schema_version", "export_timestamp"],
371                    "properties": {
372                        "analysis_type": {"const": "unsafe_ffi_analysis_optimized"},
373                        "schema_version": {"const": "2.0"},
374                        "export_timestamp": {"type": "integer", "minimum": 0},
375                        "optimization_level": {"enum": ["low", "medium", "high"]},
376                        "processing_mode": {"enum": ["sequential", "parallel", "streaming"]},
377                        "data_integrity_hash": {"type": "string"}
378                    }
379                },
380                "unsafe_analysis": {
381                    "type": "object",
382                    "required": ["summary", "allocations"],
383                    "properties": {
384                        "summary": {"$ref": "#/definitions/RiskDistribution"},
385                        "allocations": {"type": "array"},
386                        "performance_metrics": {"type": "object"}
387                    }
388                },
389                "ffi_analysis": {
390                    "type": "object",
391                    "required": ["summary", "allocations"],
392                    "properties": {
393                        "summary": {"$ref": "#/definitions/RiskDistribution"},
394                        "allocations": {"type": "array"},
395                        "performance_metrics": {"type": "object"}
396                    }
397                },
398                "boundary_analysis": {
399                    "type": "object",
400                    "properties": {
401                        "cross_boundary_transfers": {"type": "integer", "minimum": 0},
402                        "events": {"type": "array"},
403                        "performance_impact": {"type": "object"}
404                    }
405                },
406                "safety_violations": {
407                    "type": "object",
408                    "properties": {
409                        "severity_breakdown": {"type": "object"},
410                        "violations": {"type": "array"}
411                    }
412                }
413            },
414            "definitions": {
415                "RiskDistribution": {
416                    "type": "object",
417                    "properties": {
418                        "low_risk": {"type": "integer", "minimum": 0},
419                        "medium_risk": {"type": "integer", "minimum": 0},
420                        "high_risk": {"type": "integer", "minimum": 0},
421                        "critical_risk": {"type": "integer", "minimum": 0},
422                        "overall_risk_score": {"type": "number", "minimum": 0.0, "maximum": 10.0}
423                    }
424                }
425            }
426        });
427
428        self.schema_definitions.insert("2.0".to_string(), schema);
429    }
430
431    /// Validate main document structure
432    fn validate_main_structure(
433        &self,
434        data: &Value,
435        errors: &mut Vec<ValidationError>,
436        warnings: &mut Vec<ValidationWarning>,
437        metrics: &mut ValidationMetrics,
438    ) -> TrackingResult<()> {
439        // Validate that root is an object
440        if !data.is_object() {
441            errors.push(ValidationError {
442                code: "INVALID_ROOT_TYPE".to_string(),
443                message: "Root document must be a JSON object".to_string(),
444                path: "$".to_string(),
445                severity: ErrorSeverity::Critical,
446                suggested_fix: Some("Ensure root document is a JSON object".to_string()),
447            });
448            return Ok(());
449        }
450
451        metrics.sections_validated += 1;
452
453        // Check for unknown sections
454        let known_sections = [
455            "metadata",
456            "unsafe_analysis",
457            "ffi_analysis",
458            "boundary_analysis",
459            "safety_violations",
460        ];
461
462        if let Some(obj) = data.as_object() {
463            for key in obj.keys() {
464                if !known_sections.contains(&key.as_str()) {
465                    warnings.push(ValidationWarning {
466                        warning_code: "UNKNOWN_SECTION".to_string(),
467                        message: format!("Unknown section: {key}"),
468                        path: key.clone(),
469                        suggestion: Some("Remove unknown sections or update schema".to_string()),
470                    });
471                }
472            }
473        }
474
475        Ok(())
476    }
477
478    /// Validate unsafe analysis section
479    fn validate_unsafe_analysis(
480        &self,
481        unsafe_analysis: &Value,
482        errors: &mut Vec<ValidationError>,
483        _warnings: &mut [ValidationWarning],
484    ) -> TrackingResult<()> {
485        if !unsafe_analysis.is_object() {
486            errors.push(ValidationError {
487                code: "INVALID_UNSAFE_ANALYSIS_TYPE".to_string(),
488                message: "unsafe_analysis must be an object".to_string(),
489                path: "unsafe_analysis".to_string(),
490                severity: ErrorSeverity::Critical,
491                suggested_fix: Some("Ensure unsafe_analysis is a JSON object".to_string()),
492            });
493            return Ok(());
494        }
495
496        // Validate required fields
497        let required_fields = vec!["summary", "allocations"];
498        for field in required_fields {
499            if unsafe_analysis.get(field).is_none() {
500                errors.push(ValidationError {
501                    code: "MISSING_REQUIRED_FIELD".to_string(),
502                    message: format!("Required field '{field}' is missing in unsafe_analysis"),
503                    path: format!("unsafe_analysis.{field}"),
504                    severity: ErrorSeverity::Critical,
505                    suggested_fix: Some(format!("Add required field '{field}'")),
506                });
507            }
508        }
509
510        Ok(())
511    }
512
513    /// Validate FFI analysis section
514    fn validate_ffi_analysis(
515        &self,
516        ffi_analysis: &Value,
517        errors: &mut Vec<ValidationError>,
518        _warnings: &mut [ValidationWarning],
519    ) -> TrackingResult<()> {
520        if !ffi_analysis.is_object() {
521            errors.push(ValidationError {
522                code: "INVALID_FFI_ANALYSIS_TYPE".to_string(),
523                message: "ffi_analysis must be an object".to_string(),
524                path: "ffi_analysis".to_string(),
525                severity: ErrorSeverity::Critical,
526                suggested_fix: Some("Ensure ffi_analysis is a JSON object".to_string()),
527            });
528            return Ok(());
529        }
530
531        // Validate required fields
532        let required_fields = vec!["summary", "allocations"];
533        for field in required_fields {
534            if ffi_analysis.get(field).is_none() {
535                errors.push(ValidationError {
536                    code: "MISSING_REQUIRED_FIELD".to_string(),
537                    message: format!("Required field '{field}' is missing in ffi_analysis"),
538                    path: format!("ffi_analysis.{field}"),
539                    severity: ErrorSeverity::Critical,
540                    suggested_fix: Some(format!("Add required field '{field}'")),
541                });
542            }
543        }
544
545        Ok(())
546    }
547
548    /// Validate boundary analysis section
549    fn validate_boundary_analysis(
550        &self,
551        boundary_analysis: &Value,
552        errors: &mut Vec<ValidationError>,
553        _warnings: &mut [ValidationWarning],
554    ) -> TrackingResult<()> {
555        if !boundary_analysis.is_object() {
556            errors.push(ValidationError {
557                code: "INVALID_BOUNDARY_ANALYSIS_TYPE".to_string(),
558                message: "boundary_analysis must be an object".to_string(),
559                path: "boundary_analysis".to_string(),
560                severity: ErrorSeverity::Critical,
561                suggested_fix: Some("Ensure boundary_analysis is a JSON object".to_string()),
562            });
563            return Ok(());
564        }
565
566        // Validate cross_boundary_transfers field
567        if let Some(transfers) = boundary_analysis.get("cross_boundary_transfers") {
568            if !transfers.is_number() {
569                errors.push(ValidationError {
570                    code: "INVALID_FIELD_TYPE".to_string(),
571                    message: "cross_boundary_transfers must be a number".to_string(),
572                    path: "boundary_analysis.cross_boundary_transfers".to_string(),
573                    severity: ErrorSeverity::Warning,
574                    suggested_fix: Some("Ensure cross_boundary_transfers is a number".to_string()),
575                });
576            }
577        }
578
579        Ok(())
580    }
581
582    /// Validate safety violations section
583    fn validate_safety_violations(
584        &self,
585        safety_violations: &Value,
586        errors: &mut Vec<ValidationError>,
587        _warnings: &mut [ValidationWarning],
588    ) -> TrackingResult<()> {
589        if !safety_violations.is_object() {
590            errors.push(ValidationError {
591                code: "INVALID_SAFETY_VIOLATIONS_TYPE".to_string(),
592                message: "safety_violations must be an object".to_string(),
593                path: "safety_violations".to_string(),
594                severity: ErrorSeverity::Critical,
595                suggested_fix: Some("Ensure safety_violations is a JSON object".to_string()),
596            });
597            return Ok(());
598        }
599
600        // Validate violations array
601        if let Some(violations) = safety_violations.get("violations") {
602            if !violations.is_array() {
603                errors.push(ValidationError {
604                    code: "INVALID_FIELD_TYPE".to_string(),
605                    message: "violations must be an array".to_string(),
606                    path: "safety_violations.violations".to_string(),
607                    severity: ErrorSeverity::Critical,
608                    suggested_fix: Some("Ensure violations is an array".to_string()),
609                });
610            }
611        }
612
613        Ok(())
614    }
615
616    /// Apply custom validation rules
617    fn apply_custom_rules(
618        &self,
619        data: &Value,
620        errors: &mut Vec<ValidationError>,
621        _warnings: &mut [ValidationWarning],
622    ) -> TrackingResult<()> {
623        for rule in &self.config.custom_rules {
624            // Simplified path matching - in production, use proper JSONPath
625            if let Some(target_value) = self.get_value_by_path(data, &rule.path_pattern) {
626                if let Err(error_msg) = (rule.validator)(target_value) {
627                    errors.push(ValidationError {
628                        code: "CUSTOM_RULE_VIOLATION".to_string(),
629                        message: format!("Custom rule '{}' failed: {}", rule.name, error_msg),
630                        path: rule.path_pattern.clone(),
631                        severity: ErrorSeverity::Warning,
632                        suggested_fix: None,
633                    });
634                }
635            }
636        }
637        Ok(())
638    }
639
640    /// Check backward compatibility
641    fn check_backward_compatibility(
642        &self,
643        version: &str,
644        warnings: &mut Vec<ValidationWarning>,
645    ) -> TrackingResult<()> {
646        if let Some(schema_version) = self.supported_versions.get(version) {
647            if schema_version.backward_compatible_with.is_empty() {
648                warnings.push(ValidationWarning {
649                    warning_code: "NO_BACKWARD_COMPATIBILITY".to_string(),
650                    message: format!("Schema version {version} has no backward compatibility"),
651                    path: "metadata.schema_version".to_string(),
652                    suggestion: Some("Consider using a more recent schema version".to_string()),
653                });
654            }
655        }
656        Ok(())
657    }
658
659    /// Get value by simple path (simplified JSONPath)
660    fn get_value_by_path<'a>(&self, data: &'a Value, path: &str) -> Option<&'a Value> {
661        let parts: Vec<&str> = path.split('.').collect();
662        let mut current = data;
663
664        for part in parts {
665            current = current.get(part)?;
666        }
667
668        Some(current)
669    }
670
671    /// Simple hash function (use proper cryptographic hash in production)
672    fn simple_hash(&self, data: &str) -> u64 {
673        let mut hash = 0u64;
674        for byte in data.bytes() {
675            hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
676        }
677        hash
678    }
679}
680
681impl Default for SchemaValidator {
682    fn default() -> Self {
683        Self::new()
684    }
685}
686
687/// Validation metrics
688#[derive(Debug, Clone, Default, Serialize, Deserialize)]
689pub struct ValidationMetrics {
690    /// Number of sections validated
691    pub sections_validated: u32,
692    /// Number of fields validated
693    pub fields_validated: u32,
694    /// Validation duration in nanoseconds
695    pub validation_duration_ns: u128,
696}
697
698/// Convenience functions for common validation tasks
699impl SchemaValidator {
700    /// Create a validator with strict mode enabled
701    pub fn strict() -> Self {
702        Self::with_config(SchemaValidatorConfig {
703            strict_mode: true,
704            ..Default::default()
705        })
706    }
707
708    /// Create a validator with integrity checking disabled
709    pub fn without_integrity_check() -> Self {
710        Self::with_config(SchemaValidatorConfig {
711            enable_integrity_check: false,
712            ..Default::default()
713        })
714    }
715}
716
717/// Builder for schema validator configuration
718pub struct SchemaValidatorConfigBuilder {
719    config: SchemaValidatorConfig,
720}
721
722impl SchemaValidatorConfigBuilder {
723    /// Create a new configuration builder
724    pub fn new() -> Self {
725        Self {
726            config: SchemaValidatorConfig::default(),
727        }
728    }
729
730    /// Enable strict mode
731    pub fn strict_mode(mut self, enabled: bool) -> Self {
732        self.config.strict_mode = enabled;
733        self
734    }
735
736    /// Enable integrity checking
737    pub fn integrity_check(mut self, enabled: bool) -> Self {
738        self.config.enable_integrity_check = enabled;
739        self
740    }
741
742    /// Enable backward compatibility checking
743    pub fn backward_compatibility(mut self, enabled: bool) -> Self {
744        self.config.enable_backward_compatibility = enabled;
745        self
746    }
747
748    /// Set maximum allowed schema version
749    pub fn max_schema_version(mut self, version: String) -> Self {
750        self.config.max_schema_version = version;
751        self
752    }
753
754    /// Add a custom validation rule
755    pub fn add_custom_rule(mut self, rule: CustomValidationRule) -> Self {
756        self.config.custom_rules.push(rule);
757        self
758    }
759
760    /// Build the configuration
761    pub fn build(self) -> SchemaValidatorConfig {
762        self.config
763    }
764}
765
766impl Default for SchemaValidatorConfigBuilder {
767    fn default() -> Self {
768        Self::new()
769    }
770}
771
772#[cfg(test)]
773mod tests {
774    use super::*;
775    use serde_json::json;
776
777    #[test]
778    fn test_validator_creation() {
779        let validator = SchemaValidator::new();
780        assert!(!validator.supported_versions.is_empty());
781    }
782
783    #[test]
784    fn test_valid_minimal_document() {
785        let validator = SchemaValidator::new();
786        let data = json!({
787            "metadata": {
788                "analysis_type": "unsafe_ffi_analysis_optimized",
789                "schema_version": "2.0",
790                "export_timestamp": 1234567890
791            },
792            "unsafe_analysis": {
793                "summary": {"low_risk": 0, "medium_risk": 0, "high_risk": 0, "critical_risk": 0},
794                "allocations": []
795            },
796            "ffi_analysis": {
797                "summary": {"low_risk": 0, "medium_risk": 0, "high_risk": 0, "critical_risk": 0},
798                "allocations": []
799            }
800        });
801
802        let result = validator
803            .validate_unsafe_ffi_analysis(&data)
804            .expect("Failed to validate FFI analysis");
805        assert!(result.is_valid);
806        assert!(result.errors.is_empty());
807    }
808
809    #[test]
810    fn test_missing_metadata() {
811        let validator = SchemaValidator::new();
812        let data = json!({
813            "unsafe_analysis": {},
814            "ffi_analysis": {}
815        });
816
817        let result = validator.validate_unsafe_ffi_analysis(&data);
818        assert!(result.is_err());
819    }
820
821    #[test]
822    fn test_invalid_schema_version() {
823        let validator = SchemaValidator::new();
824        let data = json!({
825            "metadata": {
826                "analysis_type": "unsafe_ffi_analysis_optimized",
827                "schema_version": "999.0",
828                "export_timestamp": 1234567890
829            },
830            "unsafe_analysis": {"summary": {}, "allocations": []},
831            "ffi_analysis": {"summary": {}, "allocations": []}
832        });
833
834        let result = validator
835            .validate_unsafe_ffi_analysis(&data)
836            .expect("Test operation failed");
837        assert!(!result.is_valid);
838        assert!(!result.errors.is_empty());
839    }
840
841    #[test]
842    fn test_integrity_hash_calculation() {
843        let validator = SchemaValidator::new();
844        let data = json!({"test": "data"});
845
846        let hash1 = validator
847            .calculate_integrity_hash(&data)
848            .expect("Failed to calculate hash 1");
849        let hash2 = validator
850            .calculate_integrity_hash(&data)
851            .expect("Failed to calculate hash 2");
852
853        assert_eq!(hash1, hash2);
854        assert!(validator
855            .verify_integrity(&data, &hash1)
856            .expect("Failed to verify integrity"));
857    }
858
859    #[test]
860    fn test_config_builder() {
861        let config = SchemaValidatorConfigBuilder::new()
862            .strict_mode(true)
863            .integrity_check(false)
864            .max_schema_version("2.0".to_string())
865            .build();
866
867        assert!(config.strict_mode);
868        assert!(!config.enable_integrity_check);
869        assert_eq!(config.max_schema_version, "2.0");
870    }
871
872    #[test]
873    fn test_convenience_methods() {
874        let validator = SchemaValidator::new();
875        let valid_data = json!({
876            "metadata": {
877                "analysis_type": "unsafe_ffi_analysis_optimized",
878                "schema_version": "2.0",
879                "export_timestamp": 1234567890
880            },
881            "unsafe_analysis": {"summary": {}, "allocations": []},
882            "ffi_analysis": {"summary": {}, "allocations": []}
883        });
884
885        let result = validator
886            .validate_unsafe_ffi_analysis(&valid_data)
887            .expect("Failed to validate valid data");
888        assert!(result.is_valid);
889
890        let strict_validator = SchemaValidator::strict();
891        let result = strict_validator
892            .validate_unsafe_ffi_analysis(&valid_data)
893            .expect("Failed to validate with strict mode");
894        // In strict mode, the minimal valid data should still be valid
895        assert!(result.is_valid);
896
897        let no_integrity_validator = SchemaValidator::without_integrity_check();
898        let result = no_integrity_validator
899            .validate_unsafe_ffi_analysis(&valid_data)
900            .expect("Failed to validate without integrity check");
901        assert_eq!(result.integrity_hash, "disabled");
902    }
903}