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::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 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 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 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 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 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 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 fn check_data_consistency(&self, result: &mut ValidationResult) {
507 self.check_orphaned_entities(result);
509
510 self.check_performance_concerns(result);
512 }
513
514 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 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 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 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#[derive(Debug, Clone)]
585pub struct ValidationStatistics {
586 pub total_entities: usize,
588 pub entity_type_count: usize,
590 pub indexed_foreign_keys: usize,
592 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 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}