fallow_cli/health_types/
mod.rs1mod 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#[derive(Debug, Clone, serde::Serialize)]
23pub struct HealthTimings {
24 pub config_ms: f64,
25 pub discover_ms: f64,
26 pub parse_ms: f64,
27 pub complexity_ms: f64,
28 pub file_scores_ms: f64,
29 pub git_churn_ms: f64,
30 pub git_churn_cache_hit: bool,
31 pub hotspots_ms: f64,
32 pub duplication_ms: f64,
33 pub targets_ms: f64,
34 pub total_ms: f64,
35}
36
37#[derive(Debug, serde::Serialize)]
39pub struct HealthReport {
40 pub findings: Vec<HealthFinding>,
42 pub summary: HealthSummary,
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub vital_signs: Option<VitalSigns>,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub health_score: Option<HealthScore>,
50 #[serde(skip_serializing_if = "Vec::is_empty")]
52 pub file_scores: Vec<FileHealthScore>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub coverage_gaps: Option<CoverageGaps>,
56 #[serde(skip_serializing_if = "Vec::is_empty")]
58 pub hotspots: Vec<HotspotEntry>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub hotspot_summary: Option<HotspotSummary>,
62 #[serde(skip_serializing_if = "Vec::is_empty")]
64 pub large_functions: Vec<LargeFunctionEntry>,
65 #[serde(skip_serializing_if = "Vec::is_empty")]
67 pub targets: Vec<RefactoringTarget>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub target_thresholds: Option<TargetThresholds>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub health_trend: Option<HealthTrend>,
74}
75
76#[cfg(test)]
77#[expect(
78 clippy::derivable_impls,
79 reason = "test-only Default with custom HealthSummary thresholds (20/15)"
80)]
81impl Default for HealthReport {
82 fn default() -> Self {
83 Self {
84 findings: vec![],
85 summary: HealthSummary::default(),
86 vital_signs: None,
87 health_score: None,
88 file_scores: vec![],
89 coverage_gaps: None,
90 hotspots: vec![],
91 hotspot_summary: None,
92 large_functions: vec![],
93 targets: vec![],
94 target_thresholds: None,
95 health_trend: None,
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn health_report_skips_empty_collections() {
106 let report = HealthReport::default();
107 let json = serde_json::to_string(&report).unwrap();
108 assert!(!json.contains("file_scores"));
110 assert!(!json.contains("hotspots"));
111 assert!(!json.contains("hotspot_summary"));
112 assert!(!json.contains("large_functions"));
113 assert!(!json.contains("targets"));
114 assert!(!json.contains("vital_signs"));
115 assert!(!json.contains("health_score"));
116 }
117
118 #[test]
119 fn health_score_none_skipped_in_report() {
120 let report = HealthReport::default();
121 let json = serde_json::to_string(&report).unwrap();
122 assert!(!json.contains("health_score"));
123 }
124}