Skip to main content

fallow_config/config/
health.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4const fn default_max_cyclomatic() -> u16 {
5    20
6}
7
8const fn default_max_cognitive() -> u16 {
9    15
10}
11
12/// Configuration for complexity health metrics (`fallow health`).
13#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
14#[serde(rename_all = "camelCase")]
15pub struct HealthConfig {
16    /// Maximum allowed cyclomatic complexity per function (default: 20).
17    /// Functions exceeding this threshold are reported.
18    #[serde(default = "default_max_cyclomatic")]
19    pub max_cyclomatic: u16,
20
21    /// Maximum allowed cognitive complexity per function (default: 15).
22    /// Functions exceeding this threshold are reported.
23    #[serde(default = "default_max_cognitive")]
24    pub max_cognitive: u16,
25
26    /// Glob patterns to exclude from complexity analysis.
27    #[serde(default)]
28    pub ignore: Vec<String>,
29}
30
31impl Default for HealthConfig {
32    fn default() -> Self {
33        Self {
34            max_cyclomatic: default_max_cyclomatic(),
35            max_cognitive: default_max_cognitive(),
36            ignore: vec![],
37        }
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn health_config_defaults() {
47        let config = HealthConfig::default();
48        assert_eq!(config.max_cyclomatic, 20);
49        assert_eq!(config.max_cognitive, 15);
50        assert!(config.ignore.is_empty());
51    }
52
53    #[test]
54    fn health_config_json_all_fields() {
55        let json = r#"{
56            "maxCyclomatic": 30,
57            "maxCognitive": 25,
58            "ignore": ["**/generated/**", "vendor/**"]
59        }"#;
60        let config: HealthConfig = serde_json::from_str(json).unwrap();
61        assert_eq!(config.max_cyclomatic, 30);
62        assert_eq!(config.max_cognitive, 25);
63        assert_eq!(config.ignore, vec!["**/generated/**", "vendor/**"]);
64    }
65
66    #[test]
67    fn health_config_json_partial_uses_defaults() {
68        let json = r#"{"maxCyclomatic": 10}"#;
69        let config: HealthConfig = serde_json::from_str(json).unwrap();
70        assert_eq!(config.max_cyclomatic, 10);
71        assert_eq!(config.max_cognitive, 15); // default
72        assert!(config.ignore.is_empty()); // default
73    }
74
75    #[test]
76    fn health_config_json_empty_object_uses_all_defaults() {
77        let config: HealthConfig = serde_json::from_str("{}").unwrap();
78        assert_eq!(config.max_cyclomatic, 20);
79        assert_eq!(config.max_cognitive, 15);
80        assert!(config.ignore.is_empty());
81    }
82
83    #[test]
84    fn health_config_json_only_ignore() {
85        let json = r#"{"ignore": ["test/**"]}"#;
86        let config: HealthConfig = serde_json::from_str(json).unwrap();
87        assert_eq!(config.max_cyclomatic, 20); // default
88        assert_eq!(config.max_cognitive, 15); // default
89        assert_eq!(config.ignore, vec!["test/**"]);
90    }
91
92    // ── TOML deserialization ────────────────────────────────────────
93
94    #[test]
95    fn health_config_toml_all_fields() {
96        let toml_str = r#"
97maxCyclomatic = 25
98maxCognitive = 20
99ignore = ["generated/**", "vendor/**"]
100"#;
101        let config: HealthConfig = toml::from_str(toml_str).unwrap();
102        assert_eq!(config.max_cyclomatic, 25);
103        assert_eq!(config.max_cognitive, 20);
104        assert_eq!(config.ignore, vec!["generated/**", "vendor/**"]);
105    }
106
107    #[test]
108    fn health_config_toml_defaults() {
109        let config: HealthConfig = toml::from_str("").unwrap();
110        assert_eq!(config.max_cyclomatic, 20);
111        assert_eq!(config.max_cognitive, 15);
112        assert!(config.ignore.is_empty());
113    }
114
115    // ── Serialize roundtrip ─────────────────────────────────────────
116
117    #[test]
118    fn health_config_json_roundtrip() {
119        let config = HealthConfig {
120            max_cyclomatic: 50,
121            max_cognitive: 40,
122            ignore: vec!["test/**".to_string()],
123        };
124        let json = serde_json::to_string(&config).unwrap();
125        let restored: HealthConfig = serde_json::from_str(&json).unwrap();
126        assert_eq!(restored.max_cyclomatic, 50);
127        assert_eq!(restored.max_cognitive, 40);
128        assert_eq!(restored.ignore, vec!["test/**"]);
129    }
130
131    // ── Zero thresholds ─────────────────────────────────────────────
132
133    #[test]
134    fn health_config_zero_thresholds() {
135        let json = r#"{"maxCyclomatic": 0, "maxCognitive": 0}"#;
136        let config: HealthConfig = serde_json::from_str(json).unwrap();
137        assert_eq!(config.max_cyclomatic, 0);
138        assert_eq!(config.max_cognitive, 0);
139    }
140
141    // ── Large thresholds ────────────────────────────────────────────
142
143    #[test]
144    fn health_config_large_thresholds() {
145        let json = r#"{"maxCyclomatic": 65535, "maxCognitive": 65535}"#;
146        let config: HealthConfig = serde_json::from_str(json).unwrap();
147        assert_eq!(config.max_cyclomatic, u16::MAX);
148        assert_eq!(config.max_cognitive, u16::MAX);
149    }
150}