1use serde::{Deserialize, Serialize};
9use sklears_core::traits::Estimator;
10use std::collections::HashMap;
11use std::fmt;
12use std::marker::PhantomData;
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
16pub enum ValidationSeverity {
17 Info,
19 Warning,
21 Error,
23 Critical,
25}
26
27#[derive(Debug, Clone)]
29pub struct ValidationResult {
30 pub severity: ValidationSeverity,
31 pub component: String,
32 pub field: String,
33 pub message: String,
34 pub suggestions: Vec<String>,
35 pub error_code: String,
36}
37
38impl ValidationResult {
39 pub fn new(
40 severity: ValidationSeverity,
41 component: impl Into<String>,
42 field: impl Into<String>,
43 message: impl Into<String>,
44 ) -> Self {
45 Self {
46 severity,
47 component: component.into(),
48 field: field.into(),
49 message: message.into(),
50 suggestions: Vec::new(),
51 error_code: "VALIDATION_ERROR".to_string(),
52 }
53 }
54
55 #[must_use]
56 pub fn with_suggestions(mut self, suggestions: Vec<String>) -> Self {
57 self.suggestions = suggestions;
58 self
59 }
60
61 pub fn with_error_code(mut self, code: impl Into<String>) -> Self {
62 self.error_code = code.into();
63 self
64 }
65
66 #[must_use]
67 pub fn is_error(&self) -> bool {
68 matches!(
69 self.severity,
70 ValidationSeverity::Error | ValidationSeverity::Critical
71 )
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct ValidationReport {
78 pub results: Vec<ValidationResult>,
79 pub summary: ValidationSummary,
80}
81
82#[derive(Debug, Clone)]
83pub struct ValidationSummary {
84 pub total_checks: usize,
85 pub passed: usize,
86 pub warnings: usize,
87 pub errors: usize,
88 pub critical: usize,
89 pub overall_status: ValidationStatus,
90}
91
92#[derive(Debug, Clone, PartialEq)]
93pub enum ValidationStatus {
94 Passed,
96 PassedWithWarnings,
98 Failed,
100 Critical,
102}
103
104impl Default for ValidationReport {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl ValidationReport {
111 #[must_use]
112 pub fn new() -> Self {
113 Self {
114 results: Vec::new(),
115 summary: ValidationSummary {
116 total_checks: 0,
117 passed: 0,
118 warnings: 0,
119 errors: 0,
120 critical: 0,
121 overall_status: ValidationStatus::Passed,
122 },
123 }
124 }
125
126 pub fn add_result(&mut self, result: ValidationResult) {
127 match result.severity {
128 ValidationSeverity::Info => {}
129 ValidationSeverity::Warning => self.summary.warnings += 1,
130 ValidationSeverity::Error => self.summary.errors += 1,
131 ValidationSeverity::Critical => self.summary.critical += 1,
132 }
133 self.results.push(result);
134 self.update_summary();
135 }
136
137 fn update_summary(&mut self) {
138 self.summary.total_checks = self.results.len();
139 self.summary.passed = self.summary.total_checks
140 - self.summary.warnings
141 - self.summary.errors
142 - self.summary.critical;
143
144 self.summary.overall_status = if self.summary.critical > 0 {
145 ValidationStatus::Critical
146 } else if self.summary.errors > 0 {
147 ValidationStatus::Failed
148 } else if self.summary.warnings > 0 {
149 ValidationStatus::PassedWithWarnings
150 } else {
151 ValidationStatus::Passed
152 };
153 }
154
155 #[must_use]
156 pub fn has_errors(&self) -> bool {
157 self.summary.errors > 0 || self.summary.critical > 0
158 }
159
160 #[must_use]
161 pub fn display_summary(&self) -> String {
162 format!(
163 "Validation Summary: {} checks, {} passed, {} warnings, {} errors, {} critical (Status: {:?})",
164 self.summary.total_checks,
165 self.summary.passed,
166 self.summary.warnings,
167 self.summary.errors,
168 self.summary.critical,
169 self.summary.overall_status
170 )
171 }
172}
173
174pub trait ConfigurationValidator<T> {
176 fn validate(&self, config: &T) -> ValidationReport;
177 fn validate_field(&self, field_name: &str, value: &dyn std::fmt::Debug) -> ValidationReport;
178 fn get_validation_schema(&self) -> ValidationSchema;
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ValidationSchema {
184 pub name: String,
185 pub version: String,
186 pub fields: HashMap<String, FieldConstraints>,
187 pub dependencies: Vec<DependencyConstraint>,
188 pub custom_rules: Vec<CustomValidationRule>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct FieldConstraints {
194 pub required: bool,
195 pub field_type: FieldType,
196 pub constraints: Vec<Constraint>,
197 pub description: String,
198 pub examples: Vec<String>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub enum FieldType {
203 Integer,
205 Float,
207 String,
209 Boolean,
211 Array(Box<FieldType>),
213 Object(String), Enum(Vec<String>),
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub enum Constraint {
221 Range { min: f64, max: f64 },
223 Length { min: usize, max: usize },
225 Pattern(String), OneOf(Vec<String>),
229 Custom(String), }
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct DependencyConstraint {
236 pub name: String,
237 pub condition: String, pub error_message: String,
239 pub severity: ValidationSeverity,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct CustomValidationRule {
245 pub name: String,
246 pub description: String,
247 pub rule_type: RuleType,
248 pub parameters: HashMap<String, String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub enum RuleType {
253 ParameterCompatibility,
255 PerformanceWarning,
257 SecurityCheck,
259 ResourceValidation,
261}
262
263pub struct CompileTimeValidator<T> {
265 schema: ValidationSchema,
266 _phantom: PhantomData<T>,
267}
268
269impl<T> CompileTimeValidator<T> {
270 #[must_use]
271 pub fn new(schema: ValidationSchema) -> Self {
272 Self {
273 schema,
274 _phantom: PhantomData,
275 }
276 }
277
278 pub fn validate_static(&self, config: &T) -> ValidationReport
280 where
281 T: fmt::Debug,
282 {
283 ValidationReport::new()
287 }
288}
289
290pub trait ValidConfig {
292 type Error;
293 const IS_VALID: bool;
294
295 fn validate_config() -> Result<(), Self::Error>;
296}
297
298pub struct ValidatedConfig<T> {
300 inner: T,
301}
302
303impl<T> ValidatedConfig<T>
304where
305 T: ValidConfig,
306{
307 pub fn new(config: T) -> Result<Self, T::Error> {
309 if T::IS_VALID {
310 Ok(ValidatedConfig { inner: config })
311 } else {
312 Err(T::validate_config().unwrap_err())
313 }
314 }
315
316 pub fn inner(&self) -> &T {
318 &self.inner
319 }
320
321 pub fn into_inner(self) -> T {
323 self.inner
324 }
325}
326
327pub trait ParameterConstraints<const MIN: i32, const MAX: i32> {
329 #[must_use]
330 fn validate_range(value: i32) -> bool {
331 value >= MIN && value <= MAX
332 }
333}
334
335#[derive(Debug, Clone, Copy)]
337pub struct ValidatedParameter<const MIN: i32, const MAX: i32> {
338 value: i32,
339}
340
341impl<const MIN: i32, const MAX: i32> ValidatedParameter<MIN, MAX> {
342 #[must_use]
344 pub const fn new(value: i32) -> Option<Self> {
345 if value >= MIN && value <= MAX {
346 Some(ValidatedParameter { value })
347 } else {
348 None
349 }
350 }
351
352 pub fn new_runtime(value: i32) -> Result<Self, ParameterValidationError> {
354 if value >= MIN && value <= MAX {
355 Ok(ValidatedParameter { value })
356 } else {
357 Err(ParameterValidationError::OutOfRange {
358 value,
359 min: MIN,
360 max: MAX,
361 })
362 }
363 }
364
365 #[must_use]
367 pub const fn value(&self) -> i32 {
368 self.value
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Eq)]
374pub enum ParameterValidationError {
375 OutOfRange { value: i32, min: i32, max: i32 },
377 InvalidType { expected: String, actual: String },
379 MissingRequired { parameter: String },
381 DependencyViolation {
383 parameter: String,
384 dependency: String,
385 },
386}
387
388impl fmt::Display for ParameterValidationError {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 match self {
391 ParameterValidationError::OutOfRange { value, min, max } => {
392 write!(f, "Parameter value {value} is out of range [{min}, {max}]")
393 }
394 ParameterValidationError::InvalidType { expected, actual } => {
395 write!(f, "Expected type '{expected}', found '{actual}'")
396 }
397 ParameterValidationError::MissingRequired { parameter } => {
398 write!(f, "Required parameter '{parameter}' is missing")
399 }
400 ParameterValidationError::DependencyViolation {
401 parameter,
402 dependency,
403 } => {
404 write!(
405 f,
406 "Parameter '{parameter}' violates dependency constraint '{dependency}'"
407 )
408 }
409 }
410 }
411}
412
413impl std::error::Error for ParameterValidationError {}
414
415pub trait FeatureFlags {
417 const SUPPORTS_PARALLEL: bool;
418 const SUPPORTS_GPU: bool;
419 const REQUIRES_BLAS: bool;
420 const MEMORY_INTENSIVE: bool;
421}
422
423pub struct FeatureValidatedConfig<T, F>
425where
426 T: ValidConfig,
427 F: FeatureFlags,
428{
429 config: ValidatedConfig<T>,
430 _features: PhantomData<F>,
431}
432
433impl<T, F> FeatureValidatedConfig<T, F>
434where
435 T: ValidConfig,
436 F: FeatureFlags,
437{
438 pub fn new(config: T) -> Result<Self, ConfigurationValidationError> {
440 Self::validate_features()?;
442
443 let validated_config = ValidatedConfig::new(config)
444 .map_err(|_| ConfigurationValidationError::InvalidConfiguration)?;
445
446 Ok(FeatureValidatedConfig {
447 config: validated_config,
448 _features: PhantomData,
449 })
450 }
451
452 const fn validate_features() -> Result<(), ConfigurationValidationError> {
454 Ok(())
456 }
457
458 #[must_use]
460 pub const fn supports_parallel() -> bool {
461 F::SUPPORTS_PARALLEL
462 }
463
464 #[must_use]
466 pub const fn supports_gpu() -> bool {
467 F::SUPPORTS_GPU
468 }
469
470 pub fn config(&self) -> &ValidatedConfig<T> {
472 &self.config
473 }
474}
475
476#[derive(Debug, Clone, PartialEq, Eq)]
478pub enum ConfigurationValidationError {
479 InvalidConfiguration,
481 FeatureNotSupported { feature: String },
483 IncompatibleFeatures { feature1: String, feature2: String },
485 ResourceConstraintViolation { resource: String, limit: String },
487}
488
489impl fmt::Display for ConfigurationValidationError {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 match self {
492 ConfigurationValidationError::InvalidConfiguration => {
493 write!(f, "Configuration validation failed")
494 }
495 ConfigurationValidationError::FeatureNotSupported { feature } => {
496 write!(f, "Feature '{feature}' is not supported")
497 }
498 ConfigurationValidationError::IncompatibleFeatures { feature1, feature2 } => {
499 write!(f, "Features '{feature1}' and '{feature2}' are incompatible")
500 }
501 ConfigurationValidationError::ResourceConstraintViolation { resource, limit } => {
502 write!(f, "Resource '{resource}' violates constraint '{limit}'")
503 }
504 }
505 }
506}
507
508impl std::error::Error for ConfigurationValidationError {}
509
510pub trait PipelineStage {
512 type Input;
513 type Output;
514 type Config: ValidConfig;
515
516 const STAGE_NAME: &'static str;
517 const IS_TRANSFORMATIVE: bool;
518 const IS_TERMINAL: bool;
519
520 #[must_use]
521 fn validate_compatibility<U: PipelineStage>() -> bool
522 where
523 Self::Output: CompatibleWith<U::Input>,
524 {
525 true
526 }
527}
528
529pub trait CompatibleWith<T> {}
531
532impl CompatibleWith<f64> for f64 {}
534impl CompatibleWith<i32> for i32 {}
535impl<T> CompatibleWith<Vec<T>> for Vec<T> {}
536
537pub struct ValidatedPipeline<Stages> {
539 stages: Stages,
540}
541
542impl<S1> ValidatedPipeline<(S1,)>
543where
544 S1: PipelineStage,
545{
546 pub fn new(stage: S1) -> Self
548 where
549 S1::Config: ValidConfig,
550 {
551 ValidatedPipeline { stages: (stage,) }
553 }
554}
555
556impl<S1, S2> ValidatedPipeline<(S1, S2)>
557where
558 S1: PipelineStage,
559 S2: PipelineStage,
560 S1::Output: CompatibleWith<S2::Input>,
561{
562 pub fn new(stage1: S1, stage2: S2) -> Self
564 where
565 S1::Config: ValidConfig,
566 S2::Config: ValidConfig,
567 {
568 ValidatedPipeline {
570 stages: (stage1, stage2),
571 }
572 }
573}
574
575#[macro_export]
577macro_rules! validated_config {
578 ($config_type:ty, $($field:ident: $value:expr),*) => {{
579 compile_error!("Macro implementation would go here");
582 }};
583}
584
585pub struct TypedConfigurationValidator<T, const N: usize> {
587 validators: [fn(&T) -> ValidationResult; N],
588 _phantom: PhantomData<T>,
589}
590
591impl<T, const N: usize> TypedConfigurationValidator<T, N> {
592 pub const fn new(validators: [fn(&T) -> ValidationResult; N]) -> Self {
594 Self {
595 validators,
596 _phantom: PhantomData,
597 }
598 }
599
600 pub fn validate(&self, config: &T) -> ValidationReport {
602 let mut report = ValidationReport::new();
603
604 for validator in &self.validators {
605 let result = validator(config);
606 report.add_result(result);
607 }
608
609 report
610 }
611}
612
613pub struct RuntimeValidator {
615 schemas: HashMap<String, ValidationSchema>,
616 custom_validators:
617 HashMap<String, Box<dyn Fn(&dyn std::fmt::Debug) -> ValidationResult + Send + Sync>>,
618}
619
620impl Default for RuntimeValidator {
621 fn default() -> Self {
622 Self::new()
623 }
624}
625
626impl RuntimeValidator {
627 #[must_use]
628 pub fn new() -> Self {
629 Self {
630 schemas: HashMap::new(),
631 custom_validators: HashMap::new(),
632 }
633 }
634
635 pub fn register_schema(&mut self, name: String, schema: ValidationSchema) {
636 self.schemas.insert(name, schema);
637 }
638
639 pub fn register_custom_validator<F>(&mut self, name: String, validator: F)
640 where
641 F: Fn(&dyn std::fmt::Debug) -> ValidationResult + Send + Sync + 'static,
642 {
643 self.custom_validators.insert(name, Box::new(validator));
644 }
645
646 pub fn validate_configuration<T>(&self, config: &T, schema_name: &str) -> ValidationReport
647 where
648 T: fmt::Debug,
649 {
650 let mut report = ValidationReport::new();
651
652 if let Some(schema) = self.schemas.get(schema_name) {
653 for (field_name, constraints) in &schema.fields {
655 let field_result = self.validate_field_constraints(field_name, constraints, config);
656 if let Some(result) = field_result {
657 report.add_result(result);
658 }
659 }
660
661 for dependency in &schema.dependencies {
663 let dep_result = self.validate_dependency(dependency, config);
664 if let Some(result) = dep_result {
665 report.add_result(result);
666 }
667 }
668
669 for rule in &schema.custom_rules {
671 let rule_result = self.apply_custom_rule(rule, config);
672 if let Some(result) = rule_result {
673 report.add_result(result);
674 }
675 }
676 } else {
677 report.add_result(
678 ValidationResult::new(
679 ValidationSeverity::Error,
680 "RuntimeValidator",
681 "schema",
682 format!("Schema '{schema_name}' not found"),
683 )
684 .with_error_code("SCHEMA_NOT_FOUND"),
685 );
686 }
687
688 report
689 }
690
691 fn validate_field_constraints<T>(
692 &self,
693 field_name: &str,
694 constraints: &FieldConstraints,
695 config: &T,
696 ) -> Option<ValidationResult>
697 where
698 T: fmt::Debug,
699 {
700 if constraints.required {
702 None } else {
705 None
706 }
707 }
708
709 fn validate_dependency<T>(
710 &self,
711 dependency: &DependencyConstraint,
712 config: &T,
713 ) -> Option<ValidationResult>
714 where
715 T: fmt::Debug,
716 {
717 None
720 }
721
722 fn apply_custom_rule<T>(
723 &self,
724 rule: &CustomValidationRule,
725 config: &T,
726 ) -> Option<ValidationResult>
727 where
728 T: fmt::Debug,
729 {
730 None
733 }
734}
735
736pub struct PipelineConfigValidator {
738 validator: RuntimeValidator,
739}
740
741impl Default for PipelineConfigValidator {
742 fn default() -> Self {
743 Self::new()
744 }
745}
746
747impl PipelineConfigValidator {
748 #[must_use]
749 pub fn new() -> Self {
750 let mut validator = RuntimeValidator::new();
751
752 let pipeline_schema = ValidationSchema {
754 name: "PipelineConfig".to_string(),
755 version: "1.0.0".to_string(),
756 fields: Self::create_pipeline_field_constraints(),
757 dependencies: Self::create_pipeline_dependencies(),
758 custom_rules: Self::create_pipeline_custom_rules(),
759 };
760
761 validator.register_schema("PipelineConfig".to_string(), pipeline_schema);
762
763 validator.register_custom_validator("performance_check".to_string(), |config| {
765 ValidationResult::new(
766 ValidationSeverity::Info,
767 "PipelineConfig",
768 "performance",
769 "Performance validation passed",
770 )
771 });
772
773 Self { validator }
774 }
775
776 fn create_pipeline_field_constraints() -> HashMap<String, FieldConstraints> {
777 let mut fields = HashMap::new();
778
779 fields.insert(
780 "n_jobs".to_string(),
781 FieldConstraints {
783 required: false,
784 field_type: FieldType::Integer,
785 constraints: vec![Constraint::Range {
786 min: -1.0,
787 max: 1000.0,
788 }],
789 description: "Number of parallel jobs (-1 for all cores)".to_string(),
790 examples: vec!["1".to_string(), "4".to_string(), "-1".to_string()],
791 },
792 );
793
794 fields.insert(
795 "random_state".to_string(),
796 FieldConstraints {
798 required: false,
799 field_type: FieldType::Integer,
800 constraints: vec![Constraint::Range {
801 min: 0.0,
802 max: 2_147_483_647.0,
803 }],
804 description: "Random seed for reproducibility".to_string(),
805 examples: vec!["42".to_string(), "123".to_string()],
806 },
807 );
808
809 fields.insert(
810 "verbose".to_string(),
811 FieldConstraints {
813 required: false,
814 field_type: FieldType::Boolean,
815 constraints: vec![],
816 description: "Enable verbose output".to_string(),
817 examples: vec!["true".to_string(), "false".to_string()],
818 },
819 );
820
821 fields
822 }
823
824 fn create_pipeline_dependencies() -> Vec<DependencyConstraint> {
825 vec![DependencyConstraint {
826 name: "parallel_processing_compatibility".to_string(),
827 condition: "n_jobs > 1 implies thread_safe == true".to_string(),
828 error_message: "Parallel processing requires thread-safe components".to_string(),
829 severity: ValidationSeverity::Error,
830 }]
831 }
832
833 fn create_pipeline_custom_rules() -> Vec<CustomValidationRule> {
834 vec![CustomValidationRule {
835 name: "memory_usage_warning".to_string(),
836 description: "Warn about potentially high memory usage".to_string(),
837 rule_type: RuleType::PerformanceWarning,
838 parameters: HashMap::new(),
839 }]
840 }
841
842 pub fn validate_pipeline_config<T>(&self, config: &T) -> ValidationReport
843 where
844 T: fmt::Debug,
845 {
846 self.validator
847 .validate_configuration(config, "PipelineConfig")
848 }
849}
850
851pub struct ValidationBuilder {
853 schema: ValidationSchema,
854}
855
856impl ValidationBuilder {
857 pub fn new(name: impl Into<String>) -> Self {
858 Self {
859 schema: ValidationSchema {
860 name: name.into(),
861 version: "1.0.0".to_string(),
862 fields: HashMap::new(),
863 dependencies: Vec::new(),
864 custom_rules: Vec::new(),
865 },
866 }
867 }
868
869 pub fn add_field(mut self, name: impl Into<String>, constraints: FieldConstraints) -> Self {
870 self.schema.fields.insert(name.into(), constraints);
871 self
872 }
873
874 #[must_use]
875 pub fn add_dependency(mut self, dependency: DependencyConstraint) -> Self {
876 self.schema.dependencies.push(dependency);
877 self
878 }
879
880 #[must_use]
881 pub fn add_custom_rule(mut self, rule: CustomValidationRule) -> Self {
882 self.schema.custom_rules.push(rule);
883 self
884 }
885
886 #[must_use]
887 pub fn build(self) -> ValidationSchema {
888 self.schema
889 }
890}
891
892pub mod examples {
894 use super::{
895 Constraint, CustomValidationRule, DependencyConstraint, FieldConstraints, FieldType,
896 HashMap, RuleType, ValidationBuilder, ValidationSchema, ValidationSeverity,
897 };
898
899 #[must_use]
900 pub fn create_linear_regression_validator() -> ValidationSchema {
901 ValidationBuilder::new("LinearRegressionConfig")
902 .add_field(
903 "fit_intercept",
904 FieldConstraints {
906 required: false,
907 field_type: FieldType::Boolean,
908 constraints: vec![],
909 description: "Whether to calculate the intercept".to_string(),
910 examples: vec!["true".to_string(), "false".to_string()],
911 },
912 )
913 .add_field(
914 "alpha",
915 FieldConstraints {
917 required: false,
918 field_type: FieldType::Float,
919 constraints: vec![Constraint::Range { min: 0.0, max: 1e6 }],
920 description: "Regularization strength".to_string(),
921 examples: vec!["0.01".to_string(), "1.0".to_string(), "100.0".to_string()],
922 },
923 )
924 .add_dependency(DependencyConstraint {
925 name: "regularization_warning".to_string(),
926 condition: "alpha > 1000".to_string(),
927 error_message: "Very high regularization may lead to underfitting".to_string(),
928 severity: ValidationSeverity::Warning,
929 })
930 .build()
931 }
932
933 #[must_use]
934 pub fn create_ensemble_validator() -> ValidationSchema {
935 ValidationBuilder::new("EnsembleConfig")
936 .add_field(
937 "n_estimators",
938 FieldConstraints {
940 required: true,
941 field_type: FieldType::Integer,
942 constraints: vec![Constraint::Range {
943 min: 1.0,
944 max: 10000.0,
945 }],
946 description: "Number of estimators in the ensemble".to_string(),
947 examples: vec!["10".to_string(), "100".to_string(), "1000".to_string()],
948 },
949 )
950 .add_field(
951 "voting",
952 FieldConstraints {
954 required: false,
955 field_type: FieldType::Enum(vec!["hard".to_string(), "soft".to_string()]),
956 constraints: vec![Constraint::OneOf(vec![
957 "hard".to_string(),
958 "soft".to_string(),
959 ])],
960 description: "Voting strategy for ensemble".to_string(),
961 examples: vec!["hard".to_string(), "soft".to_string()],
962 },
963 )
964 .add_custom_rule(CustomValidationRule {
965 name: "performance_vs_accuracy_tradeoff".to_string(),
966 description: "Warn about performance implications of large ensembles".to_string(),
967 rule_type: RuleType::PerformanceWarning,
968 parameters: HashMap::new(),
969 })
970 .build()
971 }
972}
973
974#[allow(non_snake_case)]
975#[cfg(test)]
976mod tests {
977 use super::*;
978
979 #[test]
980 fn test_validation_result_creation() {
981 let result = ValidationResult::new(
982 ValidationSeverity::Warning,
983 "TestComponent",
984 "test_field",
985 "Test message",
986 )
987 .with_suggestions(vec!["Fix suggestion".to_string()])
988 .with_error_code("TEST_001");
989
990 assert_eq!(result.severity, ValidationSeverity::Warning);
991 assert_eq!(result.component, "TestComponent");
992 assert_eq!(result.field, "test_field");
993 assert_eq!(result.message, "Test message");
994 assert_eq!(result.suggestions.len(), 1);
995 assert_eq!(result.error_code, "TEST_001");
996 assert!(!result.is_error());
997 }
998
999 #[test]
1000 fn test_validation_report() {
1001 let mut report = ValidationReport::new();
1002
1003 report.add_result(ValidationResult::new(
1004 ValidationSeverity::Warning,
1005 "Component1",
1006 "field1",
1007 "Warning message",
1008 ));
1009
1010 report.add_result(ValidationResult::new(
1011 ValidationSeverity::Error,
1012 "Component2",
1013 "field2",
1014 "Error message",
1015 ));
1016
1017 assert_eq!(report.summary.warnings, 1);
1018 assert_eq!(report.summary.errors, 1);
1019 assert_eq!(report.summary.overall_status, ValidationStatus::Failed);
1020 assert!(report.has_errors());
1021 }
1022
1023 #[test]
1024 fn test_pipeline_config_validator() {
1025 let validator = PipelineConfigValidator::new();
1026
1027 #[derive(Debug)]
1029 struct MockConfig {
1030 n_jobs: i32,
1031 verbose: bool,
1032 }
1033
1034 let config = MockConfig {
1035 n_jobs: 4,
1036 verbose: true,
1037 };
1038
1039 let report = validator.validate_pipeline_config(&config);
1040
1041 assert_eq!(report.summary.overall_status, ValidationStatus::Passed);
1043 }
1044
1045 #[test]
1046 fn test_validation_builder() {
1047 let schema = ValidationBuilder::new("TestSchema")
1048 .add_field(
1049 "test_field",
1050 FieldConstraints {
1052 required: true,
1053 field_type: FieldType::Integer,
1054 constraints: vec![Constraint::Range {
1055 min: 1.0,
1056 max: 100.0,
1057 }],
1058 description: "Test field".to_string(),
1059 examples: vec!["50".to_string()],
1060 },
1061 )
1062 .build();
1063
1064 assert_eq!(schema.name, "TestSchema");
1065 assert_eq!(schema.fields.len(), 1);
1066 assert!(schema.fields.contains_key("test_field"));
1067 }
1068
1069 #[test]
1070 fn test_validated_parameter() {
1071 type JobCount = ValidatedParameter<1, 8>;
1073
1074 let valid_param = JobCount::new_runtime(4).unwrap();
1076 assert_eq!(valid_param.value(), 4);
1077
1078 let invalid_param = JobCount::new_runtime(10);
1080 assert!(invalid_param.is_err());
1081
1082 match invalid_param.unwrap_err() {
1083 ParameterValidationError::OutOfRange { value, min, max } => {
1084 assert_eq!(value, 10);
1085 assert_eq!(min, 1);
1086 assert_eq!(max, 8);
1087 }
1088 _ => panic!("Expected OutOfRange error"),
1089 }
1090 }
1091
1092 #[test]
1093 fn test_feature_flags() {
1094 struct TestFeatures;
1096 impl FeatureFlags for TestFeatures {
1097 const SUPPORTS_PARALLEL: bool = true;
1098 const SUPPORTS_GPU: bool = false;
1099 const REQUIRES_BLAS: bool = true;
1100 const MEMORY_INTENSIVE: bool = false;
1101 }
1102
1103 #[derive(Debug)]
1105 struct TestConfig;
1106 impl ValidConfig for TestConfig {
1107 type Error = ConfigurationValidationError;
1108 const IS_VALID: bool = true;
1109
1110 fn validate_config() -> Result<(), Self::Error> {
1111 Ok(())
1112 }
1113 }
1114
1115 let config = TestConfig;
1117 let feature_config = FeatureValidatedConfig::<TestConfig, TestFeatures>::new(config);
1118 assert!(feature_config.is_ok());
1119
1120 assert!(FeatureValidatedConfig::<TestConfig, TestFeatures>::supports_parallel());
1122 assert!(!FeatureValidatedConfig::<TestConfig, TestFeatures>::supports_gpu());
1123 }
1124
1125 #[test]
1126 fn test_pipeline_stage_compatibility() {
1127 #[derive(Debug)]
1129 struct TestStage1;
1130 #[derive(Debug)]
1131 struct TestStage2;
1132
1133 #[derive(Debug)]
1134 struct TestConfig;
1135 impl ValidConfig for TestConfig {
1136 type Error = ConfigurationValidationError;
1137 const IS_VALID: bool = true;
1138
1139 fn validate_config() -> Result<(), Self::Error> {
1140 Ok(())
1141 }
1142 }
1143
1144 impl PipelineStage for TestStage1 {
1145 type Input = i32;
1146 type Output = f64;
1147 type Config = TestConfig;
1148
1149 const STAGE_NAME: &'static str = "TestStage1";
1150 const IS_TRANSFORMATIVE: bool = true;
1151 const IS_TERMINAL: bool = false;
1152 }
1153
1154 impl PipelineStage for TestStage2 {
1155 type Input = f64;
1156 type Output = String;
1157 type Config = TestConfig;
1158
1159 const STAGE_NAME: &'static str = "TestStage2";
1160 const IS_TRANSFORMATIVE: bool = true;
1161 const IS_TERMINAL: bool = true;
1162 }
1163
1164 let stage1 = TestStage1;
1166 let stage2 = TestStage2;
1167 let _pipeline = ValidatedPipeline::<(TestStage1, TestStage2)>::new(stage1, stage2);
1168
1169 assert_eq!(TestStage1::STAGE_NAME, "TestStage1");
1171 assert!(TestStage1::IS_TRANSFORMATIVE);
1172 assert!(!TestStage1::IS_TERMINAL);
1173 assert!(TestStage2::IS_TERMINAL);
1174 }
1175
1176 #[test]
1177 fn test_typed_configuration_validator() {
1178 #[derive(Debug)]
1180 struct TestConfig {
1181 value: i32,
1182 }
1183
1184 fn validate_positive(config: &TestConfig) -> ValidationResult {
1186 if config.value > 0 {
1187 ValidationResult::new(
1188 ValidationSeverity::Info,
1189 "TestConfig",
1190 "value",
1191 "Value is positive",
1192 )
1193 } else {
1194 ValidationResult::new(
1195 ValidationSeverity::Error,
1196 "TestConfig",
1197 "value",
1198 "Value must be positive",
1199 )
1200 }
1201 }
1202
1203 fn validate_range(config: &TestConfig) -> ValidationResult {
1204 if config.value >= 1 && config.value <= 100 {
1205 ValidationResult::new(
1206 ValidationSeverity::Info,
1207 "TestConfig",
1208 "value",
1209 "Value is in range",
1210 )
1211 } else {
1212 ValidationResult::new(
1213 ValidationSeverity::Warning,
1214 "TestConfig",
1215 "value",
1216 "Value is outside recommended range",
1217 )
1218 }
1219 }
1220
1221 let validator = TypedConfigurationValidator::new([validate_positive, validate_range]);
1223
1224 let valid_config = TestConfig { value: 50 };
1226 let report = validator.validate(&valid_config);
1227 assert_eq!(report.summary.errors, 0);
1228
1229 let invalid_config = TestConfig { value: -10 };
1231 let report = validator.validate(&invalid_config);
1232 assert_eq!(report.summary.errors, 1);
1233 assert_eq!(report.summary.warnings, 1);
1234 }
1235
1236 #[test]
1237 fn test_parameter_validation_error_display() {
1238 let error = ParameterValidationError::OutOfRange {
1239 value: 15,
1240 min: 1,
1241 max: 10,
1242 };
1243 assert_eq!(
1244 error.to_string(),
1245 "Parameter value 15 is out of range [1, 10]"
1246 );
1247
1248 let error = ParameterValidationError::InvalidType {
1249 expected: "Integer".to_string(),
1250 actual: "String".to_string(),
1251 };
1252 assert_eq!(error.to_string(), "Expected type 'Integer', found 'String'");
1253
1254 let error = ParameterValidationError::MissingRequired {
1255 parameter: "alpha".to_string(),
1256 };
1257 assert_eq!(error.to_string(), "Required parameter 'alpha' is missing");
1258 }
1259
1260 #[test]
1261 fn test_configuration_validation_error_display() {
1262 let error = ConfigurationValidationError::FeatureNotSupported {
1263 feature: "GPU".to_string(),
1264 };
1265 assert_eq!(error.to_string(), "Feature 'GPU' is not supported");
1266
1267 let error = ConfigurationValidationError::IncompatibleFeatures {
1268 feature1: "Parallel".to_string(),
1269 feature2: "SingleThread".to_string(),
1270 };
1271 assert_eq!(
1272 error.to_string(),
1273 "Features 'Parallel' and 'SingleThread' are incompatible"
1274 );
1275 }
1276}