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