Skip to main content

fallow_cli/health_types/
mod.rs

1//! Health / complexity analysis report types.
2//!
3//! Separated from the `health` command module so that report formatters
4//! (which are compiled as part of both the lib and bin targets) can
5//! reference these types without pulling in binary-only dependencies.
6
7mod scores;
8mod targets;
9mod trends;
10mod vital_signs;
11
12pub use scores::*;
13pub use targets::*;
14pub use trends::*;
15pub use vital_signs::*;
16
17/// Result of complexity analysis for reporting.
18#[derive(Debug, serde::Serialize)]
19pub struct HealthReport {
20    /// Functions exceeding thresholds.
21    pub findings: Vec<HealthFinding>,
22    /// Summary statistics.
23    pub summary: HealthSummary,
24    /// Project-wide vital signs (always computed from available data).
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub vital_signs: Option<VitalSigns>,
27    /// Project-wide health score (only populated with `--score`).
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub health_score: Option<HealthScore>,
30    /// Per-file health scores (only populated with `--file-scores` or `--hotspots`).
31    #[serde(skip_serializing_if = "Vec::is_empty")]
32    pub file_scores: Vec<FileHealthScore>,
33    /// Hotspot entries (only populated with `--hotspots`).
34    #[serde(skip_serializing_if = "Vec::is_empty")]
35    pub hotspots: Vec<HotspotEntry>,
36    /// Hotspot analysis summary (only set with `--hotspots`).
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub hotspot_summary: Option<HotspotSummary>,
39    /// Ranked refactoring recommendations (only populated with `--targets`).
40    #[serde(skip_serializing_if = "Vec::is_empty")]
41    pub targets: Vec<RefactoringTarget>,
42    /// Adaptive thresholds used for target scoring (only set with `--targets`).
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub target_thresholds: Option<TargetThresholds>,
45    /// Health trend comparison against a previous snapshot (only set with `--trend`).
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub health_trend: Option<HealthTrend>,
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn health_report_skips_empty_collections() {
56        let report = HealthReport {
57            findings: vec![],
58            summary: HealthSummary {
59                files_analyzed: 0,
60                functions_analyzed: 0,
61                functions_above_threshold: 0,
62                max_cyclomatic_threshold: 20,
63                max_cognitive_threshold: 15,
64                files_scored: None,
65                average_maintainability: None,
66            },
67            vital_signs: None,
68            health_score: None,
69            file_scores: vec![],
70            hotspots: vec![],
71            hotspot_summary: None,
72            targets: vec![],
73            target_thresholds: None,
74            health_trend: None,
75        };
76        let json = serde_json::to_string(&report).unwrap();
77        // Empty vecs should be omitted due to skip_serializing_if
78        assert!(!json.contains("file_scores"));
79        assert!(!json.contains("hotspots"));
80        assert!(!json.contains("hotspot_summary"));
81        assert!(!json.contains("targets"));
82        assert!(!json.contains("vital_signs"));
83        assert!(!json.contains("health_score"));
84    }
85
86    #[test]
87    fn health_score_none_skipped_in_report() {
88        let report = HealthReport {
89            findings: vec![],
90            summary: HealthSummary {
91                files_analyzed: 0,
92                functions_analyzed: 0,
93                functions_above_threshold: 0,
94                max_cyclomatic_threshold: 20,
95                max_cognitive_threshold: 15,
96                files_scored: None,
97                average_maintainability: None,
98            },
99            vital_signs: None,
100            health_score: None,
101            file_scores: vec![],
102            hotspots: vec![],
103            hotspot_summary: None,
104            targets: vec![],
105            target_thresholds: None,
106            health_trend: None,
107        };
108        let json = serde_json::to_string(&report).unwrap();
109        assert!(!json.contains("health_score"));
110    }
111}