cc_audit/aggregator/
summary.rs1use crate::rules::{Finding, Severity, Summary};
4use rustc_hash::FxHashMap;
5
6#[derive(Debug, Default)]
8pub struct SummaryBuilder {
9 findings: Vec<Finding>,
10 files_scanned: usize,
11 scan_duration_ms: u64,
12}
13
14impl SummaryBuilder {
15 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn with_findings(mut self, findings: Vec<Finding>) -> Self {
22 self.findings = findings;
23 self
24 }
25
26 pub fn with_files_scanned(mut self, count: usize) -> Self {
28 self.files_scanned = count;
29 self
30 }
31
32 pub fn with_duration_ms(mut self, duration: u64) -> Self {
34 self.scan_duration_ms = duration;
35 self
36 }
37
38 pub fn build(self) -> Summary {
40 let mut by_severity: FxHashMap<Severity, usize> = FxHashMap::default();
41
42 for finding in &self.findings {
43 *by_severity.entry(finding.severity).or_default() += 1;
44 }
45
46 let critical = by_severity.get(&Severity::Critical).copied().unwrap_or(0);
47 let high = by_severity.get(&Severity::High).copied().unwrap_or(0);
48 let medium = by_severity.get(&Severity::Medium).copied().unwrap_or(0);
49 let low = by_severity.get(&Severity::Low).copied().unwrap_or(0);
50
51 Summary {
52 critical,
53 high,
54 medium,
55 low,
56 passed: critical == 0 && high == 0,
57 errors: 0,
58 warnings: 0,
59 }
60 }
61
62 pub fn files_scanned(&self) -> usize {
64 self.files_scanned
65 }
66
67 pub fn total_findings(&self) -> usize {
69 self.findings.len()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::rules::{Category, Confidence, Location};
77
78 fn make_finding(severity: Severity) -> Finding {
79 Finding {
80 id: "TEST-001".to_string(),
81 severity,
82 category: Category::PromptInjection,
83 confidence: Confidence::Firm,
84 name: "Test".to_string(),
85 location: Location {
86 file: "test.md".to_string(),
87 line: 1,
88 column: None,
89 },
90 code: "test".to_string(),
91 message: "test".to_string(),
92 recommendation: "fix".to_string(),
93 fix_hint: None,
94 cwe_ids: Vec::new(),
95 rule_severity: None,
96 client: None,
97 context: None,
98 }
99 }
100
101 #[test]
102 fn test_summary_builder() {
103 let findings = vec![
104 make_finding(Severity::Critical),
105 make_finding(Severity::High),
106 make_finding(Severity::High),
107 make_finding(Severity::Medium),
108 ];
109
110 let builder = SummaryBuilder::new()
111 .with_findings(findings)
112 .with_files_scanned(10);
113
114 assert_eq!(builder.total_findings(), 4);
115 assert_eq!(builder.files_scanned(), 10);
116
117 let summary = builder.build();
118 assert_eq!(summary.critical, 1);
119 assert_eq!(summary.high, 2);
120 assert_eq!(summary.medium, 1);
121 assert_eq!(summary.low, 0);
122 assert!(!summary.passed); }
124
125 #[test]
126 fn test_empty_summary() {
127 let builder = SummaryBuilder::new().with_files_scanned(5);
128
129 assert_eq!(builder.total_findings(), 0);
130 assert_eq!(builder.files_scanned(), 5);
131
132 let summary = builder.build();
133 assert_eq!(summary.critical, 0);
134 assert!(summary.passed); }
136}