stern4rust/reporting/
json_printer.rs1use crate::reporting::offence::Offence;
6use crate::reporting::offence_threshold::OffenceThreshold;
7use serde_json::json;
8use serde_json::to_string_pretty;
9use std::collections::BTreeSet;
10
11pub struct JsonPrinter {
19 files_scanned: usize,
20 threshold: OffenceThreshold,
21 applied: Vec<String>,
22 skipped: Vec<String>,
23 unconfigured: Vec<String>,
24 exclusions: Vec<(String, usize)>,
25 config_file: Option<String>,
26 baseline: Option<String>,
27 suppressed: usize,
28 stale: usize,
29 fixed: usize,
30}
31
32impl JsonPrinter {
33 pub fn new(files_scanned: usize) -> Self {
34 Self {
35 files_scanned,
36 threshold: OffenceThreshold::default(),
37 applied: Vec::new(),
38 skipped: Vec::new(),
39 unconfigured: Vec::new(),
40 exclusions: Vec::new(),
41 config_file: None,
42 baseline: None,
43 suppressed: 0,
44 stale: 0,
45 fixed: 0,
46 }
47 }
48
49 pub fn with_fixed(self, fixed: usize) -> Self {
53 Self { fixed, ..self }
54 }
55
56 pub fn with_baseline(self, baseline: Option<String>, suppressed: usize, stale: usize) -> Self {
57 Self {
58 baseline,
59 suppressed,
60 stale,
61 ..self
62 }
63 }
64
65 pub fn with_config_file(self, config_file: Option<String>) -> Self {
66 Self {
67 config_file,
68 ..self
69 }
70 }
71
72 pub fn with_exclusions(self, exclusions: Vec<(String, usize)>) -> Self {
77 Self { exclusions, ..self }
78 }
79
80 pub fn with_rules(
84 self,
85 applied: Vec<String>,
86 skipped: Vec<String>,
87 unconfigured: Vec<String>,
88 ) -> Self {
89 Self {
90 applied,
91 skipped,
92 unconfigured,
93 ..self
94 }
95 }
96
97 pub fn with_threshold(self, threshold: OffenceThreshold) -> Self {
98 Self { threshold, ..self }
99 }
100
101 fn excluded_total(&self) -> usize {
108 self.exclusions.iter().map(|(_, count)| count).sum()
109 }
110
111 pub fn render(&self, offences: &[Offence]) -> String {
112 let broken: BTreeSet<&str> = offences.iter().map(|offence| offence.rule).collect();
113 let shown = self.threshold.kept(offences);
114 let document = json!({
115 "baseline": self.baseline,
116 "files_fixed": self.fixed,
117 "baselined": self.suppressed,
118 "baseline_stale_entries": self.stale,
119 "config_file": self.config_file,
120 "files_scanned": self.files_scanned,
121 "files_excluded": self.excluded_total(),
122 "exclusions": self.exclusions.iter().map(|(pattern, count)| json!({
123 "pattern": pattern,
124 "files_excluded": count,
125 })).collect::<Vec<_>>(),
126 "offences_found": offences.len(),
127 "offences_reported": shown.len(),
128 "offences_omitted": self.threshold.omitted(offences),
129 "offence_threshold": self.threshold.limit(),
130 "rules_broken": broken.len(),
131 "rules_applied": self.applied,
132 "rules_skipped": self.skipped,
133 "rules_unconfigured": self.unconfigured,
134 "offences": shown,
135 });
136 to_string_pretty(&document).unwrap_or_default()
137 }
138
139 pub fn print(&self, offences: &[Offence]) {
140 println!("{}", self.render(offences));
141 }
142}