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    /// Functions exceeding 60 LOC (only populated when unit size very-high-risk >= 3%).
45    #[serde(skip_serializing_if = "Vec::is_empty")]
46    pub large_functions: Vec<LargeFunctionEntry>,
47    /// Ranked refactoring recommendations (only populated with `--targets`).
48    #[serde(skip_serializing_if = "Vec::is_empty")]
49    pub targets: Vec<RefactoringTarget>,
50    /// Adaptive thresholds used for target scoring (only set with `--targets`).
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub target_thresholds: Option<TargetThresholds>,
53    /// Health trend comparison against a previous snapshot (only set with `--trend`).
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub health_trend: Option<HealthTrend>,
56}
57
58#[cfg(test)]
59#[expect(
60    clippy::derivable_impls,
61    reason = "test-only Default with custom HealthSummary thresholds (20/15)"
62)]
63impl Default for HealthReport {
64    fn default() -> Self {
65        Self {
66            findings: vec![],
67            summary: HealthSummary::default(),
68            vital_signs: None,
69            health_score: None,
70            file_scores: vec![],
71            coverage_gaps: None,
72            hotspots: vec![],
73            hotspot_summary: None,
74            large_functions: vec![],
75            targets: vec![],
76            target_thresholds: None,
77            health_trend: None,
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn health_report_skips_empty_collections() {
88        let report = HealthReport::default();
89        let json = serde_json::to_string(&report).unwrap();
90        // Empty vecs should be omitted due to skip_serializing_if
91        assert!(!json.contains("file_scores"));
92        assert!(!json.contains("hotspots"));
93        assert!(!json.contains("hotspot_summary"));
94        assert!(!json.contains("large_functions"));
95        assert!(!json.contains("targets"));
96        assert!(!json.contains("vital_signs"));
97        assert!(!json.contains("health_score"));
98    }
99
100    #[test]
101    fn health_score_none_skipped_in_report() {
102        let report = HealthReport::default();
103        let json = serde_json::to_string(&report).unwrap();
104        assert!(!json.contains("health_score"));
105    }
106}