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                coverage_model: None,
72                istanbul_matched: None,
73                istanbul_total: None,
74            },
75            vital_signs: None,
76            health_score: None,
77            file_scores: vec![],
78            coverage_gaps: None,
79            hotspots: vec![],
80            hotspot_summary: None,
81            targets: vec![],
82            target_thresholds: None,
83            health_trend: None,
84        };
85        let json = serde_json::to_string(&report).unwrap();
86        // Empty vecs should be omitted due to skip_serializing_if
87        assert!(!json.contains("file_scores"));
88        assert!(!json.contains("hotspots"));
89        assert!(!json.contains("hotspot_summary"));
90        assert!(!json.contains("targets"));
91        assert!(!json.contains("vital_signs"));
92        assert!(!json.contains("health_score"));
93    }
94
95    #[test]
96    fn health_score_none_skipped_in_report() {
97        let report = HealthReport {
98            findings: vec![],
99            summary: HealthSummary {
100                files_analyzed: 0,
101                functions_analyzed: 0,
102                functions_above_threshold: 0,
103                max_cyclomatic_threshold: 20,
104                max_cognitive_threshold: 15,
105                files_scored: None,
106                average_maintainability: None,
107                coverage_model: None,
108                istanbul_matched: None,
109                istanbul_total: None,
110            },
111            vital_signs: None,
112            health_score: None,
113            file_scores: vec![],
114            coverage_gaps: None,
115            hotspots: vec![],
116            hotspot_summary: None,
117            targets: vec![],
118            target_thresholds: None,
119            health_trend: None,
120        };
121        let json = serde_json::to_string(&report).unwrap();
122        assert!(!json.contains("health_score"));
123    }
124}