sbom_tools/pipeline/
mod.rs1mod diff_stage;
7pub mod enrich;
8mod output;
9mod parse;
10mod report_stage;
11
12pub use diff_stage::{
13 apply_post_diff_filters, compute_diff, graph_diff_config_from, validate_post_diff_filters,
14};
15pub use enrich::{AggregatedEnrichmentStats, enrich_sbom_full, enrich_sboms};
16pub use output::{OutputTarget, auto_detect_format, should_use_color, write_output};
17pub use parse::{ParsedSbom, STDIN_PATH, is_stdin_path, parse_sbom_with_context, read_input};
18pub use report_stage::{discover_cra_sidecar, output_report};
19
20#[cfg(feature = "enrichment")]
21pub use parse::{
22 build_enrichment_config, enrich_eol, enrich_epss, enrich_huggingface, enrich_kev, enrich_sbom,
23 enrich_staleness, enrich_vex,
24};
25
26#[derive(Debug, thiserror::Error)]
28pub enum PipelineError {
29 #[error("Parse failed for {path}: {source}")]
31 ParseFailed { path: String, source: anyhow::Error },
32
33 #[error("Enrichment failed: {reason}")]
35 EnrichmentFailed { reason: String },
36
37 #[error("Diff failed: {source}")]
39 DiffFailed {
40 #[source]
41 source: anyhow::Error,
42 },
43
44 #[error("Report failed: {source}")]
46 ReportFailed {
47 #[source]
48 source: anyhow::Error,
49 },
50}
51
52pub mod exit_codes {
64 pub const SUCCESS: i32 = 0;
66 pub const CHANGES_DETECTED: i32 = 1;
68 pub const VULNS_INTRODUCED: i32 = 2;
70 pub const ERROR: i32 = 3;
73 pub const VEX_GAPS_FOUND: i32 = 4;
75 pub const LICENSE_VIOLATIONS: i32 = 5;
77 pub const KEV_INTRODUCED: i32 = 6;
79 pub const ML_REGRESSION: i32 = 7;
81
82 pub const COMPLIANCE_ERRORS: i32 = CHANGES_DETECTED;
87 pub const COMPLIANCE_WARNINGS: i32 = VULNS_INTRODUCED;
90 pub const QUALITY_BELOW_THRESHOLD: i32 = CHANGES_DETECTED;
93 pub const NO_MATCHES: i32 = CHANGES_DETECTED;
96}
97
98pub mod dirs {
100 use std::path::PathBuf;
101
102 #[must_use]
104 pub fn cache_dir() -> Option<PathBuf> {
105 #[cfg(target_os = "macos")]
106 {
107 std::env::var("HOME")
108 .ok()
109 .map(|h| PathBuf::from(h).join("Library").join("Caches"))
110 }
111 #[cfg(target_os = "linux")]
112 {
113 std::env::var("XDG_CACHE_HOME")
114 .ok()
115 .map(PathBuf::from)
116 .or_else(|| {
117 std::env::var("HOME")
118 .ok()
119 .map(|h| PathBuf::from(h).join(".cache"))
120 })
121 }
122 #[cfg(target_os = "windows")]
123 {
124 std::env::var("LOCALAPPDATA").ok().map(PathBuf::from)
125 }
126 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
127 {
128 std::env::var("HOME")
129 .ok()
130 .map(|h| PathBuf::from(h).join(".cache"))
131 }
132 }
133
134 #[must_use]
136 pub fn osv_cache_dir() -> PathBuf {
137 cache_dir()
138 .unwrap_or_else(|| PathBuf::from(".cache"))
139 .join("sbom-tools")
140 .join("osv")
141 }
142
143 #[must_use]
145 pub fn eol_cache_dir() -> PathBuf {
146 cache_dir()
147 .unwrap_or_else(|| PathBuf::from(".cache"))
148 .join("sbom-tools")
149 .join("eol")
150 }
151
152 #[must_use]
154 pub fn kev_cache_dir() -> PathBuf {
155 cache_dir()
156 .unwrap_or_else(|| PathBuf::from(".cache"))
157 .join("sbom-tools")
158 .join("kev")
159 }
160
161 #[must_use]
163 pub fn epss_cache_dir() -> PathBuf {
164 cache_dir()
165 .unwrap_or_else(|| PathBuf::from(".cache"))
166 .join("sbom-tools")
167 .join("epss")
168 }
169
170 #[must_use]
172 pub fn staleness_cache_dir() -> PathBuf {
173 cache_dir()
174 .unwrap_or_else(|| PathBuf::from(".cache"))
175 .join("sbom-tools")
176 .join("staleness")
177 }
178
179 #[must_use]
181 pub fn huggingface_cache_dir() -> PathBuf {
182 cache_dir()
183 .unwrap_or_else(|| PathBuf::from(".cache"))
184 .join("sbom-tools")
185 .join("huggingface")
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_exit_codes_values() {
195 assert_eq!(exit_codes::SUCCESS, 0);
196 assert_eq!(exit_codes::CHANGES_DETECTED, 1);
197 assert_eq!(exit_codes::VULNS_INTRODUCED, 2);
198 assert_eq!(exit_codes::ERROR, 3);
199 assert_eq!(exit_codes::VEX_GAPS_FOUND, 4);
200 assert_eq!(exit_codes::LICENSE_VIOLATIONS, 5);
201 assert_eq!(exit_codes::KEV_INTRODUCED, 6);
202 }
203
204 #[test]
205 fn test_per_command_exit_code_aliases_preserve_values() {
206 assert_eq!(exit_codes::COMPLIANCE_ERRORS, exit_codes::CHANGES_DETECTED);
208 assert_eq!(
209 exit_codes::COMPLIANCE_WARNINGS,
210 exit_codes::VULNS_INTRODUCED
211 );
212 assert_eq!(
213 exit_codes::QUALITY_BELOW_THRESHOLD,
214 exit_codes::CHANGES_DETECTED
215 );
216 assert_eq!(exit_codes::NO_MATCHES, exit_codes::CHANGES_DETECTED);
217 }
218
219 #[test]
220 fn test_cache_dir_returns_some() {
221 let _ = dirs::cache_dir();
224 }
225
226 #[test]
227 fn test_osv_cache_dir_path() {
228 let path = dirs::osv_cache_dir();
229 let path_str = path.to_string_lossy();
230 assert!(path_str.contains("osv"));
231 }
232}