sbom_tools/reports/
mod.rs1pub mod analyst;
19mod csv;
20pub mod escape;
21mod html;
22mod json;
23mod markdown;
24mod sarif;
25mod sidebyside;
26pub mod streaming;
27mod summary;
28mod types;
29
30pub use csv::CsvReporter;
31pub use html::HtmlReporter;
32pub use json::JsonReporter;
33pub use markdown::MarkdownReporter;
34pub use sarif::SarifReporter;
35pub use sarif::{generate_compliance_sarif, generate_multi_compliance_sarif};
36pub use sidebyside::SideBySideReporter;
37pub use streaming::{NdjsonReporter, NdjsonWriter, StreamingJsonReporter, StreamingJsonWriter};
38pub use summary::{SummaryReporter, TableReporter};
39pub use types::{MinSeverity, ReportConfig, ReportFormat, ReportMetadata, ReportType};
40
41use crate::diff::DiffResult;
45use crate::model::NormalizedSbom;
46use std::io::Write;
47use thiserror::Error;
48
49#[derive(Error, Debug)]
51pub enum ReportError {
52 #[error("IO error: {0}")]
53 IoError(#[from] std::io::Error),
54
55 #[error("Serialization error: {0}")]
56 SerializationError(String),
57
58 #[error("Template error: {0}")]
59 TemplateError(String),
60
61 #[error("Invalid configuration: {0}")]
62 ConfigError(String),
63
64 #[error("Format error: {0}")]
65 FormatError(#[from] std::fmt::Error),
66}
67
68pub trait ReportGenerator {
70 fn generate_diff_report(
72 &self,
73 result: &DiffResult,
74 old_sbom: &NormalizedSbom,
75 new_sbom: &NormalizedSbom,
76 config: &ReportConfig,
77 ) -> Result<String, ReportError>;
78
79 fn generate_view_report(
81 &self,
82 sbom: &NormalizedSbom,
83 config: &ReportConfig,
84 ) -> Result<String, ReportError>;
85
86 fn write_diff_report(
88 &self,
89 result: &DiffResult,
90 old_sbom: &NormalizedSbom,
91 new_sbom: &NormalizedSbom,
92 config: &ReportConfig,
93 writer: &mut dyn Write,
94 ) -> Result<(), ReportError> {
95 let report = self.generate_diff_report(result, old_sbom, new_sbom, config)?;
96 writer.write_all(report.as_bytes())?;
97 Ok(())
98 }
99
100 fn format(&self) -> ReportFormat;
102}
103
104pub trait WriterReporter {
126 fn write_diff_to<W: Write>(
131 &self,
132 result: &DiffResult,
133 old_sbom: &NormalizedSbom,
134 new_sbom: &NormalizedSbom,
135 config: &ReportConfig,
136 writer: &mut W,
137 ) -> Result<(), ReportError>;
138
139 fn write_view_to<W: Write>(
141 &self,
142 sbom: &NormalizedSbom,
143 config: &ReportConfig,
144 writer: &mut W,
145 ) -> Result<(), ReportError>;
146
147 fn format(&self) -> ReportFormat;
149}
150
151#[deprecated(since = "0.2.0", note = "Renamed to WriterReporter for clarity")]
153pub trait StreamingReporter: WriterReporter {}
154
155impl<T: ReportGenerator> WriterReporter for T {
162 fn write_diff_to<W: Write>(
163 &self,
164 result: &DiffResult,
165 old_sbom: &NormalizedSbom,
166 new_sbom: &NormalizedSbom,
167 config: &ReportConfig,
168 writer: &mut W,
169 ) -> Result<(), ReportError> {
170 let report = self.generate_diff_report(result, old_sbom, new_sbom, config)?;
171 writer.write_all(report.as_bytes())?;
172 Ok(())
173 }
174
175 fn write_view_to<W: Write>(
176 &self,
177 sbom: &NormalizedSbom,
178 config: &ReportConfig,
179 writer: &mut W,
180 ) -> Result<(), ReportError> {
181 let report = self.generate_view_report(sbom, config)?;
182 writer.write_all(report.as_bytes())?;
183 Ok(())
184 }
185
186 fn format(&self) -> ReportFormat {
187 ReportGenerator::format(self)
188 }
189}
190
191#[allow(deprecated)]
192impl<T: WriterReporter> StreamingReporter for T {}
193
194#[must_use]
196pub fn create_reporter(format: ReportFormat) -> Box<dyn ReportGenerator> {
197 create_reporter_with_options(format, true)
198}
199
200#[must_use]
202pub fn create_reporter_with_options(
203 format: ReportFormat,
204 use_color: bool,
205) -> Box<dyn ReportGenerator> {
206 match format {
207 ReportFormat::Auto | ReportFormat::Summary => {
208 if use_color {
209 Box::new(SummaryReporter::new())
210 } else {
211 Box::new(SummaryReporter::new().no_color())
212 }
213 }
214 ReportFormat::Json | ReportFormat::Tui => Box::new(JsonReporter::new()), ReportFormat::Sarif => Box::new(SarifReporter::new()),
216 ReportFormat::Markdown => Box::new(MarkdownReporter::new()),
217 ReportFormat::Html => Box::new(HtmlReporter::new()),
218 ReportFormat::SideBySide => Box::new(SideBySideReporter::new()),
219 ReportFormat::Table => {
220 if use_color {
221 Box::new(TableReporter::new())
222 } else {
223 Box::new(TableReporter::new().no_color())
224 }
225 }
226 ReportFormat::Csv => Box::new(CsvReporter::new()),
227 }
228}