1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use anyhow;
use anyhow::Context;
use log::{info, warn};
use std::fs::File;
use std::io::Write;
use std::path::{PathBuf, Path};
use crate::sarif_conversion::ToSarif;
use crate::{
program_library::report::{Report, ReportCollection},
file_definition::FileLibrary,
};
pub trait ReportFilter {
fn filter(&self, report: &Report) -> bool;
}
impl<F: Fn(&Report) -> bool> ReportFilter for F {
fn filter(&self, report: &Report) -> bool {
self(report)
}
}
pub trait ReportWriter {
fn write(&mut self, reports: &ReportCollection, file_library: &FileLibrary) -> usize;
#[must_use]
fn written(&self) -> usize;
}
#[derive(Default)]
pub struct StdoutWriter {
verbose: bool,
written: usize,
filters: Vec<Box<dyn ReportFilter>>,
}
impl StdoutWriter {
pub fn new(verbose: bool) -> StdoutWriter {
StdoutWriter { verbose, ..Default::default() }
}
pub fn add_filter(mut self, filter: impl ReportFilter + 'static) -> StdoutWriter {
self.filters.push(Box::new(filter));
self
}
fn filter(&self, reports: &ReportCollection) -> ReportCollection {
reports
.iter()
.filter(|report| self.filters.iter().all(|f| f.filter(report)))
.cloned()
.collect()
}
}
impl ReportWriter for StdoutWriter {
fn write(&mut self, reports: &ReportCollection, file_library: &FileLibrary) -> usize {
let reports = self.filter(reports);
Report::print_reports(&reports, file_library, self.verbose);
self.written += reports.len();
reports.len()
}
fn written(&self) -> usize {
self.written
}
}
#[derive(Default)]
pub struct SarifWriter {
sarif_file: PathBuf,
written: usize,
filters: Vec<Box<dyn ReportFilter>>,
}
impl SarifWriter {
pub fn new(sarif_file: &Path) -> SarifWriter {
SarifWriter { sarif_file: sarif_file.to_owned(), ..Default::default() }
}
pub fn add_filter(mut self, filter: impl ReportFilter + 'static) -> SarifWriter {
self.filters.push(Box::new(filter));
self
}
fn filter(&self, reports: &ReportCollection) -> ReportCollection {
reports
.iter()
.filter(|report| self.filters.iter().all(|f| f.filter(report)))
.cloned()
.collect()
}
fn serialize_reports(
&self,
reports: &ReportCollection,
file_library: &FileLibrary,
) -> anyhow::Result<()> {
let sarif =
reports.to_sarif(file_library).context("failed to convert reports to Sarif format")?;
let json = serde_json::to_string_pretty(&sarif)?;
let mut sarif_file = File::create(&self.sarif_file)?;
writeln!(sarif_file, "{}", &json)
.with_context(|| format!("could not write to {}", self.sarif_file.display()))?;
Ok(())
}
}
impl ReportWriter for SarifWriter {
fn write(&mut self, reports: &ReportCollection, file_library: &FileLibrary) -> usize {
let reports = self.filter(reports);
match self.serialize_reports(&reports, file_library) {
Ok(()) => info!("reports written to `{}`", self.sarif_file.display()),
Err(_) => warn!("failed to write reports to `{}`", self.sarif_file.display()),
}
self.written += reports.len();
reports.len()
}
fn written(&self) -> usize {
self.written
}
}