mockforge_grpc/reflection/
validation_framework.rs

1//! Cross-endpoint validation framework
2//!
3//! This module provides validation capabilities to ensure data coherence
4//! across different endpoints, maintaining referential integrity and
5//! business logic constraints in generated mock data.
6
7use crate::reflection::schema_graph::{ForeignKeyMapping, Relationship, SchemaGraph};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use tracing::{debug, info};
11
12/// Configuration for cross-endpoint validation
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ValidationConfig {
15    /// Enable cross-endpoint validation
16    pub enabled: bool,
17    /// Strict mode - fail on any validation error
18    pub strict_mode: bool,
19    /// Maximum validation depth for nested relationships
20    pub max_validation_depth: usize,
21    /// Custom validation rules
22    pub custom_rules: Vec<CustomValidationRule>,
23    /// Cache validation results for performance
24    pub cache_results: bool,
25}
26
27impl Default for ValidationConfig {
28    fn default() -> Self {
29        Self {
30            enabled: true,
31            strict_mode: false,
32            max_validation_depth: 3,
33            custom_rules: vec![],
34            cache_results: true,
35        }
36    }
37}
38
39/// Custom validation rule definition
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct CustomValidationRule {
42    /// Rule name/identifier
43    pub name: String,
44    /// Entity types this rule applies to
45    pub applies_to_entities: Vec<String>,
46    /// Fields this rule validates
47    pub validates_fields: Vec<String>,
48    /// Rule type
49    pub rule_type: ValidationRuleType,
50    /// Rule parameters
51    pub parameters: HashMap<String, String>,
52    /// Error message template
53    pub error_message: String,
54}
55
56/// Types of validation rules
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum ValidationRuleType {
59    /// Foreign key existence validation
60    ForeignKeyExists,
61    /// Field format validation
62    FieldFormat,
63    /// Range validation
64    Range,
65    /// Unique constraint validation
66    Unique,
67    /// Business logic validation
68    BusinessLogic,
69    /// Custom validation function
70    Custom,
71}
72
73/// Result of a validation operation
74#[derive(Debug, Clone)]
75pub struct ValidationResult {
76    /// Whether validation passed
77    pub is_valid: bool,
78    /// Validation errors found
79    pub errors: Vec<ValidationError>,
80    /// Warnings (non-fatal issues)
81    pub warnings: Vec<ValidationWarning>,
82    /// Entities that were validated
83    pub validated_entities: Vec<String>,
84}
85
86/// A validation error
87#[derive(Debug, Clone)]
88pub struct ValidationError {
89    /// Error type
90    pub error_type: ValidationErrorType,
91    /// Entity where error occurred
92    pub entity_name: String,
93    /// Field where error occurred
94    pub field_name: String,
95    /// Error message
96    pub message: String,
97    /// Value that caused the error
98    pub invalid_value: String,
99    /// Suggested fix (if available)
100    pub suggested_fix: Option<String>,
101}
102
103/// A validation warning
104#[derive(Debug, Clone)]
105pub struct ValidationWarning {
106    /// Warning type
107    pub warning_type: ValidationWarningType,
108    /// Entity where warning occurred
109    pub entity_name: String,
110    /// Field where warning occurred (optional)
111    pub field_name: Option<String>,
112    /// Warning message
113    pub message: String,
114}
115
116/// Types of validation errors
117#[derive(Debug, Clone, PartialEq)]
118pub enum ValidationErrorType {
119    /// Foreign key references non-existent entity
120    ForeignKeyNotFound,
121    /// Field format is invalid
122    InvalidFormat,
123    /// Value is outside allowed range
124    OutOfRange,
125    /// Unique constraint violation
126    DuplicateValue,
127    /// Business logic constraint violation
128    BusinessRuleViolation,
129    /// Circular reference detected
130    CircularReference,
131}
132
133/// Types of validation warnings
134#[derive(Debug, Clone)]
135pub enum ValidationWarningType {
136    /// Potential data inconsistency
137    DataInconsistency,
138    /// Performance concern
139    PerformanceConcern,
140    /// Best practice violation
141    BestPracticeViolation,
142}
143
144/// Data store for validation - tracks generated entities
145#[derive(Debug, Default)]
146pub struct ValidationDataStore {
147    /// Generated entities by type
148    entities: HashMap<String, Vec<GeneratedEntity>>,
149    /// Index for fast foreign key lookups
150    foreign_key_index: HashMap<String, HashMap<String, Vec<usize>>>,
151}
152
153/// A generated entity for validation
154#[derive(Debug, Clone)]
155pub struct GeneratedEntity {
156    /// Entity type name
157    pub entity_type: String,
158    /// Primary key value (if available)
159    pub primary_key: Option<String>,
160    /// All field values
161    pub field_values: HashMap<String, String>,
162    /// Endpoint this entity was generated for
163    pub endpoint: String,
164    /// Generation timestamp
165    pub generated_at: std::time::SystemTime,
166}
167
168/// Cross-endpoint validation framework
169pub struct ValidationFramework {
170    /// Configuration
171    config: ValidationConfig,
172    /// Schema graph for relationship validation
173    schema_graph: Option<SchemaGraph>,
174    /// Data store for tracking generated entities
175    data_store: ValidationDataStore,
176    /// Validation cache
177    validation_cache: HashMap<String, ValidationResult>,
178}
179
180impl ValidationFramework {
181    /// Create a new validation framework
182    pub fn new(config: ValidationConfig) -> Self {
183        Self {
184            config,
185            schema_graph: None,
186            data_store: ValidationDataStore::default(),
187            validation_cache: HashMap::new(),
188        }
189    }
190
191    /// Set the schema graph for relationship validation
192    pub fn set_schema_graph(&mut self, schema_graph: SchemaGraph) {
193        info!(
194            "Setting schema graph with {} entities for validation",
195            schema_graph.entities.len()
196        );
197        self.schema_graph = Some(schema_graph);
198    }
199
200    /// Register a generated entity for validation
201    pub fn register_entity(
202        &mut self,
203        entity: GeneratedEntity,
204    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
205        debug!("Registering entity {} for endpoint {}", entity.entity_type, entity.endpoint);
206
207        let entity_type = entity.entity_type.clone();
208        let primary_key = entity.primary_key.clone();
209
210        // Add to entities list
211        let entities_list = self.data_store.entities.entry(entity_type.clone()).or_default();
212        let entity_index = entities_list.len();
213        entities_list.push(entity);
214
215        // Update foreign key index if primary key exists
216        if let Some(pk) = primary_key {
217            let type_index = self.data_store.foreign_key_index.entry(entity_type).or_default();
218            let pk_index = type_index.entry(pk).or_default();
219            pk_index.push(entity_index);
220        }
221
222        Ok(())
223    }
224
225    /// Validate all registered entities for cross-endpoint consistency
226    pub fn validate_all_entities(&mut self) -> ValidationResult {
227        if !self.config.enabled {
228            return ValidationResult {
229                is_valid: true,
230                errors: vec![],
231                warnings: vec![],
232                validated_entities: vec![],
233            };
234        }
235
236        info!(
237            "Starting cross-endpoint validation of {} entity types",
238            self.data_store.entities.len()
239        );
240
241        let mut result = ValidationResult {
242            is_valid: true,
243            errors: vec![],
244            warnings: vec![],
245            validated_entities: vec![],
246        };
247
248        // Validate foreign key relationships
249        self.validate_foreign_key_relationships(&mut result);
250
251        // Validate custom rules
252        self.validate_custom_rules(&mut result);
253
254        // Validate referential integrity
255        self.validate_referential_integrity(&mut result);
256
257        // Check for potential issues
258        self.check_data_consistency(&mut result);
259
260        result.is_valid = result.errors.is_empty() || !self.config.strict_mode;
261
262        info!(
263            "Validation completed: {} errors, {} warnings",
264            result.errors.len(),
265            result.warnings.len()
266        );
267
268        result
269    }
270
271    /// Validate foreign key relationships
272    fn validate_foreign_key_relationships(&self, result: &mut ValidationResult) {
273        if let Some(schema_graph) = &self.schema_graph {
274            for (entity_type, entities) in &self.data_store.entities {
275                result.validated_entities.push(entity_type.clone());
276
277                // Get foreign key mappings for this entity
278                if let Some(fk_mappings) = schema_graph.foreign_keys.get(entity_type) {
279                    for entity in entities {
280                        self.validate_entity_foreign_keys(entity, fk_mappings, result);
281                    }
282                }
283            }
284        }
285    }
286
287    /// Validate foreign keys for a specific entity
288    fn validate_entity_foreign_keys(
289        &self,
290        entity: &GeneratedEntity,
291        fk_mappings: &[ForeignKeyMapping],
292        result: &mut ValidationResult,
293    ) {
294        for mapping in fk_mappings {
295            if let Some(fk_value) = entity.field_values.get(&mapping.field_name) {
296                // Check if the referenced entity exists
297                if !self.foreign_key_exists(&mapping.target_entity, fk_value) {
298                    result.errors.push(ValidationError {
299                        error_type: ValidationErrorType::ForeignKeyNotFound,
300                        entity_name: entity.entity_type.clone(),
301                        field_name: mapping.field_name.clone(),
302                        message: format!(
303                            "Foreign key '{}' references non-existent {} with value '{}'",
304                            mapping.field_name, mapping.target_entity, fk_value
305                        ),
306                        invalid_value: fk_value.clone(),
307                        suggested_fix: Some(format!(
308                            "Create a {} entity with primary key '{}'",
309                            mapping.target_entity, fk_value
310                        )),
311                    });
312                }
313            }
314        }
315    }
316
317    /// Check if a foreign key value exists
318    fn foreign_key_exists(&self, target_entity: &str, key_value: &str) -> bool {
319        if let Some(type_index) = self.data_store.foreign_key_index.get(target_entity) {
320            type_index.contains_key(key_value)
321        } else {
322            false
323        }
324    }
325
326    /// Validate custom validation rules
327    fn validate_custom_rules(&self, result: &mut ValidationResult) {
328        for rule in &self.config.custom_rules {
329            for entity_type in &rule.applies_to_entities {
330                if let Some(entities) = self.data_store.entities.get(entity_type) {
331                    for entity in entities {
332                        self.validate_entity_against_rule(entity, rule, result);
333                    }
334                }
335            }
336        }
337    }
338
339    /// Validate an entity against a custom rule
340    fn validate_entity_against_rule(
341        &self,
342        entity: &GeneratedEntity,
343        rule: &CustomValidationRule,
344        result: &mut ValidationResult,
345    ) {
346        match &rule.rule_type {
347            ValidationRuleType::FieldFormat => {
348                self.validate_field_format(entity, rule, result);
349            }
350            ValidationRuleType::Range => {
351                self.validate_field_range(entity, rule, result);
352            }
353            ValidationRuleType::Unique => {
354                self.validate_field_uniqueness(entity, rule, result);
355            }
356            _ => {
357                debug!("Custom validation rule type {:?} not yet implemented", rule.rule_type);
358            }
359        }
360    }
361
362    /// Validate field format
363    fn validate_field_format(
364        &self,
365        entity: &GeneratedEntity,
366        rule: &CustomValidationRule,
367        result: &mut ValidationResult,
368    ) {
369        for field_name in &rule.validates_fields {
370            if let Some(field_value) = entity.field_values.get(field_name) {
371                if let Some(pattern) = rule.parameters.get("pattern") {
372                    if let Ok(regex) = regex::Regex::new(pattern) {
373                        if !regex.is_match(field_value) {
374                            result.errors.push(ValidationError {
375                                error_type: ValidationErrorType::InvalidFormat,
376                                entity_name: entity.entity_type.clone(),
377                                field_name: field_name.clone(),
378                                message: rule.error_message.clone(),
379                                invalid_value: field_value.clone(),
380                                suggested_fix: Some(format!(
381                                    "Value should match pattern: {}",
382                                    pattern
383                                )),
384                            });
385                        }
386                    }
387                }
388            }
389        }
390    }
391
392    /// Validate field range constraints
393    fn validate_field_range(
394        &self,
395        entity: &GeneratedEntity,
396        rule: &CustomValidationRule,
397        result: &mut ValidationResult,
398    ) {
399        for field_name in &rule.validates_fields {
400            if let Some(field_value) = entity.field_values.get(field_name) {
401                if let Ok(value) = field_value.parse::<f64>() {
402                    let min = rule.parameters.get("min").and_then(|s| s.parse::<f64>().ok());
403                    let max = rule.parameters.get("max").and_then(|s| s.parse::<f64>().ok());
404
405                    let out_of_range = (min.is_some() && value < min.unwrap())
406                        || (max.is_some() && value > max.unwrap());
407
408                    if out_of_range {
409                        result.errors.push(ValidationError {
410                            error_type: ValidationErrorType::OutOfRange,
411                            entity_name: entity.entity_type.clone(),
412                            field_name: field_name.clone(),
413                            message: rule.error_message.clone(),
414                            invalid_value: field_value.clone(),
415                            suggested_fix: Some(format!(
416                                "Value should be between {} and {}",
417                                min.map_or("any".to_string(), |v| v.to_string()),
418                                max.map_or("any".to_string(), |v| v.to_string())
419                            )),
420                        });
421                    }
422                }
423            }
424        }
425    }
426
427    /// Validate field uniqueness constraints
428    fn validate_field_uniqueness(
429        &self,
430        entity: &GeneratedEntity,
431        rule: &CustomValidationRule,
432        result: &mut ValidationResult,
433    ) {
434        for field_name in &rule.validates_fields {
435            if let Some(field_value) = entity.field_values.get(field_name) {
436                // Check if this value appears in other entities
437                let mut duplicate_count = 0;
438
439                if let Some(entities) = self.data_store.entities.get(&entity.entity_type) {
440                    for other_entity in entities {
441                        if let Some(other_value) = other_entity.field_values.get(field_name) {
442                            if other_value == field_value {
443                                duplicate_count += 1;
444                            }
445                        }
446                    }
447                }
448
449                if duplicate_count > 1 {
450                    result.errors.push(ValidationError {
451                        error_type: ValidationErrorType::DuplicateValue,
452                        entity_name: entity.entity_type.clone(),
453                        field_name: field_name.clone(),
454                        message: rule.error_message.clone(),
455                        invalid_value: field_value.clone(),
456                        suggested_fix: Some("Generate unique values for this field".to_string()),
457                    });
458                }
459            }
460        }
461    }
462
463    /// Validate referential integrity across endpoints
464    fn validate_referential_integrity(&self, result: &mut ValidationResult) {
465        if let Some(schema_graph) = &self.schema_graph {
466            for relationship in &schema_graph.relationships {
467                self.validate_relationship_integrity(relationship, result);
468            }
469        }
470    }
471
472    /// Validate a specific relationship's integrity
473    fn validate_relationship_integrity(
474        &self,
475        relationship: &Relationship,
476        result: &mut ValidationResult,
477    ) {
478        if let (Some(from_entities), Some(to_entities)) = (
479            self.data_store.entities.get(&relationship.from_entity),
480            self.data_store.entities.get(&relationship.to_entity),
481        ) {
482            for from_entity in from_entities {
483                if let Some(ref_value) = from_entity.field_values.get(&relationship.field_name) {
484                    let target_exists = to_entities
485                        .iter()
486                        .any(|to_entity| to_entity.primary_key.as_ref() == Some(ref_value));
487
488                    if !target_exists && relationship.is_required {
489                        result.warnings.push(ValidationWarning {
490                            warning_type: ValidationWarningType::DataInconsistency,
491                            entity_name: from_entity.entity_type.clone(),
492                            field_name: Some(relationship.field_name.clone()),
493                            message: format!(
494                                "Required relationship from {} to {} not satisfied - referenced {} '{}' does not exist",
495                                relationship.from_entity, relationship.to_entity,
496                                relationship.to_entity, ref_value
497                            ),
498                        });
499                    }
500                }
501            }
502        }
503    }
504
505    /// Check for general data consistency issues
506    fn check_data_consistency(&self, result: &mut ValidationResult) {
507        // Check for entities that are never referenced
508        self.check_orphaned_entities(result);
509
510        // Check for potential performance issues
511        self.check_performance_concerns(result);
512    }
513
514    /// Check for entities that might be orphaned
515    fn check_orphaned_entities(&self, result: &mut ValidationResult) {
516        if let Some(schema_graph) = &self.schema_graph {
517            for (entity_type, entity_node) in &schema_graph.entities {
518                if entity_node.referenced_by.is_empty() && !entity_node.is_root {
519                    if let Some(entities) = self.data_store.entities.get(entity_type) {
520                        if !entities.is_empty() {
521                            result.warnings.push(ValidationWarning {
522                                warning_type: ValidationWarningType::DataInconsistency,
523                                entity_name: entity_type.clone(),
524                                field_name: None,
525                                message: format!(
526                                    "Entity type {} is not referenced by any other entities but {} instances were generated",
527                                    entity_type, entities.len()
528                                ),
529                            });
530                        }
531                    }
532                }
533            }
534        }
535    }
536
537    /// Check for potential performance concerns
538    fn check_performance_concerns(&self, result: &mut ValidationResult) {
539        for (entity_type, entities) in &self.data_store.entities {
540            if entities.len() > 10000 {
541                result.warnings.push(ValidationWarning {
542                    warning_type: ValidationWarningType::PerformanceConcern,
543                    entity_name: entity_type.clone(),
544                    field_name: None,
545                    message: format!(
546                        "Large number of {} entities ({}) may impact performance",
547                        entity_type,
548                        entities.len()
549                    ),
550                });
551            }
552        }
553    }
554
555    /// Clear all validation data
556    pub fn clear(&mut self) {
557        self.data_store.entities.clear();
558        self.data_store.foreign_key_index.clear();
559        self.validation_cache.clear();
560        info!("Validation framework data cleared");
561    }
562
563    /// Get validation statistics
564    pub fn get_statistics(&self) -> ValidationStatistics {
565        let total_entities: usize = self.data_store.entities.values().map(|v| v.len()).sum();
566        let entity_type_count = self.data_store.entities.len();
567        let indexed_keys: usize = self
568            .data_store
569            .foreign_key_index
570            .values()
571            .map(|type_index| type_index.len())
572            .sum();
573
574        ValidationStatistics {
575            total_entities,
576            entity_type_count,
577            indexed_foreign_keys: indexed_keys,
578            cache_size: self.validation_cache.len(),
579        }
580    }
581}
582
583/// Validation framework statistics
584#[derive(Debug, Clone)]
585pub struct ValidationStatistics {
586    /// Total number of entities tracked
587    pub total_entities: usize,
588    /// Number of different entity types
589    pub entity_type_count: usize,
590    /// Number of indexed foreign key values
591    pub indexed_foreign_keys: usize,
592    /// Size of validation cache
593    pub cache_size: usize,
594}
595
596#[cfg(test)]
597mod tests {
598    use super::*;
599    use std::time::SystemTime;
600
601    #[test]
602    fn test_validation_framework_creation() {
603        let config = ValidationConfig::default();
604        let framework = ValidationFramework::new(config);
605        assert!(framework.config.enabled);
606        assert!(!framework.config.strict_mode);
607    }
608
609    #[test]
610    fn test_entity_registration() {
611        let config = ValidationConfig::default();
612        let mut framework = ValidationFramework::new(config);
613
614        let entity = GeneratedEntity {
615            entity_type: "User".to_string(),
616            primary_key: Some("user_123".to_string()),
617            field_values: HashMap::from([
618                ("id".to_string(), "user_123".to_string()),
619                ("name".to_string(), "John Doe".to_string()),
620            ]),
621            endpoint: "/users".to_string(),
622            generated_at: SystemTime::now(),
623        };
624
625        framework.register_entity(entity).expect("Should register entity successfully");
626
627        let stats = framework.get_statistics();
628        assert_eq!(stats.total_entities, 1);
629        assert_eq!(stats.entity_type_count, 1);
630    }
631
632    #[test]
633    fn test_validation_with_no_schema() {
634        let config = ValidationConfig::default();
635        let mut framework = ValidationFramework::new(config);
636
637        // Add some entities
638        let entity1 = GeneratedEntity {
639            entity_type: "User".to_string(),
640            primary_key: Some("1".to_string()),
641            field_values: HashMap::from([("id".to_string(), "1".to_string())]),
642            endpoint: "/users".to_string(),
643            generated_at: SystemTime::now(),
644        };
645
646        framework.register_entity(entity1).unwrap();
647
648        let result = framework.validate_all_entities();
649        assert!(result.is_valid);
650        assert!(result.errors.is_empty());
651    }
652
653    #[test]
654    fn test_custom_validation_rule() {
655        let mut config = ValidationConfig::default();
656        config.custom_rules.push(CustomValidationRule {
657            name: "email_format".to_string(),
658            applies_to_entities: vec!["User".to_string()],
659            validates_fields: vec!["email".to_string()],
660            rule_type: ValidationRuleType::FieldFormat,
661            parameters: HashMap::from([(
662                "pattern".to_string(),
663                r"^[^@]+@[^@]+\.[^@]+$".to_string(),
664            )]),
665            error_message: "Invalid email format".to_string(),
666        });
667
668        let mut framework = ValidationFramework::new(config);
669
670        let entity = GeneratedEntity {
671            entity_type: "User".to_string(),
672            primary_key: Some("1".to_string()),
673            field_values: HashMap::from([
674                ("id".to_string(), "1".to_string()),
675                ("email".to_string(), "invalid-email".to_string()),
676            ]),
677            endpoint: "/users".to_string(),
678            generated_at: SystemTime::now(),
679        };
680
681        framework.register_entity(entity).unwrap();
682
683        let result = framework.validate_all_entities();
684        assert!(!result.errors.is_empty());
685        assert_eq!(result.errors[0].error_type, ValidationErrorType::InvalidFormat);
686    }
687}