Skip to main content

crap_core/domain/
threshold.rs

1/// Strict CRAP threshold — for high-quality or safety-critical code.
2/// Matches SonarSource S3776 cognitive complexity limit and eliminates false
3/// positives on well-tested idiomatic Rust (SeaORM-style large match arms).
4pub const STRICT_THRESHOLD: f64 = 15.0;
5
6/// Default CRAP threshold — balanced for typical Rust codebases.
7pub const DEFAULT_THRESHOLD: f64 = 25.0;
8
9/// Lenient CRAP threshold — for legacy or transitional code.
10pub const LENIENT_THRESHOLD: f64 = 40.0;
11
12/// Named threshold preset.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ThresholdPreset {
15    /// `STRICT_THRESHOLD` (15) — high-quality libraries, safety-critical code.
16    Strict,
17    /// `DEFAULT_THRESHOLD` (25) — typical Rust projects.
18    Default,
19    /// `LENIENT_THRESHOLD` (40) — legacy or transitional code.
20    Lenient,
21}
22
23impl ThresholdPreset {
24    /// Returns the f64 threshold value for this preset.
25    pub fn threshold(self) -> f64 {
26        match self {
27            Self::Strict => STRICT_THRESHOLD,
28            Self::Default => DEFAULT_THRESHOLD,
29            Self::Lenient => LENIENT_THRESHOLD,
30        }
31    }
32}
33
34/// Returns true if the value is a valid CRAP threshold (finite and positive).
35pub fn is_valid_threshold(value: f64) -> bool {
36    value.is_finite() && value > 0.0
37}
38
39/// A glob-based threshold override for a specific path pattern.
40#[derive(Debug, Clone, PartialEq)]
41pub struct ThresholdOverride {
42    /// Glob pattern matched against project-relative file paths (e.g. `domain/**`).
43    pub pattern: String,
44    /// CRAP threshold for functions in files matching this pattern.
45    pub threshold: f64,
46}
47
48/// Threshold configuration with optional per-path overrides.
49///
50/// When overrides are present, each function's file path is tested against
51/// the override patterns in declaration order. The last matching override
52/// wins. If no override matches, the global threshold applies.
53#[derive(Debug, Clone, PartialEq)]
54pub struct ThresholdConfig {
55    /// Global CRAP threshold (used when no override matches).
56    pub global: f64,
57    /// Per-path overrides, evaluated in order (last match wins).
58    pub overrides: Vec<ThresholdOverride>,
59}
60
61impl Default for ThresholdConfig {
62    fn default() -> Self {
63        Self {
64            global: DEFAULT_THRESHOLD,
65            overrides: Vec::new(),
66        }
67    }
68}
69
70impl ThresholdConfig {
71    /// Returns true if any per-path overrides are configured.
72    pub fn has_overrides(&self) -> bool {
73        !self.overrides.is_empty()
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn threshold_constants() {
83        assert_eq!(DEFAULT_THRESHOLD, 25.0);
84        assert_eq!(STRICT_THRESHOLD, 15.0);
85        assert_eq!(LENIENT_THRESHOLD, 40.0);
86    }
87
88    #[test]
89    fn preset_to_threshold() {
90        assert_eq!(ThresholdPreset::Strict.threshold(), STRICT_THRESHOLD);
91        assert_eq!(ThresholdPreset::Default.threshold(), DEFAULT_THRESHOLD);
92        assert_eq!(ThresholdPreset::Lenient.threshold(), LENIENT_THRESHOLD);
93    }
94
95    #[test]
96    fn default_config_uses_default_threshold() {
97        let config = ThresholdConfig::default();
98        assert_eq!(config.global, DEFAULT_THRESHOLD);
99        assert!(config.overrides.is_empty());
100    }
101
102    #[test]
103    fn has_overrides_false_when_empty() {
104        let config = ThresholdConfig::default();
105        assert!(!config.has_overrides());
106    }
107
108    #[test]
109    fn has_overrides_true_when_present() {
110        let config = ThresholdConfig {
111            global: DEFAULT_THRESHOLD,
112            overrides: vec![ThresholdOverride {
113                pattern: "domain/**".to_string(),
114                threshold: 5.0,
115            }],
116        };
117        assert!(config.has_overrides());
118    }
119
120    #[test]
121    fn is_valid_threshold_accepts_positive_finite() {
122        assert!(is_valid_threshold(1.0));
123        assert!(is_valid_threshold(0.001));
124        assert!(is_valid_threshold(DEFAULT_THRESHOLD));
125        assert!(is_valid_threshold(100.0));
126    }
127
128    #[test]
129    fn is_valid_threshold_rejects_invalid() {
130        assert!(!is_valid_threshold(0.0));
131        assert!(!is_valid_threshold(-1.0));
132        assert!(!is_valid_threshold(f64::NAN));
133        assert!(!is_valid_threshold(f64::INFINITY));
134        assert!(!is_valid_threshold(f64::NEG_INFINITY));
135    }
136
137    #[test]
138    fn threshold_override_equality() {
139        let a = ThresholdOverride {
140            pattern: "src/**".to_string(),
141            threshold: 10.0,
142        };
143        let b = ThresholdOverride {
144            pattern: "src/**".to_string(),
145            threshold: 10.0,
146        };
147        assert_eq!(a, b);
148    }
149}