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 {
    /// Returns true if the report should be included.
    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 {
    /// Filter and write the given reports. Returns the number of reports written.
    fn write(&mut self, reports: &ReportCollection, file_library: &FileLibrary) -> usize;

    /// Returns the number of reports written.
    #[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()
    }

    /// Returns the number of reports written.
    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
    }
}