Skip to main content

sbom_tools/reports/
types.rs

1//! Report type definitions.
2
3use clap::ValueEnum;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7/// Output format for reports
8#[derive(
9    Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum, Serialize, Deserialize, JsonSchema,
10)]
11#[non_exhaustive]
12pub enum ReportFormat {
13    /// Auto-detect: TUI if TTY, summary otherwise
14    #[default]
15    Auto,
16    /// Interactive TUI display
17    Tui,
18    /// Side-by-side terminal diff (like difftastic)
19    #[value(alias = "side-by-side")]
20    SideBySide,
21    /// Structured JSON output
22    Json,
23    /// SARIF 2.1.0 for CI/CD
24    Sarif,
25    /// Human-readable Markdown
26    Markdown,
27    /// Interactive HTML report
28    Html,
29    /// Brief summary output
30    Summary,
31    /// Compact table for terminal (colored)
32    Table,
33    /// CSV for spreadsheet import
34    Csv,
35    /// Newline-delimited JSON (one record per line, streaming-friendly)
36    Ndjson,
37}
38
39impl std::fmt::Display for ReportFormat {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            Self::Auto => write!(f, "auto"),
43            Self::Tui => write!(f, "tui"),
44            Self::SideBySide => write!(f, "side-by-side"),
45            Self::Json => write!(f, "json"),
46            Self::Sarif => write!(f, "sarif"),
47            Self::Markdown => write!(f, "markdown"),
48            Self::Html => write!(f, "html"),
49            Self::Summary => write!(f, "summary"),
50            Self::Table => write!(f, "table"),
51            Self::Csv => write!(f, "csv"),
52            Self::Ndjson => write!(f, "ndjson"),
53        }
54    }
55}
56
57/// Types of reports that can be generated
58#[derive(
59    Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum, Serialize, Deserialize, JsonSchema,
60)]
61pub enum ReportType {
62    /// All report types
63    #[default]
64    All,
65    /// Component changes summary
66    Components,
67    /// Dependency changes
68    Dependencies,
69    /// License changes
70    Licenses,
71    /// Vulnerability changes
72    Vulnerabilities,
73}
74
75/// Minimum severity level for filtering
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
77pub enum MinSeverity {
78    Low,
79    Medium,
80    High,
81    Critical,
82}
83
84impl MinSeverity {
85    /// Parse severity from string. Returns None for unrecognized values.
86    #[must_use]
87    pub fn parse(s: &str) -> Option<Self> {
88        match s.to_lowercase().as_str() {
89            "low" => Some(Self::Low),
90            "medium" => Some(Self::Medium),
91            "high" => Some(Self::High),
92            "critical" => Some(Self::Critical),
93            _ => None,
94        }
95    }
96
97    /// Check if a severity string meets this minimum threshold
98    #[must_use]
99    pub fn meets_threshold(&self, severity: &str) -> bool {
100        let sev = match severity.to_lowercase().as_str() {
101            "critical" => Self::Critical,
102            "high" => Self::High,
103            "medium" => Self::Medium,
104            "low" => Self::Low,
105            _ => return true, // Unknown severities are included
106        };
107        sev >= *self
108    }
109}
110
111/// Configuration for report generation
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ReportConfig {
114    /// Which report types to include
115    pub report_types: Vec<ReportType>,
116    /// Include unchanged items in the report
117    pub include_unchanged: bool,
118    /// Maximum items per section
119    pub max_items: Option<usize>,
120    /// Include detailed field changes
121    pub include_field_changes: bool,
122    /// Title for the report
123    pub title: Option<String>,
124    /// Additional metadata to include
125    pub metadata: ReportMetadata,
126    /// Only show items with changes (filter out unchanged)
127    pub only_changes: bool,
128    /// Minimum severity level for vulnerability filtering
129    pub min_severity: Option<MinSeverity>,
130    /// Pre-computed CRA compliance for old SBOM (avoids redundant recomputation)
131    #[serde(skip)]
132    pub old_cra_compliance: Option<crate::quality::ComplianceResult>,
133    /// Pre-computed CRA compliance for new SBOM (avoids redundant recomputation)
134    #[serde(skip)]
135    pub new_cra_compliance: Option<crate::quality::ComplianceResult>,
136    /// Pre-computed CRA compliance for single SBOM in view mode
137    #[serde(skip)]
138    pub view_cra_compliance: Option<crate::quality::ComplianceResult>,
139}
140
141impl Default for ReportConfig {
142    fn default() -> Self {
143        Self {
144            report_types: vec![ReportType::All],
145            include_unchanged: false,
146            max_items: None,
147            include_field_changes: true,
148            title: None,
149            metadata: ReportMetadata::default(),
150            only_changes: false,
151            min_severity: None,
152            old_cra_compliance: None,
153            new_cra_compliance: None,
154            view_cra_compliance: None,
155        }
156    }
157}
158
159impl ReportConfig {
160    /// Create a config for all report types
161    #[must_use]
162    pub fn all() -> Self {
163        Self::default()
164    }
165
166    /// Create a config for specific report types
167    #[must_use]
168    pub fn with_types(types: Vec<ReportType>) -> Self {
169        Self {
170            report_types: types,
171            ..Default::default()
172        }
173    }
174
175    /// Check if a report type should be included
176    #[must_use]
177    pub fn includes(&self, report_type: ReportType) -> bool {
178        self.report_types.contains(&ReportType::All) || self.report_types.contains(&report_type)
179    }
180}
181
182/// Metadata included in reports
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
184pub struct ReportMetadata {
185    /// Old SBOM file path
186    pub old_sbom_path: Option<String>,
187    /// New SBOM file path
188    pub new_sbom_path: Option<String>,
189    /// Tool version
190    pub tool_version: String,
191    /// Generation timestamp
192    pub generated_at: Option<String>,
193    /// Custom properties
194    pub custom: std::collections::HashMap<String, String>,
195}
196
197impl ReportMetadata {
198    #[must_use]
199    pub fn new() -> Self {
200        Self {
201            tool_version: env!("CARGO_PKG_VERSION").to_string(),
202            ..Default::default()
203        }
204    }
205}