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::generate_compliance_sarif;
35pub use sarif::SarifReporter;
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
194pub fn create_reporter(format: ReportFormat) -> Box<dyn ReportGenerator> {
196 create_reporter_with_options(format, true)
197}
198
199pub fn create_reporter_with_options(
201 format: ReportFormat,
202 use_color: bool,
203) -> Box<dyn ReportGenerator> {
204 match format {
205 ReportFormat::Auto => {
206 if use_color {
207 Box::new(SummaryReporter::new())
208 } else {
209 Box::new(SummaryReporter::new().no_color())
210 }
211 }
212 ReportFormat::Json => Box::new(JsonReporter::new()),
213 ReportFormat::Sarif => Box::new(SarifReporter::new()),
214 ReportFormat::Markdown => Box::new(MarkdownReporter::new()),
215 ReportFormat::Html => Box::new(HtmlReporter::new()),
216 ReportFormat::SideBySide => Box::new(SideBySideReporter::new()),
217 ReportFormat::Summary => {
218 if use_color {
219 Box::new(SummaryReporter::new())
220 } else {
221 Box::new(SummaryReporter::new().no_color())
222 }
223 }
224 ReportFormat::Table => {
225 if use_color {
226 Box::new(TableReporter::new())
227 } else {
228 Box::new(TableReporter::new().no_color())
229 }
230 }
231 ReportFormat::Csv => Box::new(CsvReporter::new()),
232 ReportFormat::Tui => Box::new(JsonReporter::new()), }
234}