pub mod analyst;
mod csv;
pub mod escape;
mod html;
mod json;
mod markdown;
mod sarif;
mod sidebyside;
pub mod streaming;
mod summary;
mod types;
pub use csv::CsvReporter;
pub use html::HtmlReporter;
pub use json::JsonReporter;
pub use markdown::MarkdownReporter;
pub use sarif::SarifReporter;
pub use sarif::{generate_compliance_sarif, generate_multi_compliance_sarif};
pub use sidebyside::SideBySideReporter;
pub use streaming::{NdjsonReporter, NdjsonWriter, StreamingJsonReporter, StreamingJsonWriter};
pub use summary::{SummaryReporter, TableReporter};
pub use types::{MinSeverity, ReportConfig, ReportFormat, ReportMetadata, ReportType};
use crate::diff::DiffResult;
use crate::model::NormalizedSbom;
use std::io::Write;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ReportError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Template error: {0}")]
TemplateError(String),
#[error("Invalid configuration: {0}")]
ConfigError(String),
#[error("Format error: {0}")]
FormatError(#[from] std::fmt::Error),
}
pub trait ReportGenerator {
fn generate_diff_report(
&self,
result: &DiffResult,
old_sbom: &NormalizedSbom,
new_sbom: &NormalizedSbom,
config: &ReportConfig,
) -> Result<String, ReportError>;
fn generate_view_report(
&self,
sbom: &NormalizedSbom,
config: &ReportConfig,
) -> Result<String, ReportError>;
fn write_diff_report(
&self,
result: &DiffResult,
old_sbom: &NormalizedSbom,
new_sbom: &NormalizedSbom,
config: &ReportConfig,
writer: &mut dyn Write,
) -> Result<(), ReportError> {
let report = self.generate_diff_report(result, old_sbom, new_sbom, config)?;
writer.write_all(report.as_bytes())?;
Ok(())
}
fn format(&self) -> ReportFormat;
}
pub trait WriterReporter {
fn write_diff_to<W: Write>(
&self,
result: &DiffResult,
old_sbom: &NormalizedSbom,
new_sbom: &NormalizedSbom,
config: &ReportConfig,
writer: &mut W,
) -> Result<(), ReportError>;
fn write_view_to<W: Write>(
&self,
sbom: &NormalizedSbom,
config: &ReportConfig,
writer: &mut W,
) -> Result<(), ReportError>;
fn format(&self) -> ReportFormat;
}
#[deprecated(since = "0.2.0", note = "Renamed to WriterReporter for clarity")]
pub trait StreamingReporter: WriterReporter {}
impl<T: ReportGenerator> WriterReporter for T {
fn write_diff_to<W: Write>(
&self,
result: &DiffResult,
old_sbom: &NormalizedSbom,
new_sbom: &NormalizedSbom,
config: &ReportConfig,
writer: &mut W,
) -> Result<(), ReportError> {
let report = self.generate_diff_report(result, old_sbom, new_sbom, config)?;
writer.write_all(report.as_bytes())?;
Ok(())
}
fn write_view_to<W: Write>(
&self,
sbom: &NormalizedSbom,
config: &ReportConfig,
writer: &mut W,
) -> Result<(), ReportError> {
let report = self.generate_view_report(sbom, config)?;
writer.write_all(report.as_bytes())?;
Ok(())
}
fn format(&self) -> ReportFormat {
ReportGenerator::format(self)
}
}
#[allow(deprecated)]
impl<T: WriterReporter> StreamingReporter for T {}
#[must_use]
pub fn create_reporter(format: ReportFormat) -> Box<dyn ReportGenerator> {
create_reporter_with_options(format, true)
}
#[must_use]
pub fn create_reporter_with_options(
format: ReportFormat,
use_color: bool,
) -> Box<dyn ReportGenerator> {
match format {
ReportFormat::Auto | ReportFormat::Summary => {
if use_color {
Box::new(SummaryReporter::new())
} else {
Box::new(SummaryReporter::new().no_color())
}
}
ReportFormat::Json | ReportFormat::Tui => Box::new(JsonReporter::new()), ReportFormat::Sarif => Box::new(SarifReporter::new()),
ReportFormat::Markdown => Box::new(MarkdownReporter::new()),
ReportFormat::Html => Box::new(HtmlReporter::new()),
ReportFormat::SideBySide => Box::new(SideBySideReporter::new()),
ReportFormat::Table => {
if use_color {
Box::new(TableReporter::new())
} else {
Box::new(TableReporter::new().no_color())
}
}
ReportFormat::Csv => Box::new(CsvReporter::new()),
}
}