1use roaring::RoaringBitmap;
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum CoverageFormat {
8 Lcov,
9 Cobertura,
10 JaCoCo,
11 GoCov,
12 LlvmCov,
13}
14
15impl std::fmt::Display for CoverageFormat {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 match self {
18 CoverageFormat::Lcov => write!(f, "lcov"),
19 CoverageFormat::Cobertura => write!(f, "cobertura"),
20 CoverageFormat::JaCoCo => write!(f, "jacoco"),
21 CoverageFormat::GoCov => write!(f, "gocov"),
22 CoverageFormat::LlvmCov => write!(f, "llvm-cov"),
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct FileCoverage {
30 pub lines_covered: RoaringBitmap,
32 pub lines_instrumented: RoaringBitmap,
34 pub branches: BTreeMap<(u32, u32), u64>,
36 pub functions: BTreeMap<String, u64>,
38}
39
40impl FileCoverage {
41 pub fn new() -> Self {
42 Self {
43 lines_covered: RoaringBitmap::new(),
44 lines_instrumented: RoaringBitmap::new(),
45 branches: BTreeMap::new(),
46 functions: BTreeMap::new(),
47 }
48 }
49
50 pub fn line_coverage_pct(&self) -> Option<f64> {
52 let instrumented = self.lines_instrumented.len() as f64;
53 if instrumented == 0.0 {
54 return None;
55 }
56 Some((self.lines_covered.len() as f64 / instrumented) * 100.0)
57 }
58
59 pub fn merge(&mut self, other: &FileCoverage) {
61 self.lines_covered |= &other.lines_covered;
62 self.lines_instrumented |= &other.lines_instrumented;
63 for (&key, &count) in &other.branches {
64 *self.branches.entry(key).or_insert(0) += count;
65 }
66 for (name, &count) in &other.functions {
67 *self.functions.entry(name.clone()).or_insert(0) += count;
68 }
69 }
70}
71
72impl Default for FileCoverage {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct CoverageData {
81 pub files: BTreeMap<String, FileCoverage>,
83 pub format: Option<CoverageFormat>,
85 pub timestamp: u64,
87}
88
89impl CoverageData {
90 pub fn new() -> Self {
91 Self {
92 files: BTreeMap::new(),
93 format: None,
94 timestamp: std::time::SystemTime::now()
95 .duration_since(std::time::UNIX_EPOCH)
96 .unwrap_or_default()
97 .as_secs(),
98 }
99 }
100
101 pub fn total_coverage_pct(&self) -> Option<f64> {
103 let mut total_covered = 0u64;
104 let mut total_instrumented = 0u64;
105 for fc in self.files.values() {
106 total_covered += fc.lines_covered.len();
107 total_instrumented += fc.lines_instrumented.len();
108 }
109 if total_instrumented == 0 {
110 return None;
111 }
112 Some((total_covered as f64 / total_instrumented as f64) * 100.0)
113 }
114
115 pub fn merge(&mut self, other: &CoverageData) {
117 for (path, fc) in &other.files {
118 self.files
119 .entry(path.clone())
120 .or_insert_with(FileCoverage::new)
121 .merge(fc);
122 }
123 }
124}
125
126impl Default for CoverageData {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
134pub enum DiffStatus {
135 Added,
136 Modified,
137 Deleted,
138 Renamed,
139}
140
141#[derive(Debug, Clone)]
143pub struct FileDiff {
144 pub path: String,
145 pub old_path: Option<String>,
146 pub status: DiffStatus,
147 pub changed_lines: RoaringBitmap,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct IssueGateCounts {
154 pub changed_errors: u32,
155 pub changed_warnings: u32,
156 pub changed_notes: u32,
157 pub total_issues: usize,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct QualityGateResult {
163 pub passed: bool,
164 pub total_coverage_pct: Option<f64>,
165 pub changed_coverage_pct: Option<f64>,
166 pub new_file_coverage_pct: Option<f64>,
167 pub violations: Vec<String>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub issue_counts: Option<IssueGateCounts>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct RepoSnapshot {
175 pub merkle_root: String,
177 pub file_hashes: BTreeMap<String, String>,
179}