stern4rust/reporting/
json_printer.rs1use crate::reporting::offence::Offence;
6use crate::reporting::offence_threshold::OffenceThreshold;
7use crate::reporting::package_roster::PackageRoster;
8use serde_json::Value;
9use serde_json::json;
10use serde_json::to_string_pretty;
11use std::collections::BTreeSet;
12
13pub struct JsonPrinter {
21 files_scanned: usize,
22 threshold: OffenceThreshold,
23 applied: Vec<String>,
24 rosters: Vec<PackageRoster>,
25 skipped: Vec<String>,
26 unconfigured: Vec<String>,
27 exclusions: Vec<(String, usize)>,
28 config_file: Option<String>,
29 baseline: Option<String>,
30 suppressed: usize,
31 stale: usize,
32 fixed: usize,
33}
34
35impl JsonPrinter {
36 pub fn new(files_scanned: usize) -> Self {
37 Self {
38 files_scanned,
39 threshold: OffenceThreshold::default(),
40 applied: Vec::new(),
41 rosters: Vec::new(),
42 skipped: Vec::new(),
43 unconfigured: Vec::new(),
44 exclusions: Vec::new(),
45 config_file: None,
46 baseline: None,
47 suppressed: 0,
48 stale: 0,
49 fixed: 0,
50 }
51 }
52
53 pub fn with_fixed(self, fixed: usize) -> Self {
57 Self { fixed, ..self }
58 }
59
60 pub fn with_baseline(self, baseline: Option<String>, suppressed: usize, stale: usize) -> Self {
61 Self {
62 baseline,
63 suppressed,
64 stale,
65 ..self
66 }
67 }
68
69 pub fn with_config_file(self, config_file: Option<String>) -> Self {
70 Self {
71 config_file,
72 ..self
73 }
74 }
75
76 pub fn with_exclusions(self, exclusions: Vec<(String, usize)>) -> Self {
81 Self { exclusions, ..self }
82 }
83
84 pub fn with_package_rosters(self, rosters: Vec<PackageRoster>) -> Self {
91 Self { rosters, ..self }
92 }
93
94 pub fn with_rules(
95 self,
96 applied: Vec<String>,
97 skipped: Vec<String>,
98 unconfigured: Vec<String>,
99 ) -> Self {
100 Self {
101 applied,
102 skipped,
103 unconfigured,
104 ..self
105 }
106 }
107
108 pub fn with_threshold(self, threshold: OffenceThreshold) -> Self {
109 Self { threshold, ..self }
110 }
111
112 fn excluded_total(&self) -> usize {
119 self.exclusions.iter().map(|(_, count)| count).sum()
120 }
121
122 pub fn render(&self, offences: &[Offence]) -> String {
123 let broken: BTreeSet<&str> = offences.iter().map(|offence| offence.rule).collect();
124 let shown = self.threshold.kept(offences);
125 let document = json!({
126 "baseline": self.baseline,
127 "files_fixed": self.fixed,
128 "baselined": self.suppressed,
129 "baseline_stale_entries": self.stale,
130 "config_file": self.config_file,
131 "files_scanned": self.files_scanned,
132 "files_excluded": self.excluded_total(),
133 "exclusions": self.exclusions.iter().map(|(pattern, count)| json!({
134 "pattern": pattern,
135 "files_excluded": count,
136 })).collect::<Vec<_>>(),
137 "offences_found": offences.len(),
138 "offences_reported": shown.len(),
139 "offences_omitted": self.threshold.omitted(offences),
140 "offence_threshold": self.threshold.limit(),
141 "rules_broken": broken.len(),
142 "rules_applied": self.applied,
143 "rules_skipped": self.skipped,
144 "rules_unconfigured": self.unconfigured,
145 "packages": self.package_documents(),
146 "offences": shown,
147 });
148 to_string_pretty(&document).unwrap_or_default()
149 }
150
151 fn package_documents(&self) -> Vec<Value> {
152 self.rosters
153 .iter()
154 .map(|roster| {
155 json!({
156 "package": roster.package,
157 "rules_applied": roster.applied,
158 "rules_skipped": roster.skipped,
159 "rules_unconfigured": roster
160 .unconfigured
161 .iter()
162 .map(|(rule, requirement)| json!({
163 "rule": rule,
164 "requirement": requirement,
165 }))
166 .collect::<Vec<_>>(),
167 })
168 })
169 .collect()
170 }
171
172 pub fn print(&self, offences: &[Offence]) {
173 println!("{}", self.render(offences));
174 }
175
176 }