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