1use crate::core::FileMetrics;
86use crate::effects::{AnalysisValidation, validation_success};
87use crate::errors::AnalysisError;
88use serde::Serialize;
89use std::path::PathBuf;
90use stillwater::predicate::Predicate;
91use stillwater::refined::{FieldError, ValidationFieldExt};
92use stillwater::{NonEmptyVec, Validation};
93
94#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
116pub struct FieldPath(Vec<String>);
117
118impl FieldPath {
119 pub fn root() -> Self {
121 Self(Vec::new())
122 }
123
124 pub fn new(field: impl Into<String>) -> Self {
126 Self(vec![field.into()])
127 }
128
129 pub fn push(&self, field: impl Into<String>) -> Self {
131 let mut path = self.0.clone();
132 path.push(field.into());
133 Self(path)
134 }
135
136 pub fn as_string(&self) -> String {
138 self.0.join(".")
139 }
140
141 pub fn is_root(&self) -> bool {
143 self.0.is_empty()
144 }
145
146 pub fn len(&self) -> usize {
148 self.0.len()
149 }
150
151 pub fn is_empty(&self) -> bool {
153 self.0.is_empty()
154 }
155
156 pub fn last(&self) -> Option<&str> {
158 self.0.last().map(|s| s.as_str())
159 }
160
161 pub fn segments(&self) -> &[String] {
163 &self.0
164 }
165}
166
167impl std::fmt::Display for FieldPath {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 write!(f, "{}", self.as_string())
170 }
171}
172
173impl From<&str> for FieldPath {
174 fn from(s: &str) -> Self {
175 Self::new(s)
176 }
177}
178
179impl From<String> for FieldPath {
180 fn from(s: String) -> Self {
181 Self::new(s)
182 }
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
209pub struct ValidationError {
210 pub field: FieldPath,
212 pub message: String,
214 pub expected: Option<String>,
216 pub actual: Option<String>,
218}
219
220impl ValidationError {
221 pub fn at_field(field: &FieldPath, message: impl Into<String>) -> Self {
223 Self {
224 field: field.clone(),
225 message: message.into(),
226 expected: None,
227 actual: None,
228 }
229 }
230
231 pub fn for_field(field: impl Into<String>, message: impl Into<String>) -> Self {
233 Self {
234 field: FieldPath::new(field),
235 message: message.into(),
236 expected: None,
237 actual: None,
238 }
239 }
240
241 pub fn with_context(mut self, expected: impl Into<String>, actual: impl Into<String>) -> Self {
243 self.expected = Some(expected.into());
244 self.actual = Some(actual.into());
245 self
246 }
247
248 pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
250 self.expected = Some(expected.into());
251 self
252 }
253
254 pub fn with_actual(mut self, actual: impl Into<String>) -> Self {
256 self.actual = Some(actual.into());
257 self
258 }
259}
260
261impl std::fmt::Display for ValidationError {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 if self.field.is_root() {
264 write!(f, "{}", self.message)?;
265 } else {
266 write!(f, "{}: {}", self.field, self.message)?;
267 }
268
269 if let (Some(expected), Some(actual)) = (&self.expected, &self.actual) {
270 write!(f, " (expected: {}, got: {})", expected, actual)?;
271 } else if let Some(expected) = &self.expected {
272 write!(f, " (expected: {})", expected)?;
273 } else if let Some(actual) = &self.actual {
274 write!(f, " (got: {})", actual)?;
275 }
276
277 Ok(())
278 }
279}
280
281impl std::error::Error for ValidationError {}
282
283#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
304pub struct FileError {
305 pub path: PathBuf,
307 pub line: Option<u32>,
309 pub column: Option<u32>,
311 pub message: String,
313 pub error_code: Option<String>,
315}
316
317impl FileError {
318 pub fn new(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
320 Self {
321 path: path.into(),
322 line: None,
323 column: None,
324 message: message.into(),
325 error_code: None,
326 }
327 }
328
329 pub fn at_location(mut self, line: u32, column: u32) -> Self {
331 self.line = Some(line);
332 self.column = Some(column);
333 self
334 }
335
336 pub fn at_line(mut self, line: u32) -> Self {
338 self.line = Some(line);
339 self
340 }
341
342 pub fn with_code(mut self, code: impl Into<String>) -> Self {
344 self.error_code = Some(code.into());
345 self
346 }
347
348 pub fn from_parse_error(path: impl Into<PathBuf>, error: impl std::fmt::Display) -> Self {
350 Self::new(path, error.to_string()).with_code("E010")
351 }
352
353 pub fn from_analysis_error(path: impl Into<PathBuf>, error: &AnalysisError) -> Self {
355 let path = path.into();
356 let message = error.to_string();
357
358 let line = if let AnalysisError::ParseError { line, .. } = error {
360 *line
361 } else {
362 None
363 };
364
365 let mut file_error = Self::new(path, message);
366 if let Some(l) = line {
367 file_error.line = Some(l as u32);
368 }
369
370 file_error
371 }
372}
373
374impl std::fmt::Display for FileError {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 write!(f, "{}", self.path.display())?;
377 if let Some(line) = self.line {
378 write!(f, ":{}", line)?;
379 if let Some(column) = self.column {
380 write!(f, ":{}", column)?;
381 }
382 }
383 write!(f, ": {}", self.message)?;
384 if let Some(code) = &self.error_code {
385 write!(f, " [{}]", code)?;
386 }
387 Ok(())
388 }
389}
390
391impl std::error::Error for FileError {}
392
393impl From<FileError> for AnalysisError {
394 fn from(err: FileError) -> Self {
395 AnalysisError::parse_with_path(&err.message, &err.path)
396 }
397}
398
399#[derive(Clone, Debug, Default)]
421pub struct ValidatedFileSet<T> {
422 pub valid: Vec<T>,
424 pub errors: Vec<FileError>,
426}
427
428impl<T> ValidatedFileSet<T> {
429 pub fn empty() -> Self {
431 Self {
432 valid: Vec::new(),
433 errors: Vec::new(),
434 }
435 }
436
437 pub fn all_valid(valid: Vec<T>) -> Self {
439 Self {
440 valid,
441 errors: Vec::new(),
442 }
443 }
444
445 pub fn all_errors(errors: Vec<FileError>) -> Self {
447 Self {
448 valid: Vec::new(),
449 errors,
450 }
451 }
452
453 pub fn has_errors(&self) -> bool {
455 !self.errors.is_empty()
456 }
457
458 pub fn has_valid(&self) -> bool {
460 !self.valid.is_empty()
461 }
462
463 pub fn is_partial_success(&self) -> bool {
465 self.has_valid() && self.has_errors()
466 }
467
468 pub fn is_all_success(&self) -> bool {
470 self.has_valid() && !self.has_errors()
471 }
472
473 pub fn is_all_failed(&self) -> bool {
475 !self.has_valid() && self.has_errors()
476 }
477
478 pub fn valid_count(&self) -> usize {
480 self.valid.len()
481 }
482
483 pub fn error_count(&self) -> usize {
485 self.errors.len()
486 }
487
488 pub fn into_strict_result(self) -> Result<Vec<T>, Vec<FileError>> {
490 if self.errors.is_empty() {
491 Ok(self.valid)
492 } else {
493 Err(self.errors)
494 }
495 }
496
497 pub fn into_lenient_result(self) -> Result<Vec<T>, Vec<FileError>> {
499 if self.valid.is_empty() && !self.errors.is_empty() {
500 Err(self.errors)
501 } else {
502 Ok(self.valid)
503 }
504 }
505
506 pub fn add_valid(&mut self, item: T) {
508 self.valid.push(item);
509 }
510
511 pub fn add_error(&mut self, error: FileError) {
513 self.errors.push(error);
514 }
515
516 pub fn merge(&mut self, other: ValidatedFileSet<T>) {
518 self.valid.extend(other.valid);
519 self.errors.extend(other.errors);
520 }
521}
522
523impl<T: Serialize> Serialize for ValidatedFileSet<T> {
524 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
525 where
526 S: serde::Serializer,
527 {
528 use serde::ser::SerializeStruct;
529
530 let mut state = serializer.serialize_struct("ValidatedFileSet", 4)?;
531 state.serialize_field("valid_count", &self.valid.len())?;
532 state.serialize_field("error_count", &self.errors.len())?;
533 state.serialize_field("valid", &self.valid)?;
534 state.serialize_field("errors", &self.errors)?;
535 state.end()
536 }
537}
538
539pub trait FieldContextExt<T, E> {
548 fn with_field_path(self, path: &FieldPath) -> Validation<T, NonEmptyVec<ValidationError>>
557 where
558 E: std::fmt::Display;
559
560 fn with_field_name(self, field: &str) -> Validation<T, NonEmptyVec<ValidationError>>
569 where
570 E: std::fmt::Display;
571}
572
573impl<T, E> FieldContextExt<T, E> for Validation<T, NonEmptyVec<E>> {
574 fn with_field_path(self, path: &FieldPath) -> Validation<T, NonEmptyVec<ValidationError>>
575 where
576 E: std::fmt::Display,
577 {
578 match self {
579 Validation::Success(value) => Validation::Success(value),
580 Validation::Failure(errors) => {
581 let field_errors: Vec<ValidationError> = errors
582 .into_iter()
583 .map(|e| ValidationError::at_field(path, e.to_string()))
584 .collect();
585 Validation::Failure(
586 NonEmptyVec::from_vec(field_errors).expect("errors came from non-empty vec"),
587 )
588 }
589 }
590 }
591
592 fn with_field_name(self, field: &str) -> Validation<T, NonEmptyVec<ValidationError>>
593 where
594 E: std::fmt::Display,
595 {
596 self.with_field_path(&FieldPath::new(field))
597 }
598}
599
600pub use stillwater::refined::FieldError as StillwaterFieldError;
602
603pub fn validate_field<T, E>(
605 field: &'static str,
606 validation: Validation<T, E>,
607) -> Validation<T, FieldError<E>> {
608 validation.with_field(field)
609}
610
611pub trait EnsureExt<T> {
636 fn ensure<P, E>(self, predicate: P, error: E) -> Validation<T, NonEmptyVec<E>>
641 where
642 P: Predicate<T>;
643
644 fn ensure_with<P, E, F>(self, predicate: P, error_fn: F) -> Validation<T, NonEmptyVec<E>>
649 where
650 P: Predicate<T>,
651 F: FnOnce(&T) -> E;
652}
653
654impl<T> EnsureExt<T> for T {
655 fn ensure<P, E>(self, predicate: P, error: E) -> Validation<T, NonEmptyVec<E>>
656 where
657 P: Predicate<T>,
658 {
659 if predicate.check(&self) {
660 Validation::Success(self)
661 } else {
662 Validation::Failure(NonEmptyVec::new(error, Vec::new()))
663 }
664 }
665
666 fn ensure_with<P, E, F>(self, predicate: P, error_fn: F) -> Validation<T, NonEmptyVec<E>>
667 where
668 P: Predicate<T>,
669 F: FnOnce(&T) -> E,
670 {
671 if predicate.check(&self) {
672 Validation::Success(self)
673 } else {
674 let error = error_fn(&self);
675 Validation::Failure(NonEmptyVec::new(error, Vec::new()))
676 }
677 }
678}
679
680#[derive(Debug, Clone)]
689pub enum ValidatedFileResults {
690 AllSucceeded(Vec<FileMetrics>),
692
693 PartialSuccess {
696 succeeded: Vec<FileMetrics>,
698 failures: NonEmptyVec<AnalysisError>,
700 },
701
702 AllFailed(NonEmptyVec<AnalysisError>),
704}
705
706impl ValidatedFileResults {
707 pub fn from_validations(validations: Vec<AnalysisValidation<FileMetrics>>) -> Self {
709 let mut succeeded = Vec::new();
710 let mut failures: Vec<AnalysisError> = Vec::new();
711
712 for validation in validations {
713 match validation {
714 Validation::Success(metrics) => succeeded.push(metrics),
715 Validation::Failure(errors) => {
716 failures.extend(errors);
717 }
718 }
719 }
720
721 match (succeeded.is_empty(), failures.is_empty()) {
722 (false, true) => ValidatedFileResults::AllSucceeded(succeeded),
723 (false, false) => ValidatedFileResults::PartialSuccess {
724 succeeded,
725 failures: NonEmptyVec::from_vec(failures)
726 .expect("failures cannot be empty when not all succeeded"),
727 },
728 (true, false) => ValidatedFileResults::AllFailed(
729 NonEmptyVec::from_vec(failures).expect("failures cannot be empty when all failed"),
730 ),
731 (true, true) => ValidatedFileResults::AllSucceeded(Vec::new()),
732 }
733 }
734
735 pub fn succeeded(&self) -> &[FileMetrics] {
737 match self {
738 ValidatedFileResults::AllSucceeded(metrics) => metrics,
739 ValidatedFileResults::PartialSuccess { succeeded, .. } => succeeded,
740 ValidatedFileResults::AllFailed(_) => &[],
741 }
742 }
743
744 pub fn failures(&self) -> Option<&NonEmptyVec<AnalysisError>> {
746 match self {
747 ValidatedFileResults::AllSucceeded(_) => None,
748 ValidatedFileResults::PartialSuccess { failures, .. } => Some(failures),
749 ValidatedFileResults::AllFailed(failures) => Some(failures),
750 }
751 }
752
753 pub fn is_all_success(&self) -> bool {
755 matches!(self, ValidatedFileResults::AllSucceeded(_))
756 }
757
758 pub fn has_failures(&self) -> bool {
760 !matches!(self, ValidatedFileResults::AllSucceeded(_))
761 }
762
763 pub fn into_validation(self) -> AnalysisValidation<Vec<FileMetrics>> {
765 match self {
766 ValidatedFileResults::AllSucceeded(metrics) => validation_success(metrics),
767 ValidatedFileResults::PartialSuccess { failures, .. } => Validation::Failure(failures),
768 ValidatedFileResults::AllFailed(failures) => Validation::Failure(failures),
769 }
770 }
771
772 pub fn into_lenient_result(self) -> Result<Vec<FileMetrics>, NonEmptyVec<AnalysisError>> {
777 match self {
778 ValidatedFileResults::AllSucceeded(metrics) => Ok(metrics),
779 ValidatedFileResults::PartialSuccess { succeeded, .. } => Ok(succeeded),
780 ValidatedFileResults::AllFailed(failures) => Err(failures),
781 }
782 }
783}
784
785pub mod predicates {
793 use stillwater::predicate::*;
794
795 pub fn high_complexity(warning_threshold: u32, critical_threshold: u32) -> impl Predicate<u32> {
799 ge(warning_threshold).and(lt(critical_threshold))
800 }
801
802 pub fn critical_complexity(threshold: u32) -> impl Predicate<u32> {
806 ge(threshold)
807 }
808
809 pub fn within_bounds(min: u32, max: u32) -> impl Predicate<u32> {
811 ge(min).and(le(max))
812 }
813
814 pub fn acceptable_file_length(max_length: usize) -> impl Predicate<usize> {
816 le(max_length)
817 }
818
819 pub fn acceptable_nesting(max_depth: u32) -> impl Predicate<u32> {
821 le(max_depth)
822 }
823
824 pub fn acceptable_function_length(max_lines: usize) -> impl Predicate<usize> {
826 le(max_lines)
827 }
828
829 pub fn not_empty_string() -> impl Predicate<String> {
831 not_empty()
832 }
833
834 pub fn valid_name_length(min: usize, max: usize) -> impl Predicate<String> {
836 len_between(min, max)
837 }
838}
839
840#[derive(Debug, Clone)]
849pub struct ValidationRuleSet {
850 pub complexity_warning: u32,
852 pub complexity_critical: u32,
854 pub max_function_length: usize,
856 pub max_nesting_depth: u32,
858 pub max_file_length: usize,
860 pub min_name_length: usize,
862 pub max_name_length: usize,
864}
865
866impl Default for ValidationRuleSet {
867 fn default() -> Self {
868 Self {
869 complexity_warning: 21,
870 complexity_critical: 100,
871 max_function_length: 50,
872 max_nesting_depth: 4,
873 max_file_length: 1000,
874 min_name_length: 2,
875 max_name_length: 50,
876 }
877 }
878}
879
880impl ValidationRuleSet {
881 pub fn strict() -> Self {
883 Self {
884 complexity_warning: 10,
885 complexity_critical: 50,
886 max_function_length: 20,
887 max_nesting_depth: 2,
888 max_file_length: 500,
889 min_name_length: 3,
890 max_name_length: 30,
891 }
892 }
893
894 pub fn lenient() -> Self {
896 Self {
897 complexity_warning: 30,
898 complexity_critical: 150,
899 max_function_length: 100,
900 max_nesting_depth: 6,
901 max_file_length: 2000,
902 min_name_length: 1,
903 max_name_length: 100,
904 }
905 }
906
907 pub fn is_warning_complexity(&self, complexity: u32) -> bool {
909 complexity >= self.complexity_warning && complexity < self.complexity_critical
910 }
911
912 pub fn is_critical_complexity(&self, complexity: u32) -> bool {
914 complexity >= self.complexity_critical
915 }
916
917 pub fn is_acceptable_function_length(&self, length: usize) -> bool {
919 length <= self.max_function_length
920 }
921
922 pub fn is_acceptable_nesting(&self, depth: u32) -> bool {
924 depth <= self.max_nesting_depth
925 }
926
927 pub fn is_acceptable_file_length(&self, length: usize) -> bool {
929 length <= self.max_file_length
930 }
931
932 pub fn complexity_predicate(&self) -> impl Predicate<u32> + '_ {
934 use stillwater::predicate::lt;
935 lt(self.complexity_critical)
936 }
937
938 pub fn function_length_predicate(&self) -> impl Predicate<usize> + '_ {
940 use stillwater::predicate::le;
941 le(self.max_function_length)
942 }
943
944 pub fn nesting_predicate(&self) -> impl Predicate<u32> + '_ {
946 use stillwater::predicate::le;
947 le(self.max_nesting_depth)
948 }
949
950 pub fn file_length_predicate(&self) -> impl Predicate<usize> + '_ {
952 use stillwater::predicate::le;
953 le(self.max_file_length)
954 }
955}
956
957pub fn validate_function_complexity(
963 function_name: &str,
964 complexity: u32,
965 rules: &ValidationRuleSet,
966) -> AnalysisValidation<u32> {
967 use stillwater::predicate::lt;
968
969 complexity.ensure(
970 lt(rules.complexity_critical),
971 AnalysisError::validation(format!(
972 "Function '{}' has critical complexity: {} (threshold: {})",
973 function_name, complexity, rules.complexity_critical
974 )),
975 )
976}
977
978pub fn validate_function_length(
980 function_name: &str,
981 length: usize,
982 rules: &ValidationRuleSet,
983) -> AnalysisValidation<usize> {
984 use stillwater::predicate::le;
985
986 length.ensure(
987 le(rules.max_function_length),
988 AnalysisError::validation(format!(
989 "Function '{}' is too long: {} lines (max: {})",
990 function_name, length, rules.max_function_length
991 )),
992 )
993}
994
995pub fn validate_nesting_depth(
997 function_name: &str,
998 depth: u32,
999 rules: &ValidationRuleSet,
1000) -> AnalysisValidation<u32> {
1001 use stillwater::predicate::le;
1002
1003 depth.ensure(
1004 le(rules.max_nesting_depth),
1005 AnalysisError::validation(format!(
1006 "Function '{}' has excessive nesting: {} levels (max: {})",
1007 function_name, depth, rules.max_nesting_depth
1008 )),
1009 )
1010}
1011
1012pub fn validate_file_length(
1014 file_path: &std::path::Path,
1015 length: usize,
1016 rules: &ValidationRuleSet,
1017) -> AnalysisValidation<usize> {
1018 use stillwater::predicate::le;
1019
1020 length.ensure(
1021 le(rules.max_file_length),
1022 AnalysisError::validation(format!(
1023 "File '{}' is too long: {} lines (max: {})",
1024 file_path.display(),
1025 length,
1026 rules.max_file_length
1027 )),
1028 )
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033 use super::*;
1034 use crate::effects::validation_failure;
1035 use stillwater::predicate::*;
1036
1037 #[test]
1042 fn test_ensure_success() {
1043 let value: u32 = 50;
1044 let result = value.ensure(lt(100_u32), AnalysisError::validation("Too high"));
1045 assert!(result.is_success());
1046 match result {
1047 Validation::Success(v) => assert_eq!(v, 50),
1048 _ => panic!("Expected success"),
1049 }
1050 }
1051
1052 #[test]
1053 fn test_ensure_failure() {
1054 let value: u32 = 150;
1055 let result = value.ensure(lt(100_u32), AnalysisError::validation("Too high"));
1056 assert!(result.is_failure());
1057 }
1058
1059 #[test]
1060 fn test_ensure_with_error_fn() {
1061 let value: u32 = 150;
1062 let result = value.ensure_with(lt(100_u32), |v| {
1063 AnalysisError::validation(format!("Value {} exceeds limit", v))
1064 });
1065 assert!(result.is_failure());
1066 match result {
1067 Validation::Failure(errors) => {
1068 let msg = errors.head().to_string();
1069 assert!(msg.contains("150"));
1070 }
1071 _ => panic!("Expected failure"),
1072 }
1073 }
1074
1075 #[test]
1080 fn test_high_complexity_predicate() {
1081 let pred = predicates::high_complexity(21, 100);
1082 assert!(pred.check(&50)); assert!(pred.check(&21)); assert!(pred.check(&99)); assert!(!pred.check(&20)); assert!(!pred.check(&100)); }
1088
1089 #[test]
1090 fn test_critical_complexity_predicate() {
1091 let pred = predicates::critical_complexity(100);
1092 assert!(pred.check(&100));
1093 assert!(pred.check(&150));
1094 assert!(!pred.check(&99));
1095 }
1096
1097 #[test]
1098 fn test_within_bounds_predicate() {
1099 let pred = predicates::within_bounds(10, 50);
1100 assert!(pred.check(&30));
1101 assert!(pred.check(&10)); assert!(pred.check(&50)); assert!(!pred.check(&9));
1104 assert!(!pred.check(&51));
1105 }
1106
1107 #[test]
1108 fn test_acceptable_file_length_predicate() {
1109 let pred = predicates::acceptable_file_length(1000);
1110 assert!(pred.check(&500));
1111 assert!(pred.check(&1000));
1112 assert!(!pred.check(&1001));
1113 }
1114
1115 #[test]
1116 fn test_predicate_composition() {
1117 let and_pred = ge(10_u32).and(le(20_u32));
1119 assert!(and_pred.check(&15));
1120 assert!(!and_pred.check(&5));
1121 assert!(!and_pred.check(&25));
1122
1123 let or_pred = lt(10_u32).or(gt(90_u32));
1125 assert!(or_pred.check(&5));
1126 assert!(or_pred.check(&95));
1127 assert!(!or_pred.check(&50));
1128
1129 let not_pred = ge(50_u32).not();
1131 assert!(not_pred.check(&25));
1132 assert!(!not_pred.check(&75));
1133 }
1134
1135 #[test]
1140 fn test_validated_file_results_all_succeeded() {
1141 let metrics = create_test_file_metrics();
1142 let validations = vec![
1143 validation_success(metrics.clone()),
1144 validation_success(metrics.clone()),
1145 ];
1146
1147 let result = ValidatedFileResults::from_validations(validations);
1148
1149 assert!(result.is_all_success());
1150 assert!(!result.has_failures());
1151 assert_eq!(result.succeeded().len(), 2);
1152 assert!(result.failures().is_none());
1153 }
1154
1155 #[test]
1156 fn test_validated_file_results_partial_success() {
1157 let metrics = create_test_file_metrics();
1158 let validations = vec![
1159 validation_success(metrics.clone()),
1160 validation_failure(AnalysisError::parse("Parse error")),
1161 validation_success(metrics.clone()),
1162 ];
1163
1164 let result = ValidatedFileResults::from_validations(validations);
1165
1166 assert!(!result.is_all_success());
1167 assert!(result.has_failures());
1168 assert_eq!(result.succeeded().len(), 2);
1169 assert!(result.failures().is_some());
1170 assert_eq!(result.failures().unwrap().len(), 1);
1171 }
1172
1173 #[test]
1174 fn test_validated_file_results_all_failed() {
1175 let validations: Vec<AnalysisValidation<FileMetrics>> = vec![
1176 validation_failure(AnalysisError::parse("Error 1")),
1177 validation_failure(AnalysisError::parse("Error 2")),
1178 ];
1179
1180 let result = ValidatedFileResults::from_validations(validations);
1181
1182 assert!(!result.is_all_success());
1183 assert!(result.has_failures());
1184 assert!(result.succeeded().is_empty());
1185 assert!(result.failures().is_some());
1186 assert_eq!(result.failures().unwrap().len(), 2);
1187 }
1188
1189 #[test]
1190 fn test_validated_file_results_into_validation() {
1191 let metrics = create_test_file_metrics();
1192
1193 let all_success = ValidatedFileResults::AllSucceeded(vec![metrics.clone()]);
1195 assert!(all_success.into_validation().is_success());
1196
1197 let partial = ValidatedFileResults::PartialSuccess {
1199 succeeded: vec![metrics.clone()],
1200 failures: NonEmptyVec::new(AnalysisError::parse("Error"), Vec::new()),
1201 };
1202 assert!(partial.into_validation().is_failure());
1203 }
1204
1205 #[test]
1206 fn test_validated_file_results_into_lenient_result() {
1207 let metrics = create_test_file_metrics();
1208
1209 let partial = ValidatedFileResults::PartialSuccess {
1211 succeeded: vec![metrics.clone()],
1212 failures: NonEmptyVec::new(AnalysisError::parse("Error"), Vec::new()),
1213 };
1214 let result = partial.into_lenient_result();
1215 assert!(result.is_ok());
1216 assert_eq!(result.unwrap().len(), 1);
1217
1218 let all_failed = ValidatedFileResults::AllFailed(NonEmptyVec::new(
1220 AnalysisError::parse("Error"),
1221 vec![],
1222 ));
1223 assert!(all_failed.into_lenient_result().is_err());
1224 }
1225
1226 #[test]
1231 fn test_validation_rule_set_default() {
1232 let rules = ValidationRuleSet::default();
1233 assert_eq!(rules.complexity_warning, 21);
1234 assert_eq!(rules.complexity_critical, 100);
1235 assert_eq!(rules.max_function_length, 50);
1236 }
1237
1238 #[test]
1239 fn test_validation_rule_set_strict() {
1240 let rules = ValidationRuleSet::strict();
1241 assert!(rules.complexity_warning < ValidationRuleSet::default().complexity_warning);
1242 assert!(rules.max_function_length < ValidationRuleSet::default().max_function_length);
1243 }
1244
1245 #[test]
1246 fn test_validation_rule_set_lenient() {
1247 let rules = ValidationRuleSet::lenient();
1248 assert!(rules.complexity_warning > ValidationRuleSet::default().complexity_warning);
1249 assert!(rules.max_function_length > ValidationRuleSet::default().max_function_length);
1250 }
1251
1252 #[test]
1253 fn test_validation_rule_set_checks() {
1254 let rules = ValidationRuleSet::default();
1255
1256 assert!(!rules.is_warning_complexity(20));
1258 assert!(rules.is_warning_complexity(50));
1259 assert!(!rules.is_warning_complexity(100));
1260 assert!(!rules.is_critical_complexity(99));
1261 assert!(rules.is_critical_complexity(100));
1262
1263 assert!(rules.is_acceptable_function_length(50));
1265 assert!(!rules.is_acceptable_function_length(51));
1266 assert!(rules.is_acceptable_nesting(4));
1267 assert!(!rules.is_acceptable_nesting(5));
1268 }
1269
1270 #[test]
1271 fn test_validation_rule_set_predicates() {
1272 let rules = ValidationRuleSet::default();
1273
1274 let complexity_pred = rules.complexity_predicate();
1276 assert!(complexity_pred.check(&50));
1277 assert!(!complexity_pred.check(&100));
1278
1279 let length_pred = rules.function_length_predicate();
1281 assert!(length_pred.check(&50));
1282 assert!(!length_pred.check(&51));
1283 }
1284
1285 #[test]
1290 fn test_validate_function_complexity() {
1291 let rules = ValidationRuleSet::default();
1292
1293 let valid = validate_function_complexity("test_fn", 50, &rules);
1294 assert!(valid.is_success());
1295
1296 let invalid = validate_function_complexity("complex_fn", 150, &rules);
1297 assert!(invalid.is_failure());
1298 }
1299
1300 #[test]
1301 fn test_validate_function_length() {
1302 let rules = ValidationRuleSet::default();
1303
1304 let valid = validate_function_length("short_fn", 30, &rules);
1305 assert!(valid.is_success());
1306
1307 let invalid = validate_function_length("long_fn", 100, &rules);
1308 assert!(invalid.is_failure());
1309 }
1310
1311 #[test]
1312 fn test_validate_nesting_depth() {
1313 let rules = ValidationRuleSet::default();
1314
1315 let valid = validate_nesting_depth("shallow_fn", 2, &rules);
1316 assert!(valid.is_success());
1317
1318 let invalid = validate_nesting_depth("deep_fn", 10, &rules);
1319 assert!(invalid.is_failure());
1320 }
1321
1322 #[test]
1323 fn test_validate_file_length() {
1324 let rules = ValidationRuleSet::default();
1325 let path = std::path::Path::new("test.rs");
1326
1327 let valid = validate_file_length(path, 500, &rules);
1328 assert!(valid.is_success());
1329
1330 let invalid = validate_file_length(path, 2000, &rules);
1331 assert!(invalid.is_failure());
1332 }
1333
1334 #[test]
1339 fn test_field_path_root() {
1340 let path = FieldPath::root();
1341 assert!(path.is_root());
1342 assert!(path.is_empty());
1343 assert_eq!(path.len(), 0);
1344 assert_eq!(path.as_string(), "");
1345 }
1346
1347 #[test]
1348 fn test_field_path_single() {
1349 let path = FieldPath::new("config");
1350 assert!(!path.is_root());
1351 assert_eq!(path.len(), 1);
1352 assert_eq!(path.as_string(), "config");
1353 assert_eq!(path.last(), Some("config"));
1354 }
1355
1356 #[test]
1357 fn test_field_path_nested() {
1358 let path = FieldPath::root()
1359 .push("config")
1360 .push("thresholds")
1361 .push("cyclomatic");
1362 assert_eq!(path.len(), 3);
1363 assert_eq!(path.as_string(), "config.thresholds.cyclomatic");
1364 assert_eq!(path.last(), Some("cyclomatic"));
1365 assert_eq!(path.segments(), &["config", "thresholds", "cyclomatic"]);
1366 }
1367
1368 #[test]
1369 fn test_field_path_display() {
1370 let path = FieldPath::new("config").push("value");
1371 assert_eq!(format!("{}", path), "config.value");
1372 }
1373
1374 #[test]
1375 fn test_field_path_from_str() {
1376 let path: FieldPath = "config".into();
1377 assert_eq!(path.as_string(), "config");
1378 }
1379
1380 #[test]
1381 fn test_validation_error_at_field() {
1382 let path = FieldPath::new("threshold");
1383 let error = ValidationError::at_field(&path, "must be positive");
1384 assert_eq!(error.field.as_string(), "threshold");
1385 assert_eq!(error.message, "must be positive");
1386 assert!(error.expected.is_none());
1387 assert!(error.actual.is_none());
1388 }
1389
1390 #[test]
1391 fn test_validation_error_for_field() {
1392 let error = ValidationError::for_field("coverage", "out of range");
1393 assert_eq!(error.field.as_string(), "coverage");
1394 assert_eq!(error.message, "out of range");
1395 }
1396
1397 #[test]
1398 fn test_validation_error_with_context() {
1399 let error = ValidationError::for_field("threshold", "invalid value")
1400 .with_context("positive integer", "-5");
1401 assert_eq!(error.expected, Some("positive integer".to_string()));
1402 assert_eq!(error.actual, Some("-5".to_string()));
1403 }
1404
1405 #[test]
1406 fn test_validation_error_display() {
1407 let error = ValidationError::for_field("config.threshold", "must be positive")
1408 .with_context("positive", "negative");
1409 let display = format!("{}", error);
1410 assert!(display.contains("config.threshold"));
1411 assert!(display.contains("must be positive"));
1412 assert!(display.contains("expected: positive"));
1413 assert!(display.contains("got: negative"));
1414 }
1415
1416 #[test]
1417 fn test_validation_error_display_no_context() {
1418 let error = ValidationError::for_field("name", "required");
1419 assert_eq!(format!("{}", error), "name: required");
1420 }
1421
1422 #[test]
1423 fn test_validation_error_display_root_path() {
1424 let error = ValidationError::at_field(&FieldPath::root(), "general error");
1425 assert_eq!(format!("{}", error), "general error");
1426 }
1427
1428 #[test]
1429 fn test_validation_error_serialization() {
1430 let error = ValidationError::for_field("threshold", "invalid").with_context(">=0", "-1");
1431 let json = serde_json::to_string(&error).unwrap();
1432 assert!(json.contains("\"field\""));
1433 assert!(json.contains("\"message\""));
1434 assert!(json.contains("\"expected\""));
1435 assert!(json.contains("\"actual\""));
1436 }
1437
1438 #[test]
1439 fn test_file_error_new() {
1440 let error = FileError::new(PathBuf::from("src/main.rs"), "parse error");
1441 assert_eq!(error.path, PathBuf::from("src/main.rs"));
1442 assert_eq!(error.message, "parse error");
1443 assert!(error.line.is_none());
1444 assert!(error.column.is_none());
1445 assert!(error.error_code.is_none());
1446 }
1447
1448 #[test]
1449 fn test_file_error_at_location() {
1450 let error =
1451 FileError::new(PathBuf::from("test.rs"), "unexpected token").at_location(42, 15);
1452 assert_eq!(error.line, Some(42));
1453 assert_eq!(error.column, Some(15));
1454 }
1455
1456 #[test]
1457 fn test_file_error_at_line() {
1458 let error = FileError::new(PathBuf::from("test.rs"), "missing semicolon").at_line(10);
1459 assert_eq!(error.line, Some(10));
1460 assert!(error.column.is_none());
1461 }
1462
1463 #[test]
1464 fn test_file_error_with_code() {
1465 let error = FileError::new(PathBuf::from("test.rs"), "syntax error").with_code("E010");
1466 assert_eq!(error.error_code, Some("E010".to_string()));
1467 }
1468
1469 #[test]
1470 fn test_file_error_display() {
1471 let error = FileError::new(PathBuf::from("src/lib.rs"), "unexpected eof")
1472 .at_location(100, 25)
1473 .with_code("E010");
1474 let display = format!("{}", error);
1475 assert!(display.contains("src/lib.rs"));
1476 assert!(display.contains(":100:25"));
1477 assert!(display.contains("unexpected eof"));
1478 assert!(display.contains("[E010]"));
1479 }
1480
1481 #[test]
1482 fn test_file_error_display_no_location() {
1483 let error = FileError::new(PathBuf::from("test.rs"), "general error");
1484 let display = format!("{}", error);
1485 assert_eq!(display, "test.rs: general error");
1486 }
1487
1488 #[test]
1489 fn test_file_error_serialization() {
1490 let error = FileError::new(PathBuf::from("test.rs"), "error")
1491 .at_location(10, 5)
1492 .with_code("E001");
1493 let json = serde_json::to_string(&error).unwrap();
1494 assert!(json.contains("\"path\""));
1495 assert!(json.contains("\"line\""));
1496 assert!(json.contains("\"column\""));
1497 assert!(json.contains("\"message\""));
1498 assert!(json.contains("\"error_code\""));
1499 }
1500
1501 #[test]
1502 fn test_validated_file_set_empty() {
1503 let set: ValidatedFileSet<String> = ValidatedFileSet::empty();
1504 assert!(!set.has_valid());
1505 assert!(!set.has_errors());
1506 assert!(!set.is_partial_success());
1507 assert!(!set.is_all_success());
1508 assert!(!set.is_all_failed());
1509 }
1510
1511 #[test]
1512 fn test_validated_file_set_all_valid() {
1513 let set = ValidatedFileSet::all_valid(vec!["file1".to_string(), "file2".to_string()]);
1514 assert!(set.has_valid());
1515 assert!(!set.has_errors());
1516 assert!(set.is_all_success());
1517 assert!(!set.is_partial_success());
1518 assert!(!set.is_all_failed());
1519 assert_eq!(set.valid_count(), 2);
1520 assert_eq!(set.error_count(), 0);
1521 }
1522
1523 #[test]
1524 fn test_validated_file_set_all_errors() {
1525 let set: ValidatedFileSet<String> = ValidatedFileSet::all_errors(vec![
1526 FileError::new("a.rs", "error1"),
1527 FileError::new("b.rs", "error2"),
1528 ]);
1529 assert!(!set.has_valid());
1530 assert!(set.has_errors());
1531 assert!(set.is_all_failed());
1532 assert!(!set.is_partial_success());
1533 assert!(!set.is_all_success());
1534 assert_eq!(set.valid_count(), 0);
1535 assert_eq!(set.error_count(), 2);
1536 }
1537
1538 #[test]
1539 fn test_validated_file_set_partial_success() {
1540 let set = ValidatedFileSet {
1541 valid: vec!["good.rs".to_string()],
1542 errors: vec![FileError::new("bad.rs", "parse error")],
1543 };
1544 assert!(set.has_valid());
1545 assert!(set.has_errors());
1546 assert!(set.is_partial_success());
1547 assert!(!set.is_all_success());
1548 assert!(!set.is_all_failed());
1549 }
1550
1551 #[test]
1552 fn test_validated_file_set_into_strict_result() {
1553 let success_set = ValidatedFileSet::all_valid(vec!["ok".to_string()]);
1554 assert!(success_set.into_strict_result().is_ok());
1555
1556 let partial_set = ValidatedFileSet {
1557 valid: vec!["ok".to_string()],
1558 errors: vec![FileError::new("bad.rs", "error")],
1559 };
1560 assert!(partial_set.into_strict_result().is_err());
1561 }
1562
1563 #[test]
1564 fn test_validated_file_set_into_lenient_result() {
1565 let partial_set = ValidatedFileSet {
1566 valid: vec!["ok".to_string()],
1567 errors: vec![FileError::new("bad.rs", "error")],
1568 };
1569 assert!(partial_set.into_lenient_result().is_ok());
1570
1571 let all_failed: ValidatedFileSet<String> =
1572 ValidatedFileSet::all_errors(vec![FileError::new("bad.rs", "error")]);
1573 assert!(all_failed.into_lenient_result().is_err());
1574 }
1575
1576 #[test]
1577 fn test_validated_file_set_add_operations() {
1578 let mut set: ValidatedFileSet<String> = ValidatedFileSet::empty();
1579 set.add_valid("file1".to_string());
1580 set.add_error(FileError::new("bad.rs", "error"));
1581 assert!(set.is_partial_success());
1582 assert_eq!(set.valid_count(), 1);
1583 assert_eq!(set.error_count(), 1);
1584 }
1585
1586 #[test]
1587 fn test_validated_file_set_merge() {
1588 let mut set1: ValidatedFileSet<String> = ValidatedFileSet::all_valid(vec!["a".to_string()]);
1589 let set2 = ValidatedFileSet {
1590 valid: vec!["b".to_string()],
1591 errors: vec![FileError::new("c.rs", "error")],
1592 };
1593 set1.merge(set2);
1594 assert_eq!(set1.valid_count(), 2);
1595 assert_eq!(set1.error_count(), 1);
1596 }
1597
1598 #[test]
1599 fn test_validated_file_set_serialization() {
1600 let set = ValidatedFileSet {
1601 valid: vec!["file1".to_string()],
1602 errors: vec![FileError::new("bad.rs", "error")],
1603 };
1604 let json = serde_json::to_string(&set).unwrap();
1605 assert!(json.contains("\"valid_count\":1"));
1606 assert!(json.contains("\"error_count\":1"));
1607 assert!(json.contains("\"valid\""));
1608 assert!(json.contains("\"errors\""));
1609 }
1610
1611 #[test]
1612 fn test_field_context_ext_with_field_path() {
1613 let validation: Validation<u32, NonEmptyVec<String>> =
1614 Validation::Failure(NonEmptyVec::new("error message".to_string(), vec![]));
1615
1616 let path = FieldPath::new("config").push("threshold");
1617 let result = validation.with_field_path(&path);
1618
1619 match result {
1620 Validation::Failure(errors) => {
1621 let err = errors.head();
1622 assert_eq!(err.field.as_string(), "config.threshold");
1623 assert!(err.message.contains("error message"));
1624 }
1625 _ => panic!("Expected failure"),
1626 }
1627 }
1628
1629 #[test]
1630 fn test_field_context_ext_with_field_name() {
1631 let validation: Validation<u32, NonEmptyVec<String>> =
1632 Validation::Failure(NonEmptyVec::new("too large".to_string(), vec![]));
1633
1634 let result = validation.with_field_name("complexity");
1635
1636 match result {
1637 Validation::Failure(errors) => {
1638 let err = errors.head();
1639 assert_eq!(err.field.as_string(), "complexity");
1640 }
1641 _ => panic!("Expected failure"),
1642 }
1643 }
1644
1645 #[test]
1646 fn test_field_context_ext_success_passthrough() {
1647 let validation: Validation<u32, NonEmptyVec<String>> = Validation::Success(42);
1648 let result = validation.with_field_name("value");
1649
1650 match result {
1651 Validation::Success(v) => assert_eq!(v, 42),
1652 _ => panic!("Expected success"),
1653 }
1654 }
1655
1656 #[test]
1657 fn test_validate_field_with_stillwater() {
1658 let validation: Validation<u32, String> = Validation::Failure("test error".to_string());
1660 let result = validate_field("my_field", validation);
1661
1662 match result {
1663 Validation::Failure(field_error) => {
1664 assert_eq!(field_error.field, "my_field");
1665 assert_eq!(field_error.error, "test error");
1666 }
1667 _ => panic!("Expected failure"),
1668 }
1669 }
1670
1671 fn create_test_file_metrics() -> FileMetrics {
1676 use crate::core::{ComplexityMetrics, Language};
1677 FileMetrics {
1678 path: std::path::PathBuf::from("test.rs"),
1679 language: Language::Rust,
1680 complexity: ComplexityMetrics::default(),
1681 debt_items: Vec::new(),
1682 dependencies: Vec::new(),
1683 duplications: Vec::new(),
1684 total_lines: 100,
1685 module_scope: None,
1686 classes: None,
1687 }
1688 }
1689}