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()
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.write().unwrap().push(validator);
735 self
736 }
737
738 #[must_use]
740 pub fn add_dependency_validator(self, validator: Box<dyn DependencyValidator>) -> Self {
741 self.dependency_validators.write().unwrap().push(validator);
742 self
743 }
744
745 #[must_use]
747 pub fn add_cross_reference_validator(
748 self,
749 validator: Box<dyn CrossReferenceValidator>,
750 ) -> Self {
751 self.cross_reference_validators
752 .write()
753 .unwrap()
754 .push(validator);
755 self
756 }
757
758 #[must_use]
760 pub fn add_custom_validator(self, validator: Box<dyn CustomValidator>) -> Self {
761 self.custom_validators.write().unwrap().push(validator);
762 self
763 }
764
765 pub fn validate(
767 self,
768 config: &HashMap<String, ConfigValue>,
769 ) -> Result<(CompileTimeValidator<Validated>, ValidationResult)> {
770 let start_time = Instant::now();
771 let mut errors = Vec::new();
772 let warnings = Vec::new();
773 let mut suggestions = Vec::new();
774 let mut rules_checked = 0;
775
776 let config_hash = self.compute_config_hash(config);
778 if self.config.enable_caching {
779 if let Some(cached_result) = self.validation_cache.read().unwrap().get(&config_hash) {
780 let validation_cache_clone = Arc::clone(&self.validation_cache);
781 return Ok((
782 CompileTimeValidator {
784 state: PhantomData,
785 schema_validators: self.schema_validators,
786 constraint_validators: self.constraint_validators,
787 dependency_validators: self.dependency_validators,
788 cross_reference_validators: self.cross_reference_validators,
789 custom_validators: self.custom_validators,
790 config: self.config,
791 validation_cache: validation_cache_clone,
792 },
793 cached_result.clone(),
794 ));
795 }
796 }
797
798 if !self.schema_validators.read().unwrap().is_empty() {
800 for validator in self.schema_validators.read().unwrap().values() {
801 match validator.validate_schema(config) {
802 Ok(mut schema_errors) => {
803 errors.append(&mut schema_errors);
804 rules_checked += 1;
805 }
806 Err(e) => {
807 errors.push(ValidationError {
808 error_id: format!("schema_error_{rules_checked}"),
809 category: ValidationErrorCategory::SchemaViolation,
810 message: e.to_string(),
811 location: ConfigurationLocation {
812 section: "schema".to_string(),
813 field_path: "root".to_string(),
814 line_number: None,
815 column_number: None,
816 },
817 severity: ValidationSeverity::Critical,
818 expected: None,
819 actual: None,
820 suggestion: Some(
821 "Check schema definition and configuration format".to_string(),
822 ),
823 related_errors: Vec::new(),
824 });
825 }
826 }
827
828 if self.config.fail_fast && !errors.is_empty() {
829 break;
830 }
831 }
832 }
833
834 if self.config.enable_constraint_validation && (errors.is_empty() || !self.config.fail_fast)
836 {
837 for validator in self.constraint_validators.read().unwrap().iter() {
838 match validator.validate_constraints(config) {
839 Ok(mut constraint_errors) => {
840 errors.append(&mut constraint_errors);
841 rules_checked += 1;
842 }
843 Err(e) => {
844 errors.push(ValidationError {
845 error_id: format!("constraint_error_{rules_checked}"),
846 category: ValidationErrorCategory::ConstraintViolation,
847 message: e.to_string(),
848 location: ConfigurationLocation {
849 section: "constraints".to_string(),
850 field_path: "unknown".to_string(),
851 line_number: None,
852 column_number: None,
853 },
854 severity: ValidationSeverity::High,
855 expected: None,
856 actual: None,
857 suggestion: Some(
858 "Review parameter constraints and valid ranges".to_string(),
859 ),
860 related_errors: Vec::new(),
861 });
862 }
863 }
864
865 if self.config.fail_fast && !errors.is_empty() {
866 break;
867 }
868 }
869 }
870
871 if self.config.enable_dependency_validation && (errors.is_empty() || !self.config.fail_fast)
873 {
874 for validator in self.dependency_validators.read().unwrap().iter() {
875 match validator.validate_dependencies(config) {
876 Ok(mut dependency_errors) => {
877 errors.append(&mut dependency_errors);
878 rules_checked += 1;
879 }
880 Err(e) => {
881 errors.push(ValidationError {
882 error_id: format!("dependency_error_{rules_checked}"),
883 category: ValidationErrorCategory::DependencyMissing,
884 message: e.to_string(),
885 location: ConfigurationLocation {
886 section: "dependencies".to_string(),
887 field_path: "unknown".to_string(),
888 line_number: None,
889 column_number: None,
890 },
891 severity: ValidationSeverity::High,
892 expected: None,
893 actual: None,
894 suggestion: Some(
895 "Check that all required dependencies are configured".to_string(),
896 ),
897 related_errors: Vec::new(),
898 });
899 }
900 }
901
902 if self.config.fail_fast && !errors.is_empty() {
903 break;
904 }
905 }
906 }
907
908 if self.config.enable_cross_reference_validation
910 && (errors.is_empty() || !self.config.fail_fast)
911 {
912 for validator in self.cross_reference_validators.read().unwrap().iter() {
913 match validator.validate_cross_references(config) {
914 Ok(mut cross_ref_errors) => {
915 errors.append(&mut cross_ref_errors);
916 rules_checked += 1;
917 }
918 Err(e) => {
919 errors.push(ValidationError {
920 error_id: format!("cross_ref_error_{rules_checked}"),
921 category: ValidationErrorCategory::CrossReferenceError,
922 message: e.to_string(),
923 location: ConfigurationLocation {
924 section: "cross_references".to_string(),
925 field_path: "unknown".to_string(),
926 line_number: None,
927 column_number: None,
928 },
929 severity: ValidationSeverity::Medium,
930 expected: None,
931 actual: None,
932 suggestion: Some(
933 "Check cross-references between configuration sections".to_string(),
934 ),
935 related_errors: Vec::new(),
936 });
937 }
938 }
939
940 if self.config.fail_fast && !errors.is_empty() {
941 break;
942 }
943 }
944 }
945
946 if self.config.enable_custom_validation && (errors.is_empty() || !self.config.fail_fast) {
948 for validator in self.custom_validators.read().unwrap().iter() {
949 match validator.validate(config) {
950 Ok(mut custom_errors) => {
951 errors.append(&mut custom_errors);
952 rules_checked += 1;
953 }
954 Err(e) => {
955 errors.push(ValidationError {
956 error_id: format!("custom_error_{rules_checked}"),
957 category: ValidationErrorCategory::CustomValidationFailure,
958 message: e.to_string(),
959 location: ConfigurationLocation {
960 section: "custom".to_string(),
961 field_path: "unknown".to_string(),
962 line_number: None,
963 column_number: None,
964 },
965 severity: ValidationSeverity::Medium,
966 expected: None,
967 actual: None,
968 suggestion: Some("Review custom validation rules".to_string()),
969 related_errors: Vec::new(),
970 });
971 }
972 }
973
974 if self.config.fail_fast && !errors.is_empty() {
975 break;
976 }
977 }
978 }
979
980 suggestions.extend(self.generate_suggestions(&errors, config));
982
983 let status = if !errors.is_empty() {
985 ValidationStatus::Invalid
986 } else if !warnings.is_empty() {
987 ValidationStatus::Warning
988 } else {
989 ValidationStatus::Valid
990 };
991
992 let validation_time = start_time.elapsed();
994 let result = ValidationResult {
995 status,
996 errors,
997 warnings,
998 suggestions,
999 metrics: ValidationMetrics {
1000 validation_time,
1001 rules_checked,
1002 fields_validated: config.len(),
1003 cache_hit_rate: 0.0, memory_usage: 0, },
1006 timestamp: SystemTime::now(),
1007 };
1008
1009 if self.config.enable_caching {
1011 self.validation_cache
1012 .write()
1013 .unwrap()
1014 .insert(config_hash, result.clone());
1015 }
1016
1017 let validated_validator = CompileTimeValidator {
1019 state: PhantomData,
1020 schema_validators: self.schema_validators,
1021 constraint_validators: self.constraint_validators,
1022 dependency_validators: self.dependency_validators,
1023 cross_reference_validators: self.cross_reference_validators,
1024 custom_validators: self.custom_validators,
1025 config: self.config,
1026 validation_cache: self.validation_cache,
1027 };
1028
1029 Ok((validated_validator, result))
1030 }
1031
1032 fn compute_config_hash(&self, config: &HashMap<String, ConfigValue>) -> String {
1034 format!("hash_{}", config.len())
1036 }
1037
1038 fn generate_suggestions(
1039 &self,
1040 errors: &[ValidationError],
1041 config: &HashMap<String, ConfigValue>,
1042 ) -> Vec<ValidationSuggestion> {
1043 let mut suggestions = Vec::new();
1044
1045 for error in errors {
1046 match &error.category {
1047 ValidationErrorCategory::MissingRequired => {
1048 suggestions.push(ValidationSuggestion {
1049 suggestion_id: format!("add_required_{}", error.error_id),
1050 message: format!("Add required field '{}'", error.location.field_path),
1051 action: SuggestionAction::AddField {
1052 field: error.location.field_path.clone(),
1053 value: error
1054 .expected
1055 .clone()
1056 .unwrap_or_else(|| "default_value".to_string()),
1057 },
1058 confidence: 0.9,
1059 priority: SuggestionPriority::Critical,
1060 });
1061 }
1062 ValidationErrorCategory::TypeMismatch => {
1063 if let Some(expected) = &error.expected {
1064 suggestions.push(ValidationSuggestion {
1065 suggestion_id: format!("fix_type_{}", error.error_id),
1066 message: format!(
1067 "Convert field '{}' to type '{}'",
1068 error.location.field_path, expected
1069 ),
1070 action: SuggestionAction::ModifyField {
1071 field: error.location.field_path.clone(),
1072 new_value: format!("convert_to_{expected}"),
1073 },
1074 confidence: 0.8,
1075 priority: SuggestionPriority::High,
1076 });
1077 }
1078 }
1079 _ => {}
1080 }
1081 }
1082
1083 suggestions
1084 }
1085}
1086
1087impl CompileTimeValidator<Validated> {
1088 pub fn create_validated_config<T>(&self, config: T) -> ValidatedPipelineConfig<T> {
1090 let validation_proof = ValidationProof {
1091 proof_id: format!(
1092 "proof_{}",
1093 SystemTime::now()
1094 .duration_since(UNIX_EPOCH)
1095 .unwrap()
1096 .as_millis()
1097 ),
1098 validation_time: SystemTime::now(),
1099 validator_version: "1.0.0".to_string(),
1100 checksum: "validated".to_string(),
1101 };
1102
1103 ValidatedPipelineConfig {
1105 config,
1106 validation_proof,
1107 schema_version: "1.0.0".to_string(),
1108 }
1109 }
1110
1111 #[must_use]
1113 pub fn get_validation_metrics(&self) -> ValidationMetrics {
1114 ValidationMetrics {
1116 validation_time: Duration::from_millis(0),
1117 rules_checked: 0,
1118 fields_validated: 0,
1119 cache_hit_rate: 0.0,
1120 memory_usage: 0,
1121 }
1122 }
1123}
1124
1125impl<T> ValidatedPipelineConfig<T> {
1126 pub fn config(&self) -> &T {
1128 &self.config
1129 }
1130
1131 pub fn validation_proof(&self) -> &ValidationProof {
1133 &self.validation_proof
1134 }
1135
1136 pub fn map<U, F>(self, f: F) -> ValidatedPipelineConfig<U>
1138 where
1139 F: FnOnce(T) -> U,
1140 {
1141 ValidatedPipelineConfig {
1143 config: f(self.config),
1144 validation_proof: self.validation_proof,
1145 schema_version: self.schema_version,
1146 }
1147 }
1148}
1149
1150impl Default for TypeSafeConfigBuilder<Unbuilt> {
1153 fn default() -> Self {
1154 Self::new()
1155 }
1156}
1157
1158impl TypeSafeConfigBuilder<Unbuilt> {
1159 #[must_use]
1161 pub fn new() -> Self {
1162 Self {
1163 state: PhantomData,
1164 config_data: HashMap::new(),
1165 type_constraints: HashMap::new(),
1166 validation_rules: Vec::new(),
1167 builder_config: BuilderConfig {
1168 strict_validation: true,
1169 allow_unknown_fields: false,
1170 max_nesting_depth: 5,
1171 },
1172 }
1173 }
1174
1175 pub fn string_field<T: Into<String>>(mut self, name: &str, value: T) -> Self {
1177 self.config_data
1178 .insert(name.to_string(), ConfigValue::String(value.into()));
1179 self.type_constraints.insert(
1180 name.to_string(),
1181 TypeConstraint {
1183 expected_type: ConfigType::String,
1184 required: true,
1185 default_value: None,
1186 constraints: Vec::new(),
1187 custom_validator: None,
1188 },
1189 );
1190 self
1191 }
1192
1193 #[must_use]
1195 pub fn integer_field(mut self, name: &str, value: i64) -> Self {
1196 self.config_data
1197 .insert(name.to_string(), ConfigValue::Integer(value));
1198 self.type_constraints.insert(
1199 name.to_string(),
1200 TypeConstraint {
1202 expected_type: ConfigType::Integer,
1203 required: true,
1204 default_value: None,
1205 constraints: Vec::new(),
1206 custom_validator: None,
1207 },
1208 );
1209 self
1210 }
1211
1212 #[must_use]
1214 pub fn float_field(mut self, name: &str, value: f64) -> Self {
1215 self.config_data
1216 .insert(name.to_string(), ConfigValue::Float(value));
1217 self.type_constraints.insert(
1218 name.to_string(),
1219 TypeConstraint {
1221 expected_type: ConfigType::Float,
1222 required: true,
1223 default_value: None,
1224 constraints: Vec::new(),
1225 custom_validator: None,
1226 },
1227 );
1228 self
1229 }
1230
1231 #[must_use]
1233 pub fn boolean_field(mut self, name: &str, value: bool) -> Self {
1234 self.config_data
1235 .insert(name.to_string(), ConfigValue::Boolean(value));
1236 self.type_constraints.insert(
1237 name.to_string(),
1238 TypeConstraint {
1240 expected_type: ConfigType::Boolean,
1241 required: true,
1242 default_value: None,
1243 constraints: Vec::new(),
1244 custom_validator: None,
1245 },
1246 );
1247 self
1248 }
1249
1250 #[must_use]
1252 pub fn with_constraint(mut self, field: &str, constraint: ValueConstraint) -> Self {
1253 if let Some(type_constraint) = self.type_constraints.get_mut(field) {
1254 type_constraint.constraints.push(constraint);
1255 }
1256 self
1257 }
1258
1259 #[must_use]
1261 pub fn with_rule(mut self, rule: Box<dyn ValidationRule>) -> Self {
1262 self.validation_rules.push(rule);
1263 self
1264 }
1265
1266 pub fn build(self) -> Result<TypeSafeConfigBuilder<Built>> {
1268 for (field, constraint) in &self.type_constraints {
1270 if let Some(value) = self.config_data.get(field) {
1271 if !self.is_type_compatible(value, &constraint.expected_type) {
1273 return Err(SklearsComposeError::InvalidConfiguration(format!(
1274 "Type mismatch for field '{}': expected {:?}, got {:?}",
1275 field, constraint.expected_type, value
1276 )));
1277 }
1278
1279 for value_constraint in &constraint.constraints {
1281 self.validate_constraint(field, value, value_constraint)?;
1282 }
1283
1284 if let Some(ref validator) = constraint.custom_validator {
1286 validator(value)
1287 .map_err(|e| SklearsComposeError::InvalidConfiguration(e.to_string()))?;
1288 }
1289 } else if constraint.required {
1290 return Err(SklearsComposeError::InvalidConfiguration(format!(
1291 "Required field '{field}' is missing"
1292 )));
1293 }
1294 }
1295
1296 for rule in &self.validation_rules {
1298 for (field, value) in &self.config_data {
1299 rule.apply(field, value)
1300 .map_err(|e| SklearsComposeError::InvalidConfiguration(e.to_string()))?;
1301 }
1302 }
1303
1304 Ok(TypeSafeConfigBuilder {
1305 state: PhantomData,
1306 config_data: self.config_data,
1307 type_constraints: self.type_constraints,
1308 validation_rules: self.validation_rules,
1309 builder_config: self.builder_config,
1310 })
1311 }
1312
1313 fn is_type_compatible(&self, value: &ConfigValue, expected_type: &ConfigType) -> bool {
1315 match (value, expected_type) {
1316 (ConfigValue::String(_), ConfigType::String) => true,
1317 (ConfigValue::Integer(_), ConfigType::Integer) => true,
1318 (ConfigValue::Float(_), ConfigType::Float) => true,
1319 (ConfigValue::Boolean(_), ConfigType::Boolean) => true,
1320 (ConfigValue::Array(arr), ConfigType::Array(element_type)) => arr
1321 .iter()
1322 .all(|item| self.is_type_compatible(item, element_type)),
1323 (ConfigValue::Object(_), ConfigType::Object(_)) => true,
1324 (ConfigValue::Null, ConfigType::Optional(_)) => true,
1325 _ => false,
1326 }
1327 }
1328
1329 fn validate_constraint(
1330 &self,
1331 field: &str,
1332 value: &ConfigValue,
1333 constraint: &ValueConstraint,
1334 ) -> Result<()> {
1335 match constraint {
1336 ValueConstraint::Range { min, max } => {
1337 let numeric_value = match value {
1338 ConfigValue::Integer(i) => *i as f64,
1339 ConfigValue::Float(f) => *f,
1340 _ => {
1341 return Err(SklearsComposeError::InvalidConfiguration(format!(
1342 "Range constraint can only be applied to numeric values in field '{field}'"
1343 )));
1344 }
1345 };
1346
1347 if numeric_value < *min || numeric_value > *max {
1348 return Err(SklearsComposeError::InvalidConfiguration(format!(
1349 "Value {numeric_value} in field '{field}' is outside range [{min}, {max}]"
1350 )));
1351 }
1352 }
1353 ValueConstraint::Length { min, max } => {
1354 let length = match value {
1355 ConfigValue::String(s) => s.len(),
1356 ConfigValue::Array(arr) => arr.len(),
1357 _ => {
1358 return Err(SklearsComposeError::InvalidConfiguration(
1359 format!("Length constraint can only be applied to strings or arrays in field '{field}'")
1360 ));
1361 }
1362 };
1363
1364 if length < *min {
1365 return Err(SklearsComposeError::InvalidConfiguration(format!(
1366 "Length {length} in field '{field}' is less than minimum {min}"
1367 )));
1368 }
1369
1370 if let Some(max_len) = max {
1371 if length > *max_len {
1372 return Err(SklearsComposeError::InvalidConfiguration(format!(
1373 "Length {length} in field '{field}' is greater than maximum {max_len}"
1374 )));
1375 }
1376 }
1377 }
1378 ValueConstraint::OneOf(allowed_values) => {
1379 if !allowed_values.contains(value) {
1380 return Err(SklearsComposeError::InvalidConfiguration(format!(
1381 "Value in field '{field}' is not one of the allowed values"
1382 )));
1383 }
1384 }
1385 ValueConstraint::Pattern(pattern) => {
1386 if let ConfigValue::String(s) = value {
1387 if !s.contains(pattern) {
1389 return Err(SklearsComposeError::InvalidConfiguration(format!(
1390 "String in field '{field}' does not match pattern '{pattern}'"
1391 )));
1392 }
1393 } else {
1394 return Err(SklearsComposeError::InvalidConfiguration(format!(
1395 "Pattern constraint can only be applied to strings in field '{field}'"
1396 )));
1397 }
1398 }
1399 ValueConstraint::Custom(description) => {
1400 println!("Custom constraint '{description}' applied to field '{field}'");
1403 }
1404 }
1405
1406 Ok(())
1407 }
1408}
1409
1410impl TypeSafeConfigBuilder<Built> {
1411 #[must_use]
1413 pub fn config_data(&self) -> &HashMap<String, ConfigValue> {
1414 &self.config_data
1415 }
1416
1417 #[must_use]
1419 pub fn into_config_data(self) -> HashMap<String, ConfigValue> {
1420 self.config_data
1421 }
1422}
1423
1424#[derive(Debug)]
1428pub struct PipelineSchemaValidator {
1429 schema: PipelineConfigurationSchema,
1430}
1431
1432impl Default for PipelineSchemaValidator {
1433 fn default() -> Self {
1434 Self::new()
1435 }
1436}
1437
1438impl PipelineSchemaValidator {
1439 #[must_use]
1440 pub fn new() -> Self {
1441 let mut required_fields = HashSet::new();
1442 required_fields.insert("model".to_string());
1443 required_fields.insert("pipeline".to_string());
1444
1445 let mut field_definitions = HashMap::new();
1446 field_definitions.insert(
1447 "model".to_string(),
1448 FieldDefinition {
1450 field_type: ConfigType::Object(HashMap::new()),
1451 description: "Model configuration".to_string(),
1452 default: None,
1453 constraints: Vec::new(),
1454 deprecated: false,
1455 deprecation_message: None,
1456 },
1457 );
1458
1459 let schema = PipelineConfigurationSchema {
1460 version: "1.0.0".to_string(),
1461 required_fields,
1462 field_definitions,
1463 constraints: Vec::new(),
1464 cross_reference_rules: Vec::new(),
1465 };
1466
1467 Self { schema }
1468 }
1469}
1470
1471impl SchemaValidator for PipelineSchemaValidator {
1472 fn validate_schema(
1473 &self,
1474 config: &HashMap<String, ConfigValue>,
1475 ) -> Result<Vec<ValidationError>> {
1476 let mut errors = Vec::new();
1477
1478 for required_field in &self.schema.required_fields {
1480 if !config.contains_key(required_field) {
1481 errors.push(ValidationError {
1482 error_id: format!("missing_required_{required_field}"),
1483 category: ValidationErrorCategory::MissingRequired,
1484 message: format!("Required field '{required_field}' is missing"),
1485 location: ConfigurationLocation {
1486 section: "root".to_string(),
1487 field_path: required_field.clone(),
1488 line_number: None,
1489 column_number: None,
1490 },
1491 severity: ValidationSeverity::Critical,
1492 expected: Some("required field".to_string()),
1493 actual: Some("missing".to_string()),
1494 suggestion: Some(format!("Add required field '{required_field}'")),
1495 related_errors: Vec::new(),
1496 });
1497 }
1498 }
1499
1500 Ok(errors)
1501 }
1502
1503 fn schema_name(&self) -> &'static str {
1504 "PipelineSchema"
1505 }
1506
1507 fn schema_version(&self) -> &str {
1508 &self.schema.version
1509 }
1510}
1511
1512#[derive(Debug)]
1514pub struct ParameterConstraintValidator;
1515
1516impl ConstraintValidator for ParameterConstraintValidator {
1517 fn validate_constraints(
1518 &self,
1519 config: &HashMap<String, ConfigValue>,
1520 ) -> Result<Vec<ValidationError>> {
1521 let mut errors = Vec::new();
1522
1523 if let Some(ConfigValue::Object(model_config)) = config.get("model") {
1525 if let Some(ConfigValue::Float(learning_rate)) = model_config.get("learning_rate") {
1526 if *learning_rate <= 0.0 || *learning_rate > 1.0 {
1527 errors.push(ValidationError {
1528 error_id: "invalid_learning_rate".to_string(),
1529 category: ValidationErrorCategory::RangeError,
1530 message: "Learning rate must be between 0.0 and 1.0".to_string(),
1531 location: ConfigurationLocation {
1532 section: "model".to_string(),
1533 field_path: "learning_rate".to_string(),
1534 line_number: None,
1535 column_number: None,
1536 },
1537 severity: ValidationSeverity::High,
1538 expected: Some("0.0 < learning_rate <= 1.0".to_string()),
1539 actual: Some(learning_rate.to_string()),
1540 suggestion: Some(
1541 "Set learning rate to a value between 0.001 and 0.1".to_string(),
1542 ),
1543 related_errors: Vec::new(),
1544 });
1545 }
1546 }
1547 }
1548
1549 Ok(errors)
1550 }
1551
1552 fn validator_name(&self) -> &'static str {
1553 "ParameterConstraintValidator"
1554 }
1555}
1556
1557#[derive(Debug)]
1559pub struct NonEmptyStringRule;
1560
1561impl ValidationRule for NonEmptyStringRule {
1562 fn apply(&self, field: &str, value: &ConfigValue) -> Result<()> {
1563 if let ConfigValue::String(s) = value {
1564 if s.is_empty() {
1565 return Err(format!("String field '{field}' cannot be empty").into());
1566 }
1567 }
1568 Ok(())
1569 }
1570
1571 fn rule_name(&self) -> &'static str {
1572 "NonEmptyStringRule"
1573 }
1574}
1575
1576#[macro_export]
1580macro_rules! enhanced_validated_config {
1581 ($($key:expr => $value:expr),*) => {{
1582 let mut config = std::collections::HashMap::new();
1583 $(
1584 config.insert($key.to_string(), $value);
1585 )*
1586 config
1587 }};
1588}
1589
1590#[macro_export]
1592macro_rules! type_safe_config {
1593 (
1594 $($field:ident: $type:ident = $value:expr),*
1595 ) => {{
1596 let builder = TypeSafeConfigBuilder::new();
1597 $(
1598 let builder = match stringify!($type) {
1599 "String" => builder.string_field(stringify!($field), $value),
1600 "Integer" => builder.integer_field(stringify!($field), $value),
1601 "Float" => builder.float_field(stringify!($field), $value),
1602 "Boolean" => builder.boolean_field(stringify!($field), $value),
1603 _ => panic!("Unsupported type: {}", stringify!($type)),
1604 };
1605 )*
1606 builder.build()
1607 }};
1608}
1609
1610#[allow(non_snake_case)]
1611#[cfg(test)]
1612mod tests {
1613 use super::*;
1614
1615 #[test]
1616 fn test_validator_creation() {
1617 let validator = CompileTimeValidator::<Unvalidated>::new();
1618 assert!(validator.config.strict_type_checking);
1619 }
1620
1621 #[test]
1622 fn test_schema_validation() {
1623 let validator = CompileTimeValidator::<Unvalidated>::new()
1624 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1625
1626 let mut config = HashMap::new();
1627 config.insert("model".to_string(), ConfigValue::Object(HashMap::new()));
1628 config.insert("pipeline".to_string(), ConfigValue::Object(HashMap::new()));
1629
1630 let result = validator.validate(&config);
1631 assert!(result.is_ok());
1632
1633 let (_, validation_result) = result.unwrap();
1634 assert!(matches!(validation_result.status, ValidationStatus::Valid));
1635 }
1636
1637 #[test]
1638 fn test_constraint_validation() {
1639 let validator = CompileTimeValidator::<Unvalidated>::new()
1640 .add_constraint_validator(Box::new(ParameterConstraintValidator));
1641
1642 let mut model_config = HashMap::new();
1643 model_config.insert("learning_rate".to_string(), ConfigValue::Float(2.0)); let mut config = HashMap::new();
1646 config.insert("model".to_string(), ConfigValue::Object(model_config));
1647
1648 let result = validator.validate(&config);
1649 assert!(result.is_ok());
1650
1651 let (_, validation_result) = result.unwrap();
1652 assert!(matches!(
1653 validation_result.status,
1654 ValidationStatus::Invalid
1655 ));
1656 assert!(!validation_result.errors.is_empty());
1657 }
1658
1659 #[test]
1660 fn test_type_safe_builder() {
1661 let builder = TypeSafeConfigBuilder::new()
1662 .string_field("name", "test_pipeline")
1663 .float_field("learning_rate", 0.01)
1664 .integer_field("epochs", 100)
1665 .boolean_field("verbose", true)
1666 .with_constraint(
1667 "learning_rate",
1668 ValueConstraint::Range { min: 0.0, max: 1.0 },
1669 )
1670 .with_rule(Box::new(NonEmptyStringRule));
1671
1672 let result = builder.build();
1673 assert!(result.is_ok());
1674
1675 let built_config = result.unwrap();
1676 assert_eq!(built_config.config_data().len(), 4);
1677 }
1678
1679 #[test]
1680 fn test_validated_config_creation() {
1681 let validator = CompileTimeValidator::<Unvalidated>::new();
1682 let config = HashMap::new();
1683
1684 let (validated_validator, _) = validator.validate(&config).unwrap();
1685 let validated_config = validated_validator.create_validated_config(config);
1686
1687 assert!(validated_config
1688 .validation_proof()
1689 .proof_id
1690 .starts_with("proof_"));
1691 }
1692
1693 #[test]
1694 fn test_validation_errors() {
1695 let validator = CompileTimeValidator::<Unvalidated>::new()
1696 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1697
1698 let config = HashMap::new(); let result = validator.validate(&config);
1701 assert!(result.is_ok());
1702
1703 let (_, validation_result) = result.unwrap();
1704 assert!(matches!(
1705 validation_result.status,
1706 ValidationStatus::Invalid
1707 ));
1708 assert!(validation_result.errors.len() >= 2); }
1710
1711 #[test]
1712 fn test_config_value_types() {
1713 assert!(matches!(
1714 ConfigValue::String("test".to_string()),
1715 ConfigValue::String(_)
1716 ));
1717 assert!(matches!(ConfigValue::Integer(42), ConfigValue::Integer(_)));
1718 assert!(matches!(ConfigValue::Float(3.14), ConfigValue::Float(_)));
1719 assert!(matches!(
1720 ConfigValue::Boolean(true),
1721 ConfigValue::Boolean(_)
1722 ));
1723 }
1724
1725 #[test]
1726 fn test_validation_suggestions() {
1727 let validator = CompileTimeValidator::<Unvalidated>::new()
1728 .add_schema_validator(Box::new(PipelineSchemaValidator::new()));
1729
1730 let config = HashMap::new();
1731
1732 let (_, validation_result) = validator.validate(&config).unwrap();
1733 assert!(!validation_result.suggestions.is_empty());
1734
1735 let suggestion = &validation_result.suggestions[0];
1736 assert!(matches!(suggestion.priority, SuggestionPriority::Critical));
1737 }
1738
1739 #[test]
1740 fn test_constraint_range() {
1741 let builder = TypeSafeConfigBuilder::new()
1742 .float_field("value", 150.0)
1743 .with_constraint(
1744 "value",
1745 ValueConstraint::Range {
1746 min: 0.0,
1747 max: 100.0,
1748 },
1749 );
1750
1751 let result = builder.build();
1752 assert!(result.is_err());
1753 }
1754
1755 #[test]
1756 fn test_constraint_length() {
1757 let builder = TypeSafeConfigBuilder::new()
1758 .string_field("name", "a") .with_constraint(
1760 "name",
1761 ValueConstraint::Length {
1762 min: 3,
1763 max: Some(10),
1764 },
1765 );
1766
1767 let result = builder.build();
1768 assert!(result.is_err());
1769 }
1770}