1use crate::error::{Result, SklearsComposeError};
12use serde::{Deserialize, Serialize};
13use std::collections::{HashMap, HashSet};
14use std::marker::PhantomData;
15use std::sync::{Arc, RwLock};
16use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
17
18#[derive(Debug)]
20pub struct CompileTimeValidator<State = Unvalidated> {
21 state: PhantomData<State>,
23
24 schema_validators: Arc<RwLock<HashMap<String, Box<dyn SchemaValidator>>>>,
26
27 constraint_validators: Arc<RwLock<Vec<Box<dyn ConstraintValidator>>>>,
29
30 dependency_validators: Arc<RwLock<Vec<Box<dyn DependencyValidator>>>>,
32
33 cross_reference_validators: Arc<RwLock<Vec<Box<dyn CrossReferenceValidator>>>>,
35
36 custom_validators: Arc<RwLock<Vec<Box<dyn CustomValidator>>>>,
38
39 config: ValidationConfig,
41
42 validation_cache: Arc<RwLock<HashMap<String, ValidationResult>>>,
44}
45
46#[derive(Debug, Clone)]
48pub struct Unvalidated;
49
50#[derive(Debug, Clone)]
51pub struct Validated;
52
53#[derive(Debug, Clone)]
54pub struct PartiallyValidated;
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ValidationConfig {
59 pub strict_type_checking: bool,
61
62 pub enable_constraint_validation: bool,
64
65 pub enable_dependency_validation: bool,
67
68 pub enable_cross_reference_validation: bool,
70
71 pub enable_custom_validation: bool,
73
74 pub max_validation_depth: usize,
76
77 pub validation_timeout: Duration,
79
80 pub enable_caching: bool,
82
83 pub fail_fast: bool,
85
86 pub detailed_reports: bool,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ValidationResult {
93 pub status: ValidationStatus,
95
96 pub errors: Vec<ValidationError>,
98
99 pub warnings: Vec<ValidationWarning>,
101
102 pub suggestions: Vec<ValidationSuggestion>,
104
105 pub metrics: ValidationMetrics,
107
108 pub timestamp: SystemTime,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub enum ValidationStatus {
115 Valid,
117 Invalid,
119 Warning,
121 Partial,
123 Unknown,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ValidationError {
130 pub error_id: String,
132
133 pub category: ValidationErrorCategory,
135
136 pub message: String,
138
139 pub location: ConfigurationLocation,
141
142 pub severity: ValidationSeverity,
144
145 pub expected: Option<String>,
147
148 pub actual: Option<String>,
150
151 pub suggestion: Option<String>,
153
154 pub related_errors: Vec<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub enum ValidationErrorCategory {
161 TypeMismatch,
163 ConstraintViolation,
165 MissingRequired,
167 InvalidFormat,
169 DependencyMissing,
171 CrossReferenceError,
173 CustomValidationFailure,
175 SchemaViolation,
177 RangeError,
179 CompatibilityError,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub enum ValidationSeverity {
186 Critical,
188 High,
190 Medium,
192 Low,
194 Info,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ConfigurationLocation {
201 pub section: String,
203
204 pub field_path: String,
206
207 pub line_number: Option<usize>,
209
210 pub column_number: Option<usize>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct ValidationWarning {
217 pub warning_id: String,
219
220 pub message: String,
222
223 pub location: ConfigurationLocation,
225
226 pub category: WarningCategory,
228
229 pub recommendation: Option<String>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub enum WarningCategory {
236 Performance,
238 Compatibility,
240 Deprecated,
242 Suboptimal,
244 Experimental,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ValidationSuggestion {
251 pub suggestion_id: String,
253
254 pub message: String,
256
257 pub action: SuggestionAction,
259
260 pub confidence: f64,
262
263 pub priority: SuggestionPriority,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub enum SuggestionAction {
270 AddField { field: String, value: String },
272 RemoveField { field: String },
274 ModifyField { field: String, new_value: String },
276 RestructureSection {
278 section: String,
279 new_structure: String,
280 },
281 AddDependency { dependency: String },
283 UpgradeVersion { component: String, version: String },
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub enum SuggestionPriority {
290 Critical,
292 High,
294 Medium,
296 Low,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct ValidationMetrics {
303 pub validation_time: Duration,
305
306 pub rules_checked: usize,
308
309 pub fields_validated: usize,
311
312 pub cache_hit_rate: f64,
314
315 pub memory_usage: u64,
317}
318
319#[derive(Debug)]
321pub struct TypeSafeConfigBuilder<State = Unbuilt> {
322 state: PhantomData<State>,
324
325 config_data: HashMap<String, ConfigValue>,
327
328 type_constraints: HashMap<String, TypeConstraint>,
330
331 validation_rules: Vec<Box<dyn ValidationRule>>,
333
334 builder_config: BuilderConfig,
336}
337
338#[derive(Debug, Clone)]
340pub struct Unbuilt;
341
342#[derive(Debug, Clone)]
343pub struct Built;
344
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub enum ConfigValue {
348 String(String),
350 Integer(i64),
352 Float(f64),
354 Boolean(bool),
356 Array(Vec<ConfigValue>),
358 Object(HashMap<String, ConfigValue>),
360 Null,
362}
363
364pub struct TypeConstraint {
366 pub expected_type: ConfigType,
368
369 pub required: bool,
371
372 pub default_value: Option<ConfigValue>,
374
375 pub constraints: Vec<ValueConstraint>,
377
378 pub custom_validator: Option<Box<dyn Fn(&ConfigValue) -> Result<()> + Send + Sync>>,
380}
381
382impl std::fmt::Debug for TypeConstraint {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 f.debug_struct("TypeConstraint")
385 .field("expected_type", &self.expected_type)
386 .field("required", &self.required)
387 .field("default_value", &self.default_value)
388 .field("constraints", &self.constraints)
389 .field(
390 "custom_validator",
391 &format!(
392 "<{} validator>",
393 if self.custom_validator.is_some() {
394 "some"
395 } else {
396 "none"
397 }
398 ),
399 )
400 .finish()
401 }
402}
403
404impl Clone for TypeConstraint {
405 fn clone(&self) -> Self {
406 Self {
407 expected_type: self.expected_type.clone(),
408 required: self.required,
409 default_value: self.default_value.clone(),
410 constraints: self.constraints.clone(),
411 custom_validator: None, }
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
418pub enum ConfigType {
419 String,
421 Integer,
423 Float,
425 Boolean,
427 Array(Box<ConfigType>),
429 Object(HashMap<String, ConfigType>),
431 Union(Vec<ConfigType>),
433 Optional(Box<ConfigType>),
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
439pub enum ValueConstraint {
440 Range { min: f64, max: f64 },
442 Length { min: usize, max: Option<usize> },
444 Pattern(String),
446 OneOf(Vec<ConfigValue>),
448 Custom(String), }
451
452#[derive(Debug, Clone)]
454pub struct BuilderConfig {
455 pub strict_validation: bool,
457
458 pub allow_unknown_fields: bool,
460
461 pub max_nesting_depth: usize,
463}
464
465pub trait SchemaValidator: std::fmt::Debug + Send + Sync {
467 fn validate_schema(
469 &self,
470 config: &HashMap<String, ConfigValue>,
471 ) -> Result<Vec<ValidationError>>;
472
473 fn schema_name(&self) -> &str;
475
476 fn schema_version(&self) -> &str;
478}
479
480pub trait ConstraintValidator: std::fmt::Debug + Send + Sync {
482 fn validate_constraints(
484 &self,
485 config: &HashMap<String, ConfigValue>,
486 ) -> Result<Vec<ValidationError>>;
487
488 fn validator_name(&self) -> &str;
490}
491
492pub trait DependencyValidator: std::fmt::Debug + Send + Sync {
494 fn validate_dependencies(
496 &self,
497 config: &HashMap<String, ConfigValue>,
498 ) -> Result<Vec<ValidationError>>;
499
500 fn validator_name(&self) -> &str;
502}
503
504pub trait CrossReferenceValidator: std::fmt::Debug + Send + Sync {
506 fn validate_cross_references(
508 &self,
509 config: &HashMap<String, ConfigValue>,
510 ) -> Result<Vec<ValidationError>>;
511
512 fn validator_name(&self) -> &str;
514}
515
516pub trait CustomValidator: std::fmt::Debug + Send + Sync {
518 fn validate(&self, config: &HashMap<String, ConfigValue>) -> Result<Vec<ValidationError>>;
520
521 fn validator_name(&self) -> &str;
523
524 fn description(&self) -> &str;
526}
527
528pub trait ValidationRule: std::fmt::Debug + Send + Sync {
530 fn apply(&self, field: &str, value: &ConfigValue) -> Result<()>;
532
533 fn rule_name(&self) -> &str;
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct PipelineConfigurationSchema {
540 pub version: String,
542
543 pub required_fields: HashSet<String>,
545
546 pub field_definitions: HashMap<String, FieldDefinition>,
548
549 pub constraints: Vec<SchemaConstraint>,
551
552 pub cross_reference_rules: Vec<CrossReferenceRule>,
554}
555
556#[derive(Debug, Clone, Serialize, Deserialize)]
558pub struct FieldDefinition {
559 pub field_type: ConfigType,
561
562 pub description: String,
564
565 pub default: Option<ConfigValue>,
567
568 pub constraints: Vec<ValueConstraint>,
570
571 pub deprecated: bool,
573
574 pub deprecation_message: Option<String>,
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
580pub struct SchemaConstraint {
581 pub name: String,
583
584 pub description: String,
586
587 pub fields: Vec<String>,
589
590 pub constraint_type: SchemaConstraintType,
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize)]
596pub enum SchemaConstraintType {
597 MutualExclusion,
599 RequiredTogether,
601 ConditionalRequired {
603 condition: String,
604 required: Vec<String>,
605 },
606 ValueDependency {
608 field: String,
609 dependent_field: String,
610 values: Vec<ConfigValue>,
611 },
612}
613
614#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct CrossReferenceRule {
617 pub name: String,
619
620 pub source_field: String,
622
623 pub target_field: String,
625
626 pub reference_type: ReferenceType,
628
629 pub validation_rule: String,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
635pub enum ReferenceType {
636 ForeignKey,
638 WeakReference,
640 StrongReference,
642 Computed,
644}
645
646#[derive(Debug)]
648pub struct ValidatedPipelineConfig<T> {
649 config: T,
651
652 validation_proof: ValidationProof,
654
655 schema_version: String,
657}
658
659#[derive(Debug, Clone)]
661pub struct ValidationProof {
662 proof_id: String,
664
665 validation_time: SystemTime,
667
668 validator_version: String,
670
671 checksum: String,
673}
674
675pub struct ValidConfigMarker<T>(PhantomData<T>);
677
678impl Default for ValidationConfig {
679 fn default() -> Self {
680 Self {
681 strict_type_checking: true,
682 enable_constraint_validation: true,
683 enable_dependency_validation: true,
684 enable_cross_reference_validation: true,
685 enable_custom_validation: true,
686 max_validation_depth: 10,
687 validation_timeout: Duration::from_secs(30),
688 enable_caching: true,
689 fail_fast: false,
690 detailed_reports: true,
691 }
692 }
693}
694
695impl<State> CompileTimeValidator<State> {
696 #[must_use]
698 pub fn new() -> CompileTimeValidator<Unvalidated> {
699 Self::with_config(ValidationConfig::default())
700 }
701
702 #[must_use]
704 pub fn with_config(config: ValidationConfig) -> CompileTimeValidator<Unvalidated> {
705 CompileTimeValidator {
707 state: PhantomData,
708 schema_validators: Arc::new(RwLock::new(HashMap::new())),
709 constraint_validators: Arc::new(RwLock::new(Vec::new())),
710 dependency_validators: Arc::new(RwLock::new(Vec::new())),
711 cross_reference_validators: Arc::new(RwLock::new(Vec::new())),
712 custom_validators: Arc::new(RwLock::new(Vec::new())),
713 config,
714 validation_cache: Arc::new(RwLock::new(HashMap::new())),
715 }
716 }
717}
718
719impl CompileTimeValidator<Unvalidated> {
720 #[must_use]
722 pub fn add_schema_validator(self, validator: Box<dyn SchemaValidator>) -> Self {
723 let schema_name = validator.schema_name().to_string();
724 self.schema_validators
725 .write()
726 .unwrap_or_else(|e| e.into_inner())
727 .insert(schema_name, validator);
728 self
729 }
730
731 #[must_use]
733 pub fn add_constraint_validator(self, validator: Box<dyn ConstraintValidator>) -> Self {
734 self.constraint_validators
735 .write()
736 .unwrap_or_else(|e| e.into_inner())
737 .push(validator);
738 self
739 }
740
741 #[must_use]
743 pub fn add_dependency_validator(self, validator: Box<dyn DependencyValidator>) -> Self {
744 self.dependency_validators
745 .write()
746 .unwrap_or_else(|e| e.into_inner())
747 .push(validator);
748 self
749 }
750
751 #[must_use]
753 pub fn add_cross_reference_validator(
754 self,
755 validator: Box<dyn CrossReferenceValidator>,
756 ) -> Self {
757 self.cross_reference_validators
758 .write()
759 .unwrap_or_else(|e| e.into_inner())
760 .push(validator);
761 self
762 }
763
764 #[must_use]
766 pub fn add_custom_validator(self, validator: Box<dyn CustomValidator>) -> Self {
767 self.custom_validators
768 .write()
769 .unwrap_or_else(|e| e.into_inner())
770 .push(validator);
771 self
772 }
773
774 pub fn validate(
776 self,
777 config: &HashMap<String, ConfigValue>,
778 ) -> Result<(CompileTimeValidator<Validated>, ValidationResult)> {
779 let start_time = Instant::now();
780 let mut errors = Vec::new();
781 let warnings = Vec::new();
782 let mut suggestions = Vec::new();
783 let mut rules_checked = 0;
784
785 let config_hash = self.compute_config_hash(config);
787 if self.config.enable_caching {
788 if let Some(cached_result) = self
789 .validation_cache
790 .read()
791 .unwrap_or_else(|e| e.into_inner())
792 .get(&config_hash)
793 {
794 let validation_cache_clone = Arc::clone(&self.validation_cache);
795 return Ok((
796 CompileTimeValidator {
798 state: PhantomData,
799 schema_validators: self.schema_validators,
800 constraint_validators: self.constraint_validators,
801 dependency_validators: self.dependency_validators,
802 cross_reference_validators: self.cross_reference_validators,
803 custom_validators: self.custom_validators,
804 config: self.config,
805 validation_cache: validation_cache_clone,
806 },
807 cached_result.clone(),
808 ));
809 }
810 }
811
812 if !self
814 .schema_validators
815 .read()
816 .unwrap_or_else(|e| e.into_inner())
817 .is_empty()
818 {
819 for validator in self
820 .schema_validators
821 .read()
822 .unwrap_or_else(|e| e.into_inner())
823 .values()
824 {
825 match validator.validate_schema(config) {
826 Ok(mut schema_errors) => {
827 errors.append(&mut schema_errors);
828 rules_checked += 1;
829 }
830 Err(e) => {
831 errors.push(ValidationError {
832 error_id: format!("schema_error_{rules_checked}"),
833 category: ValidationErrorCategory::SchemaViolation,
834 message: e.to_string(),
835 location: ConfigurationLocation {
836 section: "schema".to_string(),
837 field_path: "root".to_string(),
838 line_number: None,
839 column_number: None,
840 },
841 severity: ValidationSeverity::Critical,
842 expected: None,
843 actual: None,
844 suggestion: Some(
845 "Check schema definition and configuration format".to_string(),
846 ),
847 related_errors: Vec::new(),
848 });
849 }
850 }
851
852 if self.config.fail_fast && !errors.is_empty() {
853 break;
854 }
855 }
856 }
857
858 if self.config.enable_constraint_validation && (errors.is_empty() || !self.config.fail_fast)
860 {
861 for validator in self
862 .constraint_validators
863 .read()
864 .unwrap_or_else(|e| e.into_inner())
865 .iter()
866 {
867 match validator.validate_constraints(config) {
868 Ok(mut constraint_errors) => {
869 errors.append(&mut constraint_errors);
870 rules_checked += 1;
871 }
872 Err(e) => {
873 errors.push(ValidationError {
874 error_id: format!("constraint_error_{rules_checked}"),
875 category: ValidationErrorCategory::ConstraintViolation,
876 message: e.to_string(),
877 location: ConfigurationLocation {
878 section: "constraints".to_string(),
879 field_path: "unknown".to_string(),
880 line_number: None,
881 column_number: None,
882 },
883 severity: ValidationSeverity::High,
884 expected: None,
885 actual: None,
886 suggestion: Some(
887 "Review parameter constraints and valid ranges".to_string(),
888 ),
889 related_errors: Vec::new(),
890 });
891 }
892 }
893
894 if self.config.fail_fast && !errors.is_empty() {
895 break;
896 }
897 }
898 }
899
900 if self.config.enable_dependency_validation && (errors.is_empty() || !self.config.fail_fast)
902 {
903 for validator in self
904 .dependency_validators
905 .read()
906 .unwrap_or_else(|e| e.into_inner())
907 .iter()
908 {
909 match validator.validate_dependencies(config) {
910 Ok(mut dependency_errors) => {
911 errors.append(&mut dependency_errors);
912 rules_checked += 1;
913 }
914 Err(e) => {
915 errors.push(ValidationError {
916 error_id: format!("dependency_error_{rules_checked}"),
917 category: ValidationErrorCategory::DependencyMissing,
918 message: e.to_string(),
919 location: ConfigurationLocation {
920 section: "dependencies".to_string(),
921 field_path: "unknown".to_string(),
922 line_number: None,
923 column_number: None,
924 },
925 severity: ValidationSeverity::High,
926 expected: None,
927 actual: None,
928 suggestion: Some(
929 "Check that all required dependencies are configured".to_string(),
930 ),
931 related_errors: Vec::new(),
932 });
933 }
934 }
935
936 if self.config.fail_fast && !errors.is_empty() {
937 break;
938 }
939 }
940 }
941
942 if self.config.enable_cross_reference_validation
944 && (errors.is_empty() || !self.config.fail_fast)
945 {
946 for validator in self
947 .cross_reference_validators
948 .read()
949 .unwrap_or_else(|e| e.into_inner())
950 .iter()
951 {
952 match validator.validate_cross_references(config) {
953 Ok(mut cross_ref_errors) => {
954 errors.append(&mut cross_ref_errors);
955 rules_checked += 1;
956 }
957 Err(e) => {
958 errors.push(ValidationError {
959 error_id: format!("cross_ref_error_{rules_checked}"),
960 category: ValidationErrorCategory::CrossReferenceError,
961 message: e.to_string(),
962 location: ConfigurationLocation {
963 section: "cross_references".to_string(),
964 field_path: "unknown".to_string(),
965 line_number: None,
966 column_number: None,
967 },
968 severity: ValidationSeverity::Medium,
969 expected: None,
970 actual: None,
971 suggestion: Some(
972 "Check cross-references between configuration sections".to_string(),
973 ),
974 related_errors: Vec::new(),
975 });
976 }
977 }
978
979 if self.config.fail_fast && !errors.is_empty() {
980 break;
981 }
982 }
983 }
984
985 if self.config.enable_custom_validation && (errors.is_empty() || !self.config.fail_fast) {
987 for validator in self
988 .custom_validators
989 .read()
990 .unwrap_or_else(|e| e.into_inner())
991 .iter()
992 {
993 match validator.validate(config) {
994 Ok(mut custom_errors) => {
995 errors.append(&mut custom_errors);
996 rules_checked += 1;
997 }
998 Err(e) => {
999 errors.push(ValidationError {
1000 error_id: format!("custom_error_{rules_checked}"),
1001 category: ValidationErrorCategory::CustomValidationFailure,
1002 message: e.to_string(),
1003 location: ConfigurationLocation {
1004 section: "custom".to_string(),
1005 field_path: "unknown".to_string(),
1006 line_number: None,
1007 column_number: None,
1008 },
1009 severity: ValidationSeverity::Medium,
1010 expected: None,
1011 actual: None,
1012 suggestion: Some("Review custom validation rules".to_string()),
1013 related_errors: Vec::new(),
1014 });
1015 }
1016 }
1017
1018 if self.config.fail_fast && !errors.is_empty() {
1019 break;
1020 }
1021 }
1022 }
1023
1024 suggestions.extend(self.generate_suggestions(&errors, config));
1026
1027 let status = if !errors.is_empty() {
1029 ValidationStatus::Invalid
1030 } else if !warnings.is_empty() {
1031 ValidationStatus::Warning
1032 } else {
1033 ValidationStatus::Valid
1034 };
1035
1036 let validation_time = start_time.elapsed();
1038 let result = ValidationResult {
1039 status,
1040 errors,
1041 warnings,
1042 suggestions,
1043 metrics: ValidationMetrics {
1044 validation_time,
1045 rules_checked,
1046 fields_validated: config.len(),
1047 cache_hit_rate: 0.0, memory_usage: 0, },
1050 timestamp: SystemTime::now(),
1051 };
1052
1053 if self.config.enable_caching {
1055 self.validation_cache
1056 .write()
1057 .unwrap_or_else(|e| e.into_inner())
1058 .insert(config_hash, result.clone());
1059 }
1060
1061 let validated_validator = CompileTimeValidator {
1063 state: PhantomData,
1064 schema_validators: self.schema_validators,
1065 constraint_validators: self.constraint_validators,
1066 dependency_validators: self.dependency_validators,
1067 cross_reference_validators: self.cross_reference_validators,
1068 custom_validators: self.custom_validators,
1069 config: self.config,
1070 validation_cache: self.validation_cache,
1071 };
1072
1073 Ok((validated_validator, result))
1074 }
1075
1076 fn compute_config_hash(&self, config: &HashMap<String, ConfigValue>) -> String {
1078 format!("hash_{}", config.len())
1080 }
1081
1082 fn generate_suggestions(
1083 &self,
1084 errors: &[ValidationError],
1085 config: &HashMap<String, ConfigValue>,
1086 ) -> Vec<ValidationSuggestion> {
1087 let mut suggestions = Vec::new();
1088
1089 for error in errors {
1090 match &error.category {
1091 ValidationErrorCategory::MissingRequired => {
1092 suggestions.push(ValidationSuggestion {
1093 suggestion_id: format!("add_required_{}", error.error_id),
1094 message: format!("Add required field '{}'", error.location.field_path),
1095 action: SuggestionAction::AddField {
1096 field: error.location.field_path.clone(),
1097 value: error
1098 .expected
1099 .clone()
1100 .unwrap_or_else(|| "default_value".to_string()),
1101 },
1102 confidence: 0.9,
1103 priority: SuggestionPriority::Critical,
1104 });
1105 }
1106 ValidationErrorCategory::TypeMismatch => {
1107 if let Some(expected) = &error.expected {
1108 suggestions.push(ValidationSuggestion {
1109 suggestion_id: format!("fix_type_{}", error.error_id),
1110 message: format!(
1111 "Convert field '{}' to type '{}'",
1112 error.location.field_path, expected
1113 ),
1114 action: SuggestionAction::ModifyField {
1115 field: error.location.field_path.clone(),
1116 new_value: format!("convert_to_{expected}"),
1117 },
1118 confidence: 0.8,
1119 priority: SuggestionPriority::High,
1120 });
1121 }
1122 }
1123 _ => {}
1124 }
1125 }
1126
1127 suggestions
1128 }
1129}
1130
1131impl CompileTimeValidator<Validated> {
1132 pub fn create_validated_config<T>(&self, config: T) -> ValidatedPipelineConfig<T> {
1134 let validation_proof = ValidationProof {
1135 proof_id: format!(
1136 "proof_{}",
1137 SystemTime::now()
1138 .duration_since(UNIX_EPOCH)
1139 .unwrap_or_default()
1140 .as_millis()
1141 ),
1142 validation_time: SystemTime::now(),
1143 validator_version: "1.0.0".to_string(),
1144 checksum: "validated".to_string(),
1145 };
1146
1147 ValidatedPipelineConfig {
1149 config,
1150 validation_proof,
1151 schema_version: "1.0.0".to_string(),
1152 }
1153 }
1154
1155 #[must_use]
1157 pub fn get_validation_metrics(&self) -> ValidationMetrics {
1158 ValidationMetrics {
1160 validation_time: Duration::from_millis(0),
1161 rules_checked: 0,
1162 fields_validated: 0,
1163 cache_hit_rate: 0.0,
1164 memory_usage: 0,
1165 }
1166 }
1167}
1168
1169impl<T> ValidatedPipelineConfig<T> {
1170 pub fn config(&self) -> &T {
1172 &self.config
1173 }
1174
1175 pub fn validation_proof(&self) -> &ValidationProof {
1177 &self.validation_proof
1178 }
1179
1180 pub fn map<U, F>(self, f: F) -> ValidatedPipelineConfig<U>
1182 where
1183 F: FnOnce(T) -> U,
1184 {
1185 ValidatedPipelineConfig {
1187 config: f(self.config),
1188 validation_proof: self.validation_proof,
1189 schema_version: self.schema_version,
1190 }
1191 }
1192}
1193
1194impl Default for TypeSafeConfigBuilder<Unbuilt> {
1197 fn default() -> Self {
1198 Self::new()
1199 }
1200}
1201
1202impl TypeSafeConfigBuilder<Unbuilt> {
1203 #[must_use]
1205 pub fn new() -> Self {
1206 Self {
1207 state: PhantomData,
1208 config_data: HashMap::new(),
1209 type_constraints: HashMap::new(),
1210 validation_rules: Vec::new(),
1211 builder_config: BuilderConfig {
1212 strict_validation: true,
1213 allow_unknown_fields: false,
1214 max_nesting_depth: 5,
1215 },
1216 }
1217 }
1218
1219 pub fn string_field<T: Into<String>>(mut self, name: &str, value: T) -> Self {
1221 self.config_data
1222 .insert(name.to_string(), ConfigValue::String(value.into()));
1223 self.type_constraints.insert(
1224 name.to_string(),
1225 TypeConstraint {
1227 expected_type: ConfigType::String,
1228 required: true,
1229 default_value: None,
1230 constraints: Vec::new(),
1231 custom_validator: None,
1232 },
1233 );
1234 self
1235 }
1236
1237 #[must_use]
1239 pub fn integer_field(mut self, name: &str, value: i64) -> Self {
1240 self.config_data
1241 .insert(name.to_string(), ConfigValue::Integer(value));
1242 self.type_constraints.insert(
1243 name.to_string(),
1244 TypeConstraint {
1246 expected_type: ConfigType::Integer,
1247 required: true,
1248 default_value: None,
1249 constraints: Vec::new(),
1250 custom_validator: None,
1251 },
1252 );
1253 self
1254 }
1255
1256 #[must_use]
1258 pub fn float_field(mut self, name: &str, value: f64) -> Self {
1259 self.config_data
1260 .insert(name.to_string(), ConfigValue::Float(value));
1261 self.type_constraints.insert(
1262 name.to_string(),
1263 TypeConstraint {
1265 expected_type: ConfigType::Float,
1266 required: true,
1267 default_value: None,
1268 constraints: Vec::new(),
1269 custom_validator: None,
1270 },
1271 );
1272 self
1273 }
1274
1275 #[must_use]
1277 pub fn boolean_field(mut self, name: &str, value: bool) -> Self {
1278 self.config_data
1279 .insert(name.to_string(), ConfigValue::Boolean(value));
1280 self.type_constraints.insert(
1281 name.to_string(),
1282 TypeConstraint {
1284 expected_type: ConfigType::Boolean,
1285 required: true,
1286 default_value: None,
1287 constraints: Vec::new(),
1288 custom_validator: None,
1289 },
1290 );
1291 self
1292 }
1293
1294 #[must_use]
1296 pub fn with_constraint(mut self, field: &str, constraint: ValueConstraint) -> Self {
1297 if let Some(type_constraint) = self.type_constraints.get_mut(field) {
1298 type_constraint.constraints.push(constraint);
1299 }
1300 self
1301 }
1302
1303 #[must_use]
1305 pub fn with_rule(mut self, rule: Box<dyn ValidationRule>) -> Self {
1306 self.validation_rules.push(rule);
1307 self
1308 }
1309
1310 pub fn build(self) -> Result<TypeSafeConfigBuilder<Built>> {
1312 for (field, constraint) in &self.type_constraints {
1314 if let Some(value) = self.config_data.get(field) {
1315 if !self.is_type_compatible(value, &constraint.expected_type) {
1317 return Err(SklearsComposeError::InvalidConfiguration(format!(
1318 "Type mismatch for field '{}': expected {:?}, got {:?}",
1319 field, constraint.expected_type, value
1320 )));
1321 }
1322
1323 for value_constraint in &constraint.constraints {
1325 self.validate_constraint(field, value, value_constraint)?;
1326 }
1327
1328 if let Some(ref validator) = constraint.custom_validator {
1330 validator(value)
1331 .map_err(|e| SklearsComposeError::InvalidConfiguration(e.to_string()))?;
1332 }
1333 } else if constraint.required {
1334 return Err(SklearsComposeError::InvalidConfiguration(format!(
1335 "Required field '{field}' is missing"
1336 )));
1337 }
1338 }
1339
1340 for rule in &self.validation_rules {
1342 for (field, value) in &self.config_data {
1343 rule.apply(field, value)
1344 .map_err(|e| SklearsComposeError::InvalidConfiguration(e.to_string()))?;
1345 }
1346 }
1347
1348 Ok(TypeSafeConfigBuilder {
1349 state: PhantomData,
1350 config_data: self.config_data,
1351 type_constraints: self.type_constraints,
1352 validation_rules: self.validation_rules,
1353 builder_config: self.builder_config,
1354 })
1355 }
1356
1357 fn is_type_compatible(&self, value: &ConfigValue, expected_type: &ConfigType) -> bool {
1359 match (value, expected_type) {
1360 (ConfigValue::String(_), ConfigType::String) => true,
1361 (ConfigValue::Integer(_), ConfigType::Integer) => true,
1362 (ConfigValue::Float(_), ConfigType::Float) => true,
1363 (ConfigValue::Boolean(_), ConfigType::Boolean) => true,
1364 (ConfigValue::Array(arr), ConfigType::Array(element_type)) => arr
1365 .iter()
1366 .all(|item| self.is_type_compatible(item, element_type)),
1367 (ConfigValue::Object(_), ConfigType::Object(_)) => true,
1368 (ConfigValue::Null, ConfigType::Optional(_)) => true,
1369 _ => false,
1370 }
1371 }
1372
1373 fn validate_constraint(
1374 &self,
1375 field: &str,
1376 value: &ConfigValue,
1377 constraint: &ValueConstraint,
1378 ) -> Result<()> {
1379 match constraint {
1380 ValueConstraint::Range { min, max } => {
1381 let numeric_value = match value {
1382 ConfigValue::Integer(i) => *i as f64,
1383 ConfigValue::Float(f) => *f,
1384 _ => {
1385 return Err(SklearsComposeError::InvalidConfiguration(format!(
1386 "Range constraint can only be applied to numeric values in field '{field}'"
1387 )));
1388 }
1389 };
1390
1391 if numeric_value < *min || numeric_value > *max {
1392 return Err(SklearsComposeError::InvalidConfiguration(format!(
1393 "Value {numeric_value} in field '{field}' is outside range [{min}, {max}]"
1394 )));
1395 }
1396 }
1397 ValueConstraint::Length { min, max } => {
1398 let length = match value {
1399 ConfigValue::String(s) => s.len(),
1400 ConfigValue::Array(arr) => arr.len(),
1401 _ => {
1402 return Err(SklearsComposeError::InvalidConfiguration(
1403 format!("Length constraint can only be applied to strings or arrays in field '{field}'")
1404 ));
1405 }
1406 };
1407
1408 if length < *min {
1409 return Err(SklearsComposeError::InvalidConfiguration(format!(
1410 "Length {length} in field '{field}' is less than minimum {min}"
1411 )));
1412 }
1413
1414 if let Some(max_len) = max {
1415 if length > *max_len {
1416 return Err(SklearsComposeError::InvalidConfiguration(format!(
1417 "Length {length} in field '{field}' is greater than maximum {max_len}"
1418 )));
1419 }
1420 }
1421 }
1422 ValueConstraint::OneOf(allowed_values) => {
1423 if !allowed_values.contains(value) {
1424 return Err(SklearsComposeError::InvalidConfiguration(format!(
1425 "Value in field '{field}' is not one of the allowed values"
1426 )));
1427 }
1428 }
1429 ValueConstraint::Pattern(pattern) => {
1430 if let ConfigValue::String(s) = value {
1431 if !s.contains(pattern) {
1433 return Err(SklearsComposeError::InvalidConfiguration(format!(
1434 "String in field '{field}' does not match pattern '{pattern}'"
1435 )));
1436 }
1437 } else {
1438 return Err(SklearsComposeError::InvalidConfiguration(format!(
1439 "Pattern constraint can only be applied to strings in field '{field}'"
1440 )));
1441 }
1442 }
1443 ValueConstraint::Custom(description) => {
1444 println!("Custom constraint '{description}' applied to field '{field}'");
1447 }
1448 }
1449
1450 Ok(())
1451 }
1452}
1453
1454impl TypeSafeConfigBuilder<Built> {
1455 #[must_use]
1457 pub fn config_data(&self) -> &HashMap<String, ConfigValue> {
1458 &self.config_data
1459 }
1460
1461 #[must_use]
1463 pub fn into_config_data(self) -> HashMap<String, ConfigValue> {
1464 self.config_data
1465 }
1466}
1467
1468#[derive(Debug)]
1472pub struct PipelineSchemaValidator {
1473 schema: PipelineConfigurationSchema,
1474}
1475
1476impl Default for PipelineSchemaValidator {
1477 fn default() -> Self {
1478 Self::new()
1479 }
1480}
1481
1482impl PipelineSchemaValidator {
1483 #[must_use]
1484 pub fn new() -> Self {
1485 let mut required_fields = HashSet::new();
1486 required_fields.insert("model".to_string());
1487 required_fields.insert("pipeline".to_string());
1488
1489 let mut field_definitions = HashMap::new();
1490 field_definitions.insert(
1491 "model".to_string(),
1492 FieldDefinition {
1494 field_type: ConfigType::Object(HashMap::new()),
1495 description: "Model configuration".to_string(),
1496 default: None,
1497 constraints: Vec::new(),
1498 deprecated: false,
1499 deprecation_message: None,
1500 },
1501 );
1502
1503 let schema = PipelineConfigurationSchema {
1504 version: "1.0.0".to_string(),
1505 required_fields,
1506 field_definitions,
1507 constraints: Vec::new(),
1508 cross_reference_rules: Vec::new(),
1509 };
1510
1511 Self { schema }
1512 }
1513}
1514
1515impl SchemaValidator for PipelineSchemaValidator {
1516 fn validate_schema(
1517 &self,
1518 config: &HashMap<String, ConfigValue>,
1519 ) -> Result<Vec<ValidationError>> {
1520 let mut errors = Vec::new();
1521
1522 for required_field in &self.schema.required_fields {
1524 if !config.contains_key(required_field) {
1525 errors.push(ValidationError {
1526 error_id: format!("missing_required_{required_field}"),
1527 category: ValidationErrorCategory::MissingRequired,
1528 message: format!("Required field '{required_field}' is missing"),
1529 location: ConfigurationLocation {
1530 section: "root".to_string(),
1531 field_path: required_field.clone(),
1532 line_number: None,
1533 column_number: None,
1534 },
1535 severity: ValidationSeverity::Critical,
1536 expected: Some("required field".to_string()),
1537 actual: Some("missing".to_string()),
1538 suggestion: Some(format!("Add required field '{required_field}'")),
1539 related_errors: Vec::new(),
1540 });
1541 }
1542 }
1543
1544 Ok(errors)
1545 }
1546
1547 fn schema_name(&self) -> &'static str {
1548 "PipelineSchema"
1549 }
1550
1551 fn schema_version(&self) -> &str {
1552 &self.schema.version
1553 }
1554}
1555
1556#[derive(Debug)]
1558pub struct ParameterConstraintValidator;
1559
1560impl ConstraintValidator for ParameterConstraintValidator {
1561 fn validate_constraints(
1562 &self,
1563 config: &HashMap<String, ConfigValue>,
1564 ) -> Result<Vec<ValidationError>> {
1565 let mut errors = Vec::new();
1566
1567 if let Some(ConfigValue::Object(model_config)) = config.get("model") {
1569 if let Some(ConfigValue::Float(learning_rate)) = model_config.get("learning_rate") {
1570 if *learning_rate <= 0.0 || *learning_rate > 1.0 {
1571 errors.push(ValidationError {
1572 error_id: "invalid_learning_rate".to_string(),
1573 category: ValidationErrorCategory::RangeError,
1574 message: "Learning rate must be between 0.0 and 1.0".to_string(),
1575 location: ConfigurationLocation {
1576 section: "model".to_string(),
1577 field_path: "learning_rate".to_string(),
1578 line_number: None,
1579 column_number: None,
1580 },
1581 severity: ValidationSeverity::High,
1582 expected: Some("0.0 < learning_rate <= 1.0".to_string()),
1583 actual: Some(learning_rate.to_string()),
1584 suggestion: Some(
1585 "Set learning rate to a value between 0.001 and 0.1".to_string(),
1586 ),
1587 related_errors: Vec::new(),
1588 });
1589 }
1590 }
1591 }
1592
1593 Ok(errors)
1594 }
1595
1596 fn validator_name(&self) -> &'static str {
1597 "ParameterConstraintValidator"
1598 }
1599}
1600
1601#[derive(Debug)]
1603pub struct NonEmptyStringRule;
1604
1605impl ValidationRule for NonEmptyStringRule {
1606 fn apply(&self, field: &str, value: &ConfigValue) -> Result<()> {
1607 if let ConfigValue::String(s) = value {
1608 if s.is_empty() {
1609 return Err(format!("String field '{field}' cannot be empty").into());
1610 }
1611 }
1612 Ok(())
1613 }
1614
1615 fn rule_name(&self) -> &'static str {
1616 "NonEmptyStringRule"
1617 }
1618}
1619
1620#[macro_export]
1624macro_rules! enhanced_validated_config {
1625 ($($key:expr => $value:expr),*) => {{
1626 let mut config = std::collections::HashMap::new();
1627 $(
1628 config.insert($key.to_string(), $value);
1629 )*
1630 config
1631 }};
1632}
1633
1634#[macro_export]
1636macro_rules! type_safe_config {
1637 (
1638 $($field:ident: $type:ident = $value:expr),*
1639 ) => {{
1640 let builder = TypeSafeConfigBuilder::new();
1641 $(
1642 let builder = match stringify!($type) {
1643 "String" => builder.string_field(stringify!($field), $value),
1644 "Integer" => builder.integer_field(stringify!($field), $value),
1645 "Float" => builder.float_field(stringify!($field), $value),
1646 "Boolean" => builder.boolean_field(stringify!($field), $value),
1647 _ => panic!("Unsupported type: {}", stringify!($type)),
1648 };
1649 )*
1650 builder.build()
1651 }};
1652}
1653
1654#[allow(non_snake_case)]
1655#[cfg(test)]
1656mod tests {
1657 use super::*;
1658
1659 #[test]
1660 fn test_validator_creation() {
1661 let validator = CompileTimeValidator::<Unvalidated>::new();
1662 assert!(validator.config.strict_type_checking);
1663 }
1664
1665 #[test]
1666 fn test_schema_validation() {
1667 let validator = CompileTimeValidator::<Unvalidated>::new()
1668 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1669
1670 let mut config = HashMap::new();
1671 config.insert("model".to_string(), ConfigValue::Object(HashMap::new()));
1672 config.insert("pipeline".to_string(), ConfigValue::Object(HashMap::new()));
1673
1674 let result = validator.validate(&config);
1675 assert!(result.is_ok());
1676
1677 let (_, validation_result) = result.expect("operation should succeed");
1678 assert!(matches!(validation_result.status, ValidationStatus::Valid));
1679 }
1680
1681 #[test]
1682 fn test_constraint_validation() {
1683 let validator = CompileTimeValidator::<Unvalidated>::new()
1684 .add_constraint_validator(Box::new(ParameterConstraintValidator));
1685
1686 let mut model_config = HashMap::new();
1687 model_config.insert("learning_rate".to_string(), ConfigValue::Float(2.0)); let mut config = HashMap::new();
1690 config.insert("model".to_string(), ConfigValue::Object(model_config));
1691
1692 let result = validator.validate(&config);
1693 assert!(result.is_ok());
1694
1695 let (_, validation_result) = result.expect("operation should succeed");
1696 assert!(matches!(
1697 validation_result.status,
1698 ValidationStatus::Invalid
1699 ));
1700 assert!(!validation_result.errors.is_empty());
1701 }
1702
1703 #[test]
1704 fn test_type_safe_builder() {
1705 let builder = TypeSafeConfigBuilder::new()
1706 .string_field("name", "test_pipeline")
1707 .float_field("learning_rate", 0.01)
1708 .integer_field("epochs", 100)
1709 .boolean_field("verbose", true)
1710 .with_constraint(
1711 "learning_rate",
1712 ValueConstraint::Range { min: 0.0, max: 1.0 },
1713 )
1714 .with_rule(Box::new(NonEmptyStringRule));
1715
1716 let result = builder.build();
1717 assert!(result.is_ok());
1718
1719 let built_config = result.expect("operation should succeed");
1720 assert_eq!(built_config.config_data().len(), 4);
1721 }
1722
1723 #[test]
1724 fn test_validated_config_creation() {
1725 let validator = CompileTimeValidator::<Unvalidated>::new();
1726 let config = HashMap::new();
1727
1728 let (validated_validator, _) = validator
1729 .validate(&config)
1730 .expect("operation should succeed");
1731 let validated_config = validated_validator.create_validated_config(config);
1732
1733 assert!(validated_config
1734 .validation_proof()
1735 .proof_id
1736 .starts_with("proof_"));
1737 }
1738
1739 #[test]
1740 fn test_validation_errors() {
1741 let validator = CompileTimeValidator::<Unvalidated>::new()
1742 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1743
1744 let config = HashMap::new(); let result = validator.validate(&config);
1747 assert!(result.is_ok());
1748
1749 let (_, validation_result) = result.expect("operation should succeed");
1750 assert!(matches!(
1751 validation_result.status,
1752 ValidationStatus::Invalid
1753 ));
1754 assert!(validation_result.errors.len() >= 2); }
1756
1757 #[test]
1758 fn test_config_value_types() {
1759 assert!(matches!(
1760 ConfigValue::String("test".to_string()),
1761 ConfigValue::String(_)
1762 ));
1763 assert!(matches!(ConfigValue::Integer(42), ConfigValue::Integer(_)));
1764 assert!(matches!(ConfigValue::Float(3.14), ConfigValue::Float(_)));
1765 assert!(matches!(
1766 ConfigValue::Boolean(true),
1767 ConfigValue::Boolean(_)
1768 ));
1769 }
1770
1771 #[test]
1772 fn test_validation_suggestions() {
1773 let validator = CompileTimeValidator::<Unvalidated>::new()
1774 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1775
1776 let config = HashMap::new();
1777
1778 let (_, validation_result) = validator
1779 .validate(&config)
1780 .expect("operation should succeed");
1781 assert!(!validation_result.suggestions.is_empty());
1782
1783 let suggestion = &validation_result.suggestions[0];
1784 assert!(matches!(suggestion.priority, SuggestionPriority::Critical));
1785 }
1786
1787 #[test]
1788 fn test_constraint_range() {
1789 let builder = TypeSafeConfigBuilder::new()
1790 .float_field("value", 150.0)
1791 .with_constraint(
1792 "value",
1793 ValueConstraint::Range {
1794 min: 0.0,
1795 max: 100.0,
1796 },
1797 );
1798
1799 let result = builder.build();
1800 assert!(result.is_err());
1801 }
1802
1803 #[test]
1804 fn test_constraint_length() {
1805 let builder = TypeSafeConfigBuilder::new()
1806 .string_field("name", "a") .with_constraint(
1808 "name",
1809 ValueConstraint::Length {
1810 min: 3,
1811 max: Some(10),
1812 },
1813 );
1814
1815 let result = builder.build();
1816 assert!(result.is_err());
1817 }
1818}