sbom_walker/model/sbom/
mod.rs1#[cfg(feature = "serde-cyclonedx")]
4pub mod serde_cyclonedx;
5
6mod json;
7
8pub use json::JsonPayload;
9
10use anyhow::{anyhow, bail};
11use serde_json::Value;
12use std::fmt::{Debug, Display, Formatter};
13
14pub enum Parser {
15 CycloneDxJson,
16 CycloneDxXml,
17}
18
19#[allow(clippy::large_enum_variant)]
21#[derive(Clone, Debug, PartialEq)]
22pub enum Sbom {
23 #[cfg(feature = "spdx-rs")]
24 Spdx(spdx_rs::models::SPDX),
25 #[cfg(feature = "cyclonedx-bom")]
26 #[deprecated(
27 since = "0.12.0",
28 note = "Replaced with serde_cyclondex and the SerdeCycloneDx variant"
29 )]
30 CycloneDx(cyclonedx_bom::prelude::Bom),
31 #[cfg(feature = "serde-cyclonedx")]
32 SerdeCycloneDx(serde_cyclonedx::Sbom<'static>),
33}
34
35#[derive(Default, Debug)]
36pub struct ParseAnyError(pub Vec<(ParserKind, anyhow::Error)>);
37
38impl std::error::Error for ParseAnyError {}
39
40impl From<(ParserKind, anyhow::Error)> for ParseAnyError {
41 fn from(value: (ParserKind, anyhow::Error)) -> Self {
42 Self(vec![value])
43 }
44}
45
46impl ParseAnyError {
47 pub fn new() -> Self {
48 Self(vec![])
49 }
50
51 pub fn add(mut self, kind: ParserKind, error: anyhow::Error) -> Self {
52 self.0.push((kind, error));
53 self
54 }
55}
56
57impl Display for ParseAnyError {
58 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59 write!(f, "failed to parse SBOM:")?;
60 if self.0.is_empty() {
61 write!(f, "failed to parse SBOM: no parser configured")?;
62 } else if self.0.len() == 1 {
63 write!(f, "{}: {}", self.0[0].0, self.0[0].1)?;
64 } else {
65 writeln!(f)?;
66 for (kind, err) in &self.0 {
67 writeln!(f, " {kind}: {err}")?;
68 }
69 }
70
71 Ok(())
72 }
73}
74
75#[derive(Copy, Clone, Debug, PartialEq, Eq)]
76pub enum ParserKind {
77 Cyclone13DxJson,
78 Cyclone13DxXml,
79 Spdx23Json,
80 Spdx23Tag,
81}
82
83impl Display for ParserKind {
84 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85 match self {
86 Self::Cyclone13DxJson => write!(f, "CycloneDX 1.3 JSON"),
87 Self::Cyclone13DxXml => write!(f, "CycloneDX 1.3 XML"),
88 Self::Spdx23Json => write!(f, "SPDX 2.3 JSON"),
89 Self::Spdx23Tag => write!(f, "SPDX 2.3 tagged"),
90 }
91 }
92}
93
94impl Sbom {
95 pub fn is_cyclondx_json(json: &Value) -> anyhow::Result<&str> {
97 let format = json["bomFormat"]
98 .as_str()
99 .ok_or_else(|| anyhow!("Missing field 'bomFormat'"))?;
100
101 if format != "CycloneDX" {
102 bail!("Unknown CycloneDX 'bomFormat' value: {format}");
103 }
104
105 let spec_version = json["specVersion"]
106 .as_str()
107 .ok_or_else(|| anyhow!("Missing field 'specVersion'"))?;
108
109 Ok(spec_version)
110 }
111
112 pub fn is_spdx_json(json: &Value) -> anyhow::Result<&str> {
114 let version = json["spdxVersion"]
115 .as_str()
116 .ok_or_else(|| anyhow!("Missing field 'spdxVersion'"))?;
117
118 Ok(version)
119 }
120
121 pub fn try_parse_any_json(json: Value) -> Result<Self, ParseAnyError> {
122 let err = ParseAnyError::new();
123
124 #[cfg(feature = "serde-cyclonedx")]
125 let err = match Self::is_cyclondx_json(&json) {
126 Ok("1.4" | "1.5" | "1.6") => {
127 return Self::try_serde_cyclonedx_json(JsonPayload::Value(json)).map_err(|e| {
128 ParseAnyError::from((ParserKind::Cyclone13DxJson, e.into()))
130 });
131 }
132 Ok(version) => {
133 return Err(ParseAnyError::from((
136 ParserKind::Cyclone13DxJson,
137 anyhow!("Unsupported CycloneDX version: {version}"),
138 )));
139 }
140 Err(e) => err.add(ParserKind::Cyclone13DxJson, e),
142 };
143
144 #[cfg(feature = "cyclonedx-bom")]
145 let err = match Self::is_cyclondx_json(&json) {
146 Ok("1.2" | "1.3" | "1.4") => {
147 return Self::try_cyclonedx_json(JsonPayload::Value(json)).map_err(|e| {
148 ParseAnyError::from((ParserKind::Cyclone13DxJson, e.into()))
150 });
151 }
152 Ok(version) => {
153 return Err(ParseAnyError::from((
156 ParserKind::Cyclone13DxJson,
157 anyhow!("Unsupported CycloneDX version: {version}"),
158 )));
159 }
160 Err(e) => err.add(ParserKind::Cyclone13DxJson, e),
162 };
163
164 #[cfg(feature = "spdx-rs")]
165 let err = match Self::is_spdx_json(&json) {
166 Ok("SPDX-2.2" | "SPDX-2.3") => {
167 return Self::try_spdx_json(JsonPayload::Value(json)).map_err(|e| {
168 ParseAnyError::from((ParserKind::Spdx23Json, e.into()))
170 });
171 }
172 Ok(version) => {
173 return Err(ParseAnyError::from((
176 ParserKind::Spdx23Json,
177 anyhow!("Unsupported SPDX version: {version}"),
178 )));
179 }
180 Err(e) => err.add(ParserKind::Spdx23Json, e),
181 };
182
183 let _json = json;
185 Err(err)
186 }
187
188 pub fn try_parse_any(data: &[u8]) -> Result<Self, ParseAnyError> {
190 #[allow(unused)]
191 if let Ok(json) = serde_json::from_slice(data) {
192 Self::try_parse_any_json(json)
195 } else {
196 let err = ParseAnyError::new();
198
199 #[cfg(feature = "cyclonedx-bom")]
200 let err = match Self::try_cyclonedx_xml(data) {
201 Ok(doc) => return Ok(doc),
202 Err(e) => err.add(ParserKind::Cyclone13DxXml, e.into()),
203 };
204
205 #[cfg(feature = "spdx-rs")]
206 use anyhow::Context;
207
208 #[cfg(feature = "spdx-rs")]
209 let err = match std::str::from_utf8(data)
210 .context("unable to interpret bytes as string")
211 .and_then(|data| Self::try_spdx_tag(data).map_err(|err| err.into()))
212 {
213 Ok(doc) => return Ok(doc),
214 Err(e) => err.add(ParserKind::Spdx23Tag, e),
215 };
216
217 Err(err)
218 }
219 }
220
221 #[cfg(feature = "spdx-rs")]
222 pub fn try_spdx_json(data: JsonPayload) -> Result<Self, serde_json::Error> {
223 Ok(Self::Spdx(data.parse()?))
224 }
225
226 #[cfg(feature = "spdx-rs")]
227 pub fn try_spdx_tag(data: &str) -> Result<Self, spdx_rs::error::SpdxError> {
228 Ok(Self::Spdx(spdx_rs::parsers::spdx_from_tag_value(data)?))
229 }
230
231 #[cfg(feature = "cyclonedx-bom")]
232 #[allow(deprecated)]
233 pub fn try_cyclonedx_json<'a>(
234 data: impl Into<JsonPayload<'a>>,
235 ) -> Result<Self, cyclonedx_bom::errors::JsonReadError> {
236 use cyclonedx_bom::prelude::Bom;
237
238 match data.into() {
239 JsonPayload::Value(json) => Ok(Self::CycloneDx(Bom::parse_json_value(json)?)),
240 JsonPayload::Bytes(data) => Ok(Self::CycloneDx(Bom::parse_from_json(data)?)),
241 }
242 }
243
244 #[cfg(feature = "cyclonedx-bom")]
245 #[allow(deprecated)]
246 pub fn try_cyclonedx_xml(data: &[u8]) -> Result<Self, cyclonedx_bom::errors::XmlReadError> {
247 Ok(Self::CycloneDx(
248 cyclonedx_bom::prelude::Bom::parse_from_xml_v1_3(data)?,
249 ))
250 }
251
252 #[cfg(feature = "serde-cyclonedx")]
253 pub fn try_serde_cyclonedx_json<'a>(
254 data: impl Into<JsonPayload<'a>>,
255 ) -> Result<Self, serde_json::Error> {
256 match data.into() {
257 JsonPayload::Value(json) => Ok(Self::SerdeCycloneDx(serde_json::from_value(json)?)),
258 JsonPayload::Bytes(data) => Ok(Self::SerdeCycloneDx(serde_json::from_slice(data)?)),
259 }
260 }
261}