1use crate::error::StatsResult;
15use serde::{Deserialize, Serialize};
16use serde_json;
17use std::collections::{HashMap, HashSet};
18use std::fmt::Debug;
19
20pub struct APIConsistencyValidator {
22 pub config: ValidationConfig,
24 pub results: ValidationResults,
26 pub function_registry: FunctionRegistry,
28}
29
30#[derive(Debug, Clone)]
32pub struct ValidationConfig {
33 pub validate_parameter_names: bool,
35 pub validate_return_types: bool,
37 pub validate_error_handling: bool,
39 pub validate_documentation: bool,
41 pub validate_performance: bool,
43 pub validate_scipy_compatibility: bool,
45 pub strict_mode: bool,
47 pub naming_conventions: NamingConventions,
49}
50
51#[derive(Debug, Clone)]
53pub struct NamingConventions {
54 pub parameter_names: HashMap<String, Vec<String>>,
56 pub function_patterns: Vec<FunctionPattern>,
58 pub module_patterns: Vec<String>,
60}
61
62#[derive(Debug, Clone)]
64pub struct FunctionPattern {
65 pub description: String,
67 pub pattern: String,
69 pub category: FunctionCategory,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
75pub enum FunctionCategory {
76 DescriptiveStats,
78 StatisticalTests,
80 Distributions,
82 Regression,
84 Correlation,
86 Utilities,
88 Advanced,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ValidationResults {
95 pub overall_status: ValidationStatus,
97 pub check_results: Vec<ValidationCheck>,
99 pub inconsistencies: Vec<APIInconsistency>,
101 pub warnings: Vec<ValidationWarning>,
103 pub summary: ValidationSummary,
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub enum ValidationStatus {
110 Passed,
112 PassedWithWarnings,
114 Failed,
116 NotRun,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ValidationCheck {
123 pub name: String,
125 pub category: CheckCategory,
127 pub status: ValidationStatus,
129 pub description: String,
131 pub details: String,
133 pub severity: Severity,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub enum CheckCategory {
140 ParameterNaming,
142 ReturnTypes,
144 ErrorHandling,
146 Documentation,
148 Performance,
150 CrossModule,
152 ScipyCompatibility,
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub enum Severity {
159 Critical,
161 Major,
163 Minor,
165 Info,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct APIInconsistency {
172 pub inconsistency_type: InconsistencyType,
174 pub affected_functions: Vec<String>,
176 pub description: String,
178 pub suggested_fix: String,
180 pub severity: Severity,
182 pub impact: String,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub enum InconsistencyType {
189 ParameterNaming,
191 ReturnTypeInconsistency,
193 ErrorHandlingInconsistency,
195 DocumentationInconsistency,
197 PerformanceInconsistency,
199 ScipyCompatibilityIssue,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ValidationWarning {
206 pub message: String,
208 pub category: CheckCategory,
210 pub location: String,
212 pub suggestion: String,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct ValidationSummary {
219 pub total_checks: usize,
221 pub passed_checks: usize,
223 pub failed_checks: usize,
225 pub warning_count: usize,
227 pub critical_issues: usize,
229 pub consistency_score: f64,
231}
232
233#[derive(Debug, Clone)]
235pub struct FunctionRegistry {
236 pub functions_by_module: HashMap<String, Vec<FunctionSignature>>,
238 pub functions_by_category: HashMap<FunctionCategory, Vec<FunctionSignature>>,
240 pub parameter_usage: HashMap<String, ParameterUsage>,
242}
243
244#[derive(Debug, Clone)]
246pub struct FunctionSignature {
247 pub name: String,
249 pub module: String,
251 pub category: FunctionCategory,
253 pub parameters: Vec<ParameterInfo>,
255 pub return_type: ReturnTypeInfo,
257 pub error_types: Vec<String>,
259 pub documentation_status: DocumentationStatus,
261}
262
263#[derive(Debug, Clone)]
265pub struct ParameterInfo {
266 pub name: String,
268 pub param_type: String,
270 pub optional: bool,
272 pub default_value: Option<String>,
274 pub description: String,
276}
277
278#[derive(Debug, Clone)]
280pub struct ReturnTypeInfo {
281 pub type_name: String,
283 pub is_result: bool,
285 pub generic_params: Vec<String>,
287 pub description: String,
289}
290
291#[derive(Debug, Clone, PartialEq)]
293pub enum DocumentationStatus {
294 Complete,
296 Partial,
298 Missing,
300 PoorQuality,
302}
303
304#[derive(Debug, Clone)]
306pub struct ParameterUsage {
307 pub name: String,
309 pub usage_count: usize,
311 pub type_signatures: HashSet<String>,
313 pub modules: HashSet<String>,
315 pub alternative_names: Vec<String>,
317}
318
319impl Default for ValidationConfig {
320 fn default() -> Self {
321 Self {
322 validate_parameter_names: true,
323 validate_return_types: true,
324 validate_error_handling: true,
325 validate_documentation: true,
326 validate_performance: false, validate_scipy_compatibility: true,
328 strict_mode: false,
329 naming_conventions: NamingConventions::default(),
330 }
331 }
332}
333
334impl Default for NamingConventions {
335 fn default() -> Self {
336 let mut parameter_names = HashMap::new();
337
338 parameter_names.insert(
340 "data".to_string(),
341 vec!["data".to_string(), "x".to_string(), "array".to_string()],
342 );
343 parameter_names.insert(
344 "axis".to_string(),
345 vec!["axis".to_string(), "dim".to_string()],
346 );
347 parameter_names.insert(
348 "degrees_of_freedom".to_string(),
349 vec!["ddof".to_string(), "df".to_string()],
350 );
351 parameter_names.insert(
352 "alpha".to_string(),
353 vec!["alpha".to_string(), "significance".to_string()],
354 );
355 parameter_names.insert(
356 "alternative".to_string(),
357 vec!["alternative".to_string(), "alt".to_string()],
358 );
359 parameter_names.insert(
360 "method".to_string(),
361 vec!["method".to_string(), "algorithm".to_string()],
362 );
363
364 let function_patterns = vec![
365 FunctionPattern {
366 description: "Statistical test functions should end with 'test'".to_string(),
367 pattern: r".*test$".to_string(),
368 category: FunctionCategory::StatisticalTests,
369 },
370 FunctionPattern {
371 description: "Correlation functions should contain 'corr'".to_string(),
372 pattern: r".*corr.*".to_string(),
373 category: FunctionCategory::Correlation,
374 },
375 FunctionPattern {
376 description: "Distribution functions should be named after the distribution"
377 .to_string(),
378 pattern: r"^[a-z_]+$".to_string(),
379 category: FunctionCategory::Distributions,
380 },
381 ];
382
383 Self {
384 parameter_names,
385 function_patterns,
386 module_patterns: vec!["^[a-z_]+$".to_string()],
387 }
388 }
389}
390
391impl APIConsistencyValidator {
392 pub fn new(config: ValidationConfig) -> Self {
394 Self {
395 config,
396 results: ValidationResults::new(),
397 function_registry: FunctionRegistry::new(),
398 }
399 }
400
401 pub fn validate_all(&mut self) -> StatsResult<&ValidationResults> {
403 self.results = ValidationResults::new();
405
406 if self.config.validate_parameter_names {
408 self.validate_parameter_naming()?;
409 }
410
411 if self.config.validate_return_types {
412 self.validate_return_type_consistency()?;
413 }
414
415 if self.config.validate_error_handling {
416 self.validate_error_handling_consistency()?;
417 }
418
419 if self.config.validate_documentation {
420 self.validate_documentation_quality()?;
421 }
422
423 if self.config.validate_performance {
424 self.validate_performance_characteristics()?;
425 }
426
427 if self.config.validate_scipy_compatibility {
428 self.validate_scipy_compatibility()?;
429 }
430
431 self.compute_validation_summary();
433
434 self.results.overall_status = self.determine_overall_status();
436
437 Ok(&self.results)
438 }
439
440 fn validate_parameter_naming(&mut self) -> StatsResult<()> {
442 let mut check = ValidationCheck {
443 name: "Parameter Naming Consistency".to_string(),
444 category: CheckCategory::ParameterNaming,
445 status: ValidationStatus::NotRun,
446 description: "Checking consistency of parameter names across functions".to_string(),
447 details: String::new(),
448 severity: Severity::Major,
449 };
450
451 let mut parameter_analysis = HashMap::new();
453
454 for (module, functions) in &self.function_registry.functions_by_module {
455 for function in functions {
456 for param in &function.parameters {
457 let entry = parameter_analysis
458 .entry(param.name.clone())
459 .or_insert_with(ParameterUsageAnalysis::new);
460
461 entry.add_usage(
462 module.clone(),
463 function.name.clone(),
464 param.param_type.clone(),
465 );
466 }
467 }
468 }
469
470 let mut inconsistencies_found = 0;
472 let mut details = Vec::new();
473
474 for (param_name, analysis) in ¶meter_analysis {
475 if analysis.type_variations.len() > 1 {
476 let inconsistency = APIInconsistency {
478 inconsistency_type: InconsistencyType::ParameterNaming,
479 affected_functions: analysis.functions.clone(),
480 description: format!(
481 "Parameter '{}' used with different types: {:?}",
482 param_name, analysis.type_variations
483 ),
484 suggested_fix: format!(
485 "Standardize parameter '{}' to use consistent type across all functions",
486 param_name
487 ),
488 severity: Severity::Major,
489 impact: "May cause confusion for users and complicate generic programming"
490 .to_string(),
491 };
492
493 self.results.inconsistencies.push(inconsistency);
494 inconsistencies_found += 1;
495 }
496
497 if let Some(standard_names) = self
499 .config
500 .naming_conventions
501 .parameter_names
502 .get(param_name)
503 {
504 if !standard_names.contains(param_name) {
505 let warning = ValidationWarning {
506 message: format!(
507 "Parameter '{}' doesn't follow naming convention. Consider: {:?}",
508 param_name, standard_names
509 ),
510 category: CheckCategory::ParameterNaming,
511 location: format!("Functions: {:?}", analysis.functions),
512 suggestion: format!("Use one of the standard names: {:?}", standard_names),
513 };
514
515 self.results.warnings.push(warning);
516 }
517 }
518 }
519
520 details.push(format!(
521 "Analyzed {} unique parameter names",
522 parameter_analysis.len()
523 ));
524 details.push(format!(
525 "Found {} type inconsistencies",
526 inconsistencies_found
527 ));
528
529 check.details = details.join("; ");
530 check.status = if inconsistencies_found == 0 {
531 ValidationStatus::Passed
532 } else if self.config.strict_mode {
533 ValidationStatus::Failed
534 } else {
535 ValidationStatus::PassedWithWarnings
536 };
537
538 self.results.check_results.push(check);
539 Ok(())
540 }
541
542 fn validate_return_type_consistency(&mut self) -> StatsResult<()> {
544 let mut check = ValidationCheck {
545 name: "Return Type Consistency".to_string(),
546 category: CheckCategory::ReturnTypes,
547 status: ValidationStatus::NotRun,
548 description: "Checking consistency of return types across similar functions"
549 .to_string(),
550 details: String::new(),
551 severity: Severity::Major,
552 };
553
554 let mut return_type_patterns = HashMap::new();
555 let mut inconsistencies_found = 0;
556
557 for (category, functions) in &self.function_registry.functions_by_category {
559 let mut category_return_types = HashSet::new();
560
561 for function in functions {
562 category_return_types.insert(function.return_type.type_name.clone());
563 }
564
565 return_type_patterns.insert(category.clone(), category_return_types);
566
567 if functions.len() > 3 && return_type_patterns[category].len() > functions.len() / 2 {
569 let inconsistency = APIInconsistency {
570 inconsistency_type: InconsistencyType::ReturnTypeInconsistency,
571 affected_functions: functions.iter().map(|f| f.name.clone()).collect(),
572 description: format!(
573 "Functions in {:?} category have inconsistent return types: {:?}",
574 category, return_type_patterns[category]
575 ),
576 suggested_fix: "Standardize return types within functional categories"
577 .to_string(),
578 severity: Severity::Major,
579 impact: "Makes API harder to learn and use consistently".to_string(),
580 };
581
582 self.results.inconsistencies.push(inconsistency);
583 inconsistencies_found += 1;
584 }
585 }
586
587 let mut result_usage_patterns = HashMap::new();
589 for (module, functions) in &self.function_registry.functions_by_module {
590 let result_count = functions.iter().filter(|f| f.return_type.is_result).count();
591 let total_count = functions.len();
592
593 result_usage_patterns.insert(module.clone(), (result_count, total_count));
594 }
595
596 check.details = format!(
597 "Analyzed return types across {} categories, found {} inconsistencies",
598 return_type_patterns.len(),
599 inconsistencies_found
600 );
601
602 check.status = if inconsistencies_found == 0 {
603 ValidationStatus::Passed
604 } else {
605 ValidationStatus::Failed
606 };
607
608 self.results.check_results.push(check);
609 Ok(())
610 }
611
612 fn validate_error_handling_consistency(&mut self) -> StatsResult<()> {
614 let mut check = ValidationCheck {
615 name: "Error Handling Consistency".to_string(),
616 category: CheckCategory::ErrorHandling,
617 status: ValidationStatus::NotRun,
618 description: "Checking consistency of error handling patterns".to_string(),
619 details: String::new(),
620 severity: Severity::Critical,
621 };
622
623 let mut error_patterns = HashMap::new();
624 let mut inconsistencies_found = 0;
625
626 for (module, functions) in &self.function_registry.functions_by_module {
628 for function in functions {
629 if !function.return_type.is_result
631 && function.category != FunctionCategory::Utilities
632 {
633 let warning = ValidationWarning {
634 message: format!(
635 "Function '{}' in module '{}' doesn't return Result<T> but may fail",
636 function.name, module
637 ),
638 category: CheckCategory::ErrorHandling,
639 location: format!("{}::{}", module, function.name),
640 suggestion: "Consider returning Result<T> for fallible operations"
641 .to_string(),
642 };
643
644 self.results.warnings.push(warning);
645 }
646
647 for error_type in &function.error_types {
649 error_patterns
650 .entry(error_type.clone())
651 .or_insert_with(Vec::new)
652 .push(format!("{}::{}", module, function.name));
653 }
654 }
655 }
656
657 let expected_error_types = ["StatsError".to_string()];
659 for (error_type, functions) in &error_patterns {
660 if !expected_error_types.contains(error_type) {
661 let inconsistency = APIInconsistency {
662 inconsistency_type: InconsistencyType::ErrorHandlingInconsistency,
663 affected_functions: functions.clone(),
664 description: format!(
665 "Non-standard error type '{}' used in functions: {:?}",
666 error_type, functions
667 ),
668 suggested_fix: "Use StatsError for consistent error handling".to_string(),
669 severity: Severity::Major,
670 impact: "Inconsistent error handling makes error recovery difficult"
671 .to_string(),
672 };
673
674 self.results.inconsistencies.push(inconsistency);
675 inconsistencies_found += 1;
676 }
677 }
678
679 check.details = format!(
680 "Analyzed error handling in {} functions, found {} inconsistencies",
681 self.function_registry
682 .functions_by_module
683 .values()
684 .map(|funcs| funcs.len())
685 .sum::<usize>(),
686 inconsistencies_found
687 );
688
689 check.status = if inconsistencies_found == 0 {
690 ValidationStatus::Passed
691 } else {
692 ValidationStatus::Failed
693 };
694
695 self.results.check_results.push(check);
696 Ok(())
697 }
698
699 fn validate_documentation_quality(&mut self) -> StatsResult<()> {
701 let mut check = ValidationCheck {
702 name: "Documentation Quality".to_string(),
703 category: CheckCategory::Documentation,
704 status: ValidationStatus::NotRun,
705 description: "Checking completeness and quality of documentation".to_string(),
706 details: String::new(),
707 severity: Severity::Minor,
708 };
709
710 let mut doc_stats = DocumentationStats::new();
711 let mut poorly_documented_functions = Vec::new();
712
713 for (module, functions) in &self.function_registry.functions_by_module {
714 for function in functions {
715 doc_stats.total_functions += 1;
716
717 match function.documentation_status {
718 DocumentationStatus::Complete => doc_stats.complete_docs += 1,
719 DocumentationStatus::Partial => doc_stats.partial_docs += 1,
720 DocumentationStatus::Missing => {
721 doc_stats.missing_docs += 1;
722 poorly_documented_functions.push(format!("{}::{}", module, function.name));
723 }
724 DocumentationStatus::PoorQuality => {
725 doc_stats.poor_quality_docs += 1;
726 poorly_documented_functions.push(format!("{}::{}", module, function.name));
727 }
728 }
729 }
730 }
731
732 let completion_rate = doc_stats.complete_docs as f64 / doc_stats.total_functions as f64;
734
735 if completion_rate < 0.8 {
736 let inconsistency = APIInconsistency {
737 inconsistency_type: InconsistencyType::DocumentationInconsistency,
738 affected_functions: poorly_documented_functions,
739 description: format!(
740 "Documentation completion rate is {:.1}%, below 80% threshold",
741 completion_rate * 100.0
742 ),
743 suggested_fix: "Add comprehensive documentation to all public functions"
744 .to_string(),
745 severity: Severity::Minor,
746 impact: "Poor documentation reduces library usability and adoption".to_string(),
747 };
748
749 self.results.inconsistencies.push(inconsistency);
750 }
751
752 check.details = format!(
753 "Documentation completion: {:.1}% ({}/{} functions documented)",
754 completion_rate * 100.0,
755 doc_stats.complete_docs,
756 doc_stats.total_functions
757 );
758
759 check.status = if completion_rate >= 0.8 {
760 ValidationStatus::Passed
761 } else {
762 ValidationStatus::PassedWithWarnings
763 };
764
765 self.results.check_results.push(check);
766 Ok(())
767 }
768
769 fn validate_performance_characteristics(&mut self) -> StatsResult<()> {
771 let mut check = ValidationCheck {
772 name: "Performance Characteristics".to_string(),
773 category: CheckCategory::Performance,
774 status: ValidationStatus::NotRun,
775 description: "Checking consistency of performance characteristics".to_string(),
776 details: String::new(),
777 severity: Severity::Minor,
778 };
779
780 let mut performance_issues = Vec::new();
784
785 for (module, functions) in &self.function_registry.functions_by_module {
787 for function in functions {
788 if function.category == FunctionCategory::DescriptiveStats
789 && !function.name.contains("simd")
790 && !function.name.contains("parallel")
791 {
792 let simd_variant_name = format!("{}_simd", function.name);
794 let has_simd_variant = functions.iter().any(|f| f.name == simd_variant_name);
795
796 if !has_simd_variant {
797 performance_issues.push(format!("{}::{}", module, function.name));
798 }
799 }
800 }
801 }
802
803 if !performance_issues.is_empty() {
804 let warning = ValidationWarning {
805 message: format!(
806 "{} functions might benefit from SIMD optimization",
807 performance_issues.len()
808 ),
809 category: CheckCategory::Performance,
810 location: "Various modules".to_string(),
811 suggestion:
812 "Consider implementing SIMD variants for performance-critical functions"
813 .to_string(),
814 };
815
816 self.results.warnings.push(warning);
817 }
818
819 check.details = format!(
820 "Analyzed performance patterns, identified {} optimization opportunities",
821 performance_issues.len()
822 );
823
824 check.status = ValidationStatus::Passed; self.results.check_results.push(check);
827 Ok(())
828 }
829
830 fn validate_scipy_compatibility(&mut self) -> StatsResult<()> {
832 let mut check = ValidationCheck {
833 name: "SciPy Compatibility".to_string(),
834 category: CheckCategory::ScipyCompatibility,
835 status: ValidationStatus::NotRun,
836 description: "Checking compatibility with SciPy API patterns".to_string(),
837 details: String::new(),
838 severity: Severity::Major,
839 };
840
841 let scipy_functions = self.get_expected_scipy_functions();
843 let mut compatibility_issues = Vec::new();
844
845 for (scipy_name, expected_sig) in &scipy_functions {
846 let mut found = false;
848 for functions in self.function_registry.functions_by_module.values() {
849 for function in functions {
850 if function.name == *scipy_name {
851 found = true;
852
853 if !self.check_parameter_compatibility(
855 &function.parameters,
856 &expected_sig.parameters,
857 ) {
858 compatibility_issues.push(format!(
859 "Function '{}' has incompatible parameters with SciPy",
860 scipy_name
861 ));
862 }
863 break;
864 }
865 }
866 if found {
867 break;
868 }
869 }
870
871 if !found {
872 compatibility_issues.push(format!(
873 "Missing SciPy-compatible function: '{}'",
874 scipy_name
875 ));
876 }
877 }
878
879 if !compatibility_issues.is_empty() {
880 let inconsistency = APIInconsistency {
881 inconsistency_type: InconsistencyType::ScipyCompatibilityIssue,
882 affected_functions: compatibility_issues.clone(),
883 description: format!(
884 "Found {} SciPy compatibility issues",
885 compatibility_issues.len()
886 ),
887 suggested_fix:
888 "Implement missing functions or adjust parameters for SciPy compatibility"
889 .to_string(),
890 severity: Severity::Major,
891 impact: "Reduces ease of migration from SciPy".to_string(),
892 };
893
894 self.results.inconsistencies.push(inconsistency);
895 }
896
897 check.details = format!(
898 "Checked {} SciPy functions, found {} compatibility issues",
899 scipy_functions.len(),
900 compatibility_issues.len()
901 );
902
903 check.status = if compatibility_issues.is_empty() {
904 ValidationStatus::Passed
905 } else {
906 ValidationStatus::Failed
907 };
908
909 self.results.check_results.push(check);
910 Ok(())
911 }
912
913 fn get_expected_scipy_functions(&self) -> HashMap<String, FunctionSignature> {
915 let mut functions = HashMap::new();
916
917 functions.insert(
919 "mean".to_string(),
920 FunctionSignature {
921 name: "mean".to_string(),
922 module: "stats".to_string(),
923 category: FunctionCategory::DescriptiveStats,
924 parameters: vec![ParameterInfo {
925 name: "data".to_string(),
926 param_type: "ArrayView1<F>".to_string(),
927 optional: false,
928 default_value: None,
929 description: "Input data".to_string(),
930 }],
931 return_type: ReturnTypeInfo {
932 type_name: "StatsResult<F>".to_string(),
933 is_result: true,
934 generic_params: vec!["F".to_string()],
935 description: "Mean value".to_string(),
936 },
937 error_types: vec!["StatsError".to_string()],
938 documentation_status: DocumentationStatus::Complete,
939 },
940 );
941
942 functions.insert(
943 "std".to_string(),
944 FunctionSignature {
945 name: "std".to_string(),
946 module: "stats".to_string(),
947 category: FunctionCategory::DescriptiveStats,
948 parameters: vec![
949 ParameterInfo {
950 name: "data".to_string(),
951 param_type: "ArrayView1<F>".to_string(),
952 optional: false,
953 default_value: None,
954 description: "Input data".to_string(),
955 },
956 ParameterInfo {
957 name: "ddof".to_string(),
958 param_type: "usize".to_string(),
959 optional: true,
960 default_value: Some("0".to_string()),
961 description: "Delta degrees of freedom".to_string(),
962 },
963 ],
964 return_type: ReturnTypeInfo {
965 type_name: "StatsResult<F>".to_string(),
966 is_result: true,
967 generic_params: vec!["F".to_string()],
968 description: "Standard deviation".to_string(),
969 },
970 error_types: vec!["StatsError".to_string()],
971 documentation_status: DocumentationStatus::Complete,
972 },
973 );
974
975 functions
978 }
979
980 fn check_parameter_compatibility(
982 &self,
983 actual: &[ParameterInfo],
984 expected: &[ParameterInfo],
985 ) -> bool {
986 if actual.len() != expected.len() {
987 return false;
988 }
989
990 for (actual_param, expected_param) in actual.iter().zip(expected.iter()) {
991 if actual_param.name != expected_param.name {
992 return false;
993 }
994
995 if actual_param.param_type != expected_param.param_type {
997 return false;
998 }
999 }
1000
1001 true
1002 }
1003
1004 fn compute_validation_summary(&mut self) {
1006 let total_checks = self.results.check_results.len();
1007 let passed_checks = self
1008 .results
1009 .check_results
1010 .iter()
1011 .filter(|c| c.status == ValidationStatus::Passed)
1012 .count();
1013 let failed_checks = self
1014 .results
1015 .check_results
1016 .iter()
1017 .filter(|c| c.status == ValidationStatus::Failed)
1018 .count();
1019 let warning_count = self.results.warnings.len();
1020 let critical_issues = self
1021 .results
1022 .inconsistencies
1023 .iter()
1024 .filter(|i| i.severity == Severity::Critical)
1025 .count();
1026
1027 let consistency_score = if total_checks > 0 {
1029 let base_score = (passed_checks as f64 / total_checks as f64) * 100.0;
1030 let warning_penalty = (warning_count as f64 * 2.0).min(20.0);
1031 let critical_penalty = (critical_issues as f64 * 10.0).min(50.0);
1032
1033 (base_score - warning_penalty - critical_penalty).max(0.0)
1034 } else {
1035 0.0
1036 };
1037
1038 self.results.summary = ValidationSummary {
1039 total_checks,
1040 passed_checks,
1041 failed_checks,
1042 warning_count,
1043 critical_issues,
1044 consistency_score,
1045 };
1046 }
1047
1048 fn determine_overall_status(&self) -> ValidationStatus {
1050 let critical_issues = self
1051 .results
1052 .inconsistencies
1053 .iter()
1054 .any(|i| i.severity == Severity::Critical);
1055
1056 let failed_checks = self
1057 .results
1058 .check_results
1059 .iter()
1060 .any(|c| c.status == ValidationStatus::Failed);
1061
1062 if critical_issues || (failed_checks && self.config.strict_mode) {
1063 ValidationStatus::Failed
1064 } else if !self.results.warnings.is_empty() || failed_checks {
1065 ValidationStatus::PassedWithWarnings
1066 } else {
1067 ValidationStatus::Passed
1068 }
1069 }
1070
1071 pub fn register_function(&mut self, functionsig: FunctionSignature) {
1073 self.function_registry
1075 .functions_by_module
1076 .entry(functionsig.module.clone())
1077 .or_default()
1078 .push(functionsig.clone());
1079
1080 self.function_registry
1082 .functions_by_category
1083 .entry(functionsig.category.clone())
1084 .or_default()
1085 .push(functionsig.clone());
1086
1087 for param in &functionsig.parameters {
1089 let usage = self
1090 .function_registry
1091 .parameter_usage
1092 .entry(param.name.clone())
1093 .or_insert_with(|| ParameterUsage {
1094 name: param.name.clone(),
1095 usage_count: 0,
1096 type_signatures: HashSet::new(),
1097 modules: HashSet::new(),
1098 alternative_names: Vec::new(),
1099 });
1100
1101 usage.usage_count += 1;
1102 usage.type_signatures.insert(param.param_type.clone());
1103 usage.modules.insert(functionsig.module.clone());
1104 }
1105 }
1106
1107 pub fn generate_report(&self) -> ValidationReport {
1109 ValidationReport::new(&self.results)
1110 }
1111}
1112
1113impl ValidationResults {
1114 fn new() -> Self {
1115 Self {
1116 overall_status: ValidationStatus::NotRun,
1117 check_results: Vec::new(),
1118 inconsistencies: Vec::new(),
1119 warnings: Vec::new(),
1120 summary: ValidationSummary {
1121 total_checks: 0,
1122 passed_checks: 0,
1123 failed_checks: 0,
1124 warning_count: 0,
1125 critical_issues: 0,
1126 consistency_score: 0.0,
1127 },
1128 }
1129 }
1130}
1131
1132impl FunctionRegistry {
1133 fn new() -> Self {
1134 Self {
1135 functions_by_module: HashMap::new(),
1136 functions_by_category: HashMap::new(),
1137 parameter_usage: HashMap::new(),
1138 }
1139 }
1140}
1141
1142#[derive(Debug)]
1144struct ParameterUsageAnalysis {
1145 functions: Vec<String>,
1146 modules: HashSet<String>,
1147 type_variations: HashSet<String>,
1148}
1149
1150impl ParameterUsageAnalysis {
1151 fn new() -> Self {
1152 Self {
1153 functions: Vec::new(),
1154 modules: HashSet::new(),
1155 type_variations: HashSet::new(),
1156 }
1157 }
1158
1159 fn add_usage(&mut self, module: String, function: String, paramtype: String) {
1160 self.functions.push(format!("{}::{}", module, function));
1161 self.modules.insert(module);
1162 self.type_variations.insert(paramtype);
1163 }
1164}
1165
1166#[derive(Debug)]
1168struct DocumentationStats {
1169 total_functions: usize,
1170 complete_docs: usize,
1171 partial_docs: usize,
1172 missing_docs: usize,
1173 poor_quality_docs: usize,
1174}
1175
1176impl DocumentationStats {
1177 fn new() -> Self {
1178 Self {
1179 total_functions: 0,
1180 complete_docs: 0,
1181 partial_docs: 0,
1182 missing_docs: 0,
1183 poor_quality_docs: 0,
1184 }
1185 }
1186}
1187
1188pub struct ValidationReport {
1190 results: ValidationResults,
1191}
1192
1193impl ValidationReport {
1194 pub fn new(results: &ValidationResults) -> Self {
1195 Self {
1196 results: results.clone(),
1197 }
1198 }
1199
1200 pub fn to_markdown(&self) -> String {
1202 let mut report = String::new();
1203
1204 report.push_str("# API Consistency Validation Report\n\n");
1205
1206 report.push_str(&format!(
1208 "**Overall Status:** {:?}\n",
1209 self.results.overall_status
1210 ));
1211 report.push_str(&format!(
1212 "**Consistency Score:** {:.1}%\n\n",
1213 self.results.summary.consistency_score
1214 ));
1215
1216 report.push_str("## Summary\n\n");
1218 report.push_str(&format!(
1219 "- **Total Checks:** {}\n",
1220 self.results.summary.total_checks
1221 ));
1222 report.push_str(&format!(
1223 "- **Passed:** {}\n",
1224 self.results.summary.passed_checks
1225 ));
1226 report.push_str(&format!(
1227 "- **Failed:** {}\n",
1228 self.results.summary.failed_checks
1229 ));
1230 report.push_str(&format!(
1231 "- **Warnings:** {}\n",
1232 self.results.summary.warning_count
1233 ));
1234 report.push_str(&format!(
1235 "- **Critical Issues:** {}\n\n",
1236 self.results.summary.critical_issues
1237 ));
1238
1239 report.push_str("## Check Results\n\n");
1241 for check in &self.results.check_results {
1242 report.push_str(&format!("### {}\n", check.name));
1243 report.push_str(&format!("**Status:** {:?}\n", check.status));
1244 report.push_str(&format!("**Category:** {:?}\n", check.category));
1245 report.push_str(&format!("**Description:** {}\n", check.description));
1246 report.push_str(&format!("**Details:** {}\n\n", check.details));
1247 }
1248
1249 if !self.results.inconsistencies.is_empty() {
1251 report.push_str("## Inconsistencies Found\n\n");
1252 for (i, inconsistency) in self.results.inconsistencies.iter().enumerate() {
1253 report.push_str(&format!("### Issue {}\n", i + 1));
1254 report.push_str(&format!(
1255 "**Type:** {:?}\n",
1256 inconsistency.inconsistency_type
1257 ));
1258 report.push_str(&format!("**Severity:** {:?}\n", inconsistency.severity));
1259 report.push_str(&format!("**Description:** {}\n", inconsistency.description));
1260 report.push_str(&format!(
1261 "**Suggested Fix:** {}\n",
1262 inconsistency.suggested_fix
1263 ));
1264 report.push_str(&format!("**Impact:** {}\n\n", inconsistency.impact));
1265 }
1266 }
1267
1268 if !self.results.warnings.is_empty() {
1270 report.push_str("## Warnings\n\n");
1271 for warning in &self.results.warnings {
1272 report.push_str(&format!(
1273 "- **{}:** {} ({})\n",
1274 warning.location, warning.message, warning.suggestion
1275 ));
1276 }
1277 }
1278
1279 report
1280 }
1281
1282 pub fn to_json(&self) -> serde_json::Result<String> {
1284 serde_json::to_string_pretty(&self.results)
1285 }
1286}
1287
1288#[allow(dead_code)]
1290pub fn validate_api_consistency(
1291 config: Option<ValidationConfig>,
1292) -> StatsResult<ValidationResults> {
1293 let config = config.unwrap_or_default();
1294 let mut validator = APIConsistencyValidator::new(config);
1295
1296 validator.validate_all()?;
1300 Ok(validator.results)
1301}
1302
1303#[cfg(test)]
1304mod tests {
1305 use super::*;
1306
1307 #[test]
1308 fn test_validation_config_default() {
1309 let config = ValidationConfig::default();
1310 assert!(config.validate_parameter_names);
1311 assert!(config.validate_return_types);
1312 assert!(config.validate_error_handling);
1313 assert!(!config.strict_mode);
1314 }
1315
1316 #[test]
1317 fn test_function_registry() {
1318 let registry = FunctionRegistry::new();
1319
1320 let functionsig = FunctionSignature {
1321 name: "mean".to_string(),
1322 module: "descriptive".to_string(),
1323 category: FunctionCategory::DescriptiveStats,
1324 parameters: vec![ParameterInfo {
1325 name: "data".to_string(),
1326 param_type: "ArrayView1<f64>".to_string(),
1327 optional: false,
1328 default_value: None,
1329 description: "Input data".to_string(),
1330 }],
1331 return_type: ReturnTypeInfo {
1332 type_name: "StatsResult<f64>".to_string(),
1333 is_result: true,
1334 generic_params: vec!["f64".to_string()],
1335 description: "Mean value".to_string(),
1336 },
1337 error_types: vec!["StatsError".to_string()],
1338 documentation_status: DocumentationStatus::Complete,
1339 };
1340
1341 let mut validator = APIConsistencyValidator::new(ValidationConfig::default());
1342 validator.register_function(functionsig);
1343
1344 assert_eq!(validator.function_registry.functions_by_module.len(), 1);
1345 assert_eq!(validator.function_registry.functions_by_category.len(), 1);
1346 }
1347
1348 #[test]
1349 fn test_validation_report() {
1350 let results = ValidationResults::new();
1351 let report = ValidationReport::new(&results);
1352 let markdown = report.to_markdown();
1353
1354 assert!(markdown.contains("API Consistency Validation Report"));
1355 assert!(markdown.contains("Overall Status"));
1356 }
1357}