fallow_cli/health_types/
mod.rs1mod coverage;
8mod grouped;
9mod runtime_coverage;
10mod scores;
11mod targets;
12mod trends;
13mod vital_signs;
14
15pub use coverage::*;
16pub use grouped::*;
17pub use runtime_coverage::*;
18pub use scores::*;
19pub use targets::*;
20pub use trends::*;
21pub use vital_signs::*;
22
23#[derive(Debug, Clone, serde::Serialize)]
27pub struct HealthTimings {
28 pub config_ms: f64,
29 pub discover_ms: f64,
30 pub parse_ms: f64,
31 pub complexity_ms: f64,
32 pub file_scores_ms: f64,
33 pub git_churn_ms: f64,
34 pub git_churn_cache_hit: bool,
35 pub hotspots_ms: f64,
36 pub duplication_ms: f64,
37 pub targets_ms: f64,
38 pub total_ms: f64,
39}
40
41#[derive(Debug, serde::Serialize)]
43pub struct HealthReport {
44 pub findings: Vec<HealthFinding>,
46 pub summary: HealthSummary,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub vital_signs: Option<VitalSigns>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub health_score: Option<HealthScore>,
54 #[serde(skip_serializing_if = "Vec::is_empty")]
56 pub file_scores: Vec<FileHealthScore>,
57 #[serde(skip_serializing_if = "Option::is_none")]
63 pub coverage_gaps: Option<CoverageGaps>,
64 #[serde(skip_serializing_if = "Vec::is_empty")]
66 pub hotspots: Vec<HotspotEntry>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub hotspot_summary: Option<HotspotSummary>,
70 #[serde(skip_serializing_if = "Option::is_none")]
73 pub runtime_coverage: Option<RuntimeCoverageReport>,
74 #[serde(skip_serializing_if = "Vec::is_empty")]
76 pub large_functions: Vec<LargeFunctionEntry>,
77 #[serde(skip_serializing_if = "Vec::is_empty")]
79 pub targets: Vec<RefactoringTarget>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub target_thresholds: Option<TargetThresholds>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub health_trend: Option<HealthTrend>,
86}
87
88#[cfg(test)]
89#[expect(
90 clippy::derivable_impls,
91 reason = "test-only Default with custom HealthSummary thresholds (20/15)"
92)]
93impl Default for HealthReport {
94 fn default() -> Self {
95 Self {
96 findings: vec![],
97 summary: HealthSummary::default(),
98 vital_signs: None,
99 health_score: None,
100 file_scores: vec![],
101 coverage_gaps: None,
102 hotspots: vec![],
103 hotspot_summary: None,
104 runtime_coverage: None,
105 large_functions: vec![],
106 targets: vec![],
107 target_thresholds: None,
108 health_trend: None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn health_report_skips_empty_collections() {
119 let report = HealthReport::default();
120 let json = serde_json::to_string(&report).unwrap();
121 assert!(!json.contains("file_scores"));
123 assert!(!json.contains("hotspots"));
124 assert!(!json.contains("hotspot_summary"));
125 assert!(!json.contains("runtime_coverage"));
126 assert!(!json.contains("large_functions"));
127 assert!(!json.contains("targets"));
128 assert!(!json.contains("vital_signs"));
129 assert!(!json.contains("health_score"));
130 }
131
132 #[test]
133 fn health_score_none_skipped_in_report() {
134 let report = HealthReport::default();
135 let json = serde_json::to_string(&report).unwrap();
136 assert!(!json.contains("health_score"));
137 }
138}