1use crate::reflection::schema_graph::{ForeignKeyMapping, Relationship, SchemaGraph};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use tracing::{debug, info};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ValidationConfig {
15 pub enabled: bool,
17 pub strict_mode: bool,
19 pub max_validation_depth: usize,
21 pub custom_rules: Vec<CustomValidationRule>,
23 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#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct CustomValidationRule {
42 pub name: String,
44 pub applies_to_entities: Vec<String>,
46 pub validates_fields: Vec<String>,
48 pub rule_type: ValidationRuleType,
50 pub parameters: HashMap<String, String>,
52 pub error_message: String,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub enum ValidationRuleType {
59 ForeignKeyExists,
61 FieldFormat,
63 Range,
65 Unique,
67 BusinessLogic,
69 Custom,
71}
72
73#[derive(Debug, Clone)]
75pub struct ValidationResult {
76 pub is_valid: bool,
78 pub errors: Vec<ValidationError>,
80 pub warnings: Vec<ValidationWarning>,
82 pub validated_entities: Vec<String>,
84}
85
86#[derive(Debug, Clone)]
88pub struct ValidationError {
89 pub error_type: ValidationErrorType,
91 pub entity_name: String,
93 pub field_name: String,
95 pub message: String,
97 pub invalid_value: String,
99 pub suggested_fix: Option<String>,
101}
102
103#[derive(Debug, Clone)]
105pub struct ValidationWarning {
106 pub warning_type: ValidationWarningType,
108 pub entity_name: String,
110 pub field_name: Option<String>,
112 pub message: String,
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub enum ValidationErrorType {
119 ForeignKeyNotFound,
121 InvalidFormat,
123 OutOfRange,
125 DuplicateValue,
127 BusinessRuleViolation,
129 CircularReference,
131}
132
133#[derive(Debug, Clone)]
135pub enum ValidationWarningType {
136 DataInconsistency,
138 PerformanceConcern,
140 BestPracticeViolation,
142}
143
144#[derive(Debug, Default)]
146pub struct ValidationDataStore {
147 entities: HashMap<String, Vec<GeneratedEntity>>,
149 foreign_key_index: HashMap<String, HashMap<String, Vec<usize>>>,
151}
152
153#[derive(Debug, Clone)]
155pub struct GeneratedEntity {
156 pub entity_type: String,
158 pub primary_key: Option<String>,
160 pub field_values: HashMap<String, String>,
162 pub endpoint: String,
164 pub generated_at: std::time::SystemTime,
166}
167
168pub struct ValidationFramework {
170 config: ValidationConfig,
172 schema_graph: Option<SchemaGraph>,
174 data_store: ValidationDataStore,
176 validation_cache: HashMap<String, ValidationResult>,
178}
179
180impl ValidationFramework {
181 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 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 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 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 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 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 self.validate_foreign_key_relationships(&mut result);
250
251 self.validate_custom_rules(&mut result);
253
254 self.validate_referential_integrity(&mut result);
256
257 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 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 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 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 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 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 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 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::ForeignKeyExists => {
348 self.validate_foreign_key_rule(entity, rule, result);
349 }
350 ValidationRuleType::FieldFormat => {
351 self.validate_field_format(entity, rule, result);
352 }
353 ValidationRuleType::Range => {
354 self.validate_field_range(entity, rule, result);
355 }
356 ValidationRuleType::Unique => {
357 self.validate_field_uniqueness(entity, rule, result);
358 }
359 ValidationRuleType::BusinessLogic | ValidationRuleType::Custom => {
360 self.validate_business_logic_rule(entity, rule, result);
363 }
364 }
365 }
366
367 fn validate_foreign_key_rule(
371 &self,
372 entity: &GeneratedEntity,
373 rule: &CustomValidationRule,
374 result: &mut ValidationResult,
375 ) {
376 let Some(target_entity) = rule.parameters.get("target_entity") else {
377 result.warnings.push(ValidationWarning {
378 warning_type: ValidationWarningType::BestPracticeViolation,
379 entity_name: entity.entity_type.clone(),
380 field_name: None,
381 message: format!(
382 "Rule '{}' is missing required parameter 'target_entity'",
383 rule.name
384 ),
385 });
386 return;
387 };
388
389 for field_name in &rule.validates_fields {
390 if let Some(fk_value) = entity.field_values.get(field_name) {
391 if !self.foreign_key_exists(target_entity, fk_value) {
392 result.errors.push(ValidationError {
393 error_type: ValidationErrorType::ForeignKeyNotFound,
394 entity_name: entity.entity_type.clone(),
395 field_name: field_name.clone(),
396 message: rule.error_message.clone(),
397 invalid_value: fk_value.clone(),
398 suggested_fix: Some(format!(
399 "Create a {} entity with primary key '{}'",
400 target_entity, fk_value
401 )),
402 });
403 }
404 }
405 }
406 }
407
408 fn validate_business_logic_rule(
414 &self,
415 entity: &GeneratedEntity,
416 rule: &CustomValidationRule,
417 result: &mut ValidationResult,
418 ) {
419 let Some(field) = rule.parameters.get("field") else {
420 result.warnings.push(ValidationWarning {
421 warning_type: ValidationWarningType::BestPracticeViolation,
422 entity_name: entity.entity_type.clone(),
423 field_name: None,
424 message: format!(
425 "Rule '{}' skipped: missing 'field' parameter for {:?}",
426 rule.name, rule.rule_type
427 ),
428 });
429 return;
430 };
431 let Some(operator) = rule.parameters.get("operator") else {
432 result.warnings.push(ValidationWarning {
433 warning_type: ValidationWarningType::BestPracticeViolation,
434 entity_name: entity.entity_type.clone(),
435 field_name: Some(field.clone()),
436 message: format!(
437 "Rule '{}' skipped: missing 'operator' parameter for {:?}",
438 rule.name, rule.rule_type
439 ),
440 });
441 return;
442 };
443 let Some(expected) = rule.parameters.get("value") else {
444 result.warnings.push(ValidationWarning {
445 warning_type: ValidationWarningType::BestPracticeViolation,
446 entity_name: entity.entity_type.clone(),
447 field_name: Some(field.clone()),
448 message: format!(
449 "Rule '{}' skipped: missing 'value' parameter for {:?}",
450 rule.name, rule.rule_type
451 ),
452 });
453 return;
454 };
455
456 if let Some(actual) = entity.field_values.get(field) {
457 let passed = match operator.as_str() {
458 "eq" => actual == expected,
459 "ne" => actual != expected,
460 "contains" => actual.contains(expected),
461 "starts_with" => actual.starts_with(expected),
462 "ends_with" => actual.ends_with(expected),
463 _ => {
464 result.warnings.push(ValidationWarning {
465 warning_type: ValidationWarningType::BestPracticeViolation,
466 entity_name: entity.entity_type.clone(),
467 field_name: Some(field.clone()),
468 message: format!(
469 "Rule '{}' uses unsupported operator '{}'",
470 rule.name, operator
471 ),
472 });
473 return;
474 }
475 };
476
477 if !passed {
478 result.errors.push(ValidationError {
479 error_type: ValidationErrorType::BusinessRuleViolation,
480 entity_name: entity.entity_type.clone(),
481 field_name: field.clone(),
482 message: rule.error_message.clone(),
483 invalid_value: actual.clone(),
484 suggested_fix: Some(format!(
485 "Expected '{}' {} '{}'",
486 field, operator, expected
487 )),
488 });
489 }
490 }
491 }
492
493 fn validate_field_format(
495 &self,
496 entity: &GeneratedEntity,
497 rule: &CustomValidationRule,
498 result: &mut ValidationResult,
499 ) {
500 for field_name in &rule.validates_fields {
501 if let Some(field_value) = entity.field_values.get(field_name) {
502 if let Some(pattern) = rule.parameters.get("pattern") {
503 if let Ok(regex) = regex::Regex::new(pattern) {
504 if !regex.is_match(field_value) {
505 result.errors.push(ValidationError {
506 error_type: ValidationErrorType::InvalidFormat,
507 entity_name: entity.entity_type.clone(),
508 field_name: field_name.clone(),
509 message: rule.error_message.clone(),
510 invalid_value: field_value.clone(),
511 suggested_fix: Some(format!(
512 "Value should match pattern: {}",
513 pattern
514 )),
515 });
516 }
517 }
518 }
519 }
520 }
521 }
522
523 fn validate_field_range(
525 &self,
526 entity: &GeneratedEntity,
527 rule: &CustomValidationRule,
528 result: &mut ValidationResult,
529 ) {
530 for field_name in &rule.validates_fields {
531 if let Some(field_value) = entity.field_values.get(field_name) {
532 if let Ok(value) = field_value.parse::<f64>() {
533 let min = rule.parameters.get("min").and_then(|s| s.parse::<f64>().ok());
534 let max = rule.parameters.get("max").and_then(|s| s.parse::<f64>().ok());
535
536 let out_of_range = (min.is_some() && value < min.unwrap())
537 || (max.is_some() && value > max.unwrap());
538
539 if out_of_range {
540 result.errors.push(ValidationError {
541 error_type: ValidationErrorType::OutOfRange,
542 entity_name: entity.entity_type.clone(),
543 field_name: field_name.clone(),
544 message: rule.error_message.clone(),
545 invalid_value: field_value.clone(),
546 suggested_fix: Some(format!(
547 "Value should be between {} and {}",
548 min.map_or("any".to_string(), |v| v.to_string()),
549 max.map_or("any".to_string(), |v| v.to_string())
550 )),
551 });
552 }
553 }
554 }
555 }
556 }
557
558 fn validate_field_uniqueness(
560 &self,
561 entity: &GeneratedEntity,
562 rule: &CustomValidationRule,
563 result: &mut ValidationResult,
564 ) {
565 for field_name in &rule.validates_fields {
566 if let Some(field_value) = entity.field_values.get(field_name) {
567 let mut duplicate_count = 0;
569
570 if let Some(entities) = self.data_store.entities.get(&entity.entity_type) {
571 for other_entity in entities {
572 if let Some(other_value) = other_entity.field_values.get(field_name) {
573 if other_value == field_value {
574 duplicate_count += 1;
575 }
576 }
577 }
578 }
579
580 if duplicate_count > 1 {
581 result.errors.push(ValidationError {
582 error_type: ValidationErrorType::DuplicateValue,
583 entity_name: entity.entity_type.clone(),
584 field_name: field_name.clone(),
585 message: rule.error_message.clone(),
586 invalid_value: field_value.clone(),
587 suggested_fix: Some("Generate unique values for this field".to_string()),
588 });
589 }
590 }
591 }
592 }
593
594 fn validate_referential_integrity(&self, result: &mut ValidationResult) {
596 if let Some(schema_graph) = &self.schema_graph {
597 for relationship in &schema_graph.relationships {
598 self.validate_relationship_integrity(relationship, result);
599 }
600 }
601 }
602
603 fn validate_relationship_integrity(
605 &self,
606 relationship: &Relationship,
607 result: &mut ValidationResult,
608 ) {
609 if let (Some(from_entities), Some(to_entities)) = (
610 self.data_store.entities.get(&relationship.from_entity),
611 self.data_store.entities.get(&relationship.to_entity),
612 ) {
613 for from_entity in from_entities {
614 if let Some(ref_value) = from_entity.field_values.get(&relationship.field_name) {
615 let target_exists = to_entities
616 .iter()
617 .any(|to_entity| to_entity.primary_key.as_ref() == Some(ref_value));
618
619 if !target_exists && relationship.is_required {
620 result.warnings.push(ValidationWarning {
621 warning_type: ValidationWarningType::DataInconsistency,
622 entity_name: from_entity.entity_type.clone(),
623 field_name: Some(relationship.field_name.clone()),
624 message: format!(
625 "Required relationship from {} to {} not satisfied - referenced {} '{}' does not exist",
626 relationship.from_entity, relationship.to_entity,
627 relationship.to_entity, ref_value
628 ),
629 });
630 }
631 }
632 }
633 }
634 }
635
636 fn check_data_consistency(&self, result: &mut ValidationResult) {
638 self.check_orphaned_entities(result);
640
641 self.check_performance_concerns(result);
643 }
644
645 fn check_orphaned_entities(&self, result: &mut ValidationResult) {
647 if let Some(schema_graph) = &self.schema_graph {
648 for (entity_type, entity_node) in &schema_graph.entities {
649 if entity_node.referenced_by.is_empty() && !entity_node.is_root {
650 if let Some(entities) = self.data_store.entities.get(entity_type) {
651 if !entities.is_empty() {
652 result.warnings.push(ValidationWarning {
653 warning_type: ValidationWarningType::DataInconsistency,
654 entity_name: entity_type.clone(),
655 field_name: None,
656 message: format!(
657 "Entity type {} is not referenced by any other entities but {} instances were generated",
658 entity_type, entities.len()
659 ),
660 });
661 }
662 }
663 }
664 }
665 }
666 }
667
668 fn check_performance_concerns(&self, result: &mut ValidationResult) {
670 for (entity_type, entities) in &self.data_store.entities {
671 if entities.len() > 10000 {
672 result.warnings.push(ValidationWarning {
673 warning_type: ValidationWarningType::PerformanceConcern,
674 entity_name: entity_type.clone(),
675 field_name: None,
676 message: format!(
677 "Large number of {} entities ({}) may impact performance",
678 entity_type,
679 entities.len()
680 ),
681 });
682 }
683 }
684 }
685
686 pub fn clear(&mut self) {
688 self.data_store.entities.clear();
689 self.data_store.foreign_key_index.clear();
690 self.validation_cache.clear();
691 info!("Validation framework data cleared");
692 }
693
694 pub fn get_statistics(&self) -> ValidationStatistics {
696 let total_entities: usize = self.data_store.entities.values().map(|v| v.len()).sum();
697 let entity_type_count = self.data_store.entities.len();
698 let indexed_keys: usize = self
699 .data_store
700 .foreign_key_index
701 .values()
702 .map(|type_index| type_index.len())
703 .sum();
704
705 ValidationStatistics {
706 total_entities,
707 entity_type_count,
708 indexed_foreign_keys: indexed_keys,
709 cache_size: self.validation_cache.len(),
710 }
711 }
712}
713
714#[derive(Debug, Clone)]
716pub struct ValidationStatistics {
717 pub total_entities: usize,
719 pub entity_type_count: usize,
721 pub indexed_foreign_keys: usize,
723 pub cache_size: usize,
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730 use std::time::SystemTime;
731
732 #[test]
733 fn test_validation_framework_creation() {
734 let config = ValidationConfig::default();
735 let framework = ValidationFramework::new(config);
736 assert!(framework.config.enabled);
737 assert!(!framework.config.strict_mode);
738 }
739
740 #[test]
741 fn test_entity_registration() {
742 let config = ValidationConfig::default();
743 let mut framework = ValidationFramework::new(config);
744
745 let entity = GeneratedEntity {
746 entity_type: "User".to_string(),
747 primary_key: Some("user_123".to_string()),
748 field_values: HashMap::from([
749 ("id".to_string(), "user_123".to_string()),
750 ("name".to_string(), "John Doe".to_string()),
751 ]),
752 endpoint: "/users".to_string(),
753 generated_at: SystemTime::now(),
754 };
755
756 framework.register_entity(entity).expect("Should register entity successfully");
757
758 let stats = framework.get_statistics();
759 assert_eq!(stats.total_entities, 1);
760 assert_eq!(stats.entity_type_count, 1);
761 }
762
763 #[test]
764 fn test_validation_with_no_schema() {
765 let config = ValidationConfig::default();
766 let mut framework = ValidationFramework::new(config);
767
768 let entity1 = GeneratedEntity {
770 entity_type: "User".to_string(),
771 primary_key: Some("1".to_string()),
772 field_values: HashMap::from([("id".to_string(), "1".to_string())]),
773 endpoint: "/users".to_string(),
774 generated_at: SystemTime::now(),
775 };
776
777 framework.register_entity(entity1).unwrap();
778
779 let result = framework.validate_all_entities();
780 assert!(result.is_valid);
781 assert!(result.errors.is_empty());
782 }
783
784 #[test]
785 fn test_custom_validation_rule() {
786 let mut config = ValidationConfig::default();
787 config.custom_rules.push(CustomValidationRule {
788 name: "email_format".to_string(),
789 applies_to_entities: vec!["User".to_string()],
790 validates_fields: vec!["email".to_string()],
791 rule_type: ValidationRuleType::FieldFormat,
792 parameters: HashMap::from([(
793 "pattern".to_string(),
794 r"^[^@]+@[^@]+\.[^@]+$".to_string(),
795 )]),
796 error_message: "Invalid email format".to_string(),
797 });
798
799 let mut framework = ValidationFramework::new(config);
800
801 let entity = GeneratedEntity {
802 entity_type: "User".to_string(),
803 primary_key: Some("1".to_string()),
804 field_values: HashMap::from([
805 ("id".to_string(), "1".to_string()),
806 ("email".to_string(), "invalid-email".to_string()),
807 ]),
808 endpoint: "/users".to_string(),
809 generated_at: SystemTime::now(),
810 };
811
812 framework.register_entity(entity).unwrap();
813
814 let result = framework.validate_all_entities();
815 assert!(!result.errors.is_empty());
816 assert_eq!(result.errors[0].error_type, ValidationErrorType::InvalidFormat);
817 }
818}