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