Skip to content

Commit c29ef5c

Browse files
authored
Merge pull request #583 from justahero/sebastian/add-spec-version-type
Add spec version type
2 parents 1a377ce + a966c52 commit c29ef5c

File tree

5 files changed

+81
-16
lines changed

5 files changed

+81
-16
lines changed

cyclonedx-bom/src/errors.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* SPDX-License-Identifier: Apache-2.0
1717
*/
1818

19+
use crate::models::bom::SpecVersion;
20+
1921
#[derive(Debug, thiserror::Error)]
2022
#[non_exhaustive]
2123
pub enum BomError {
@@ -25,8 +27,11 @@ pub enum BomError {
2527
#[error("Failed to serialize BOM to XML: {0}")]
2628
XmlSerializationError(String),
2729

28-
#[error("Failed to serialize BOM to v1.3: {0}")]
29-
BomV13SerializationError(String),
30+
#[error("Failed to serialize BOM with version {0:?}: {1}")]
31+
BomSerializationError(SpecVersion, String),
32+
33+
#[error("Unsupported Spec Version '{0}'")]
34+
UnsupportedSpecVersion(String),
3035
}
3136

3237
#[derive(Debug, thiserror::Error)]
@@ -63,11 +68,19 @@ pub enum XmlWriteError {
6368
#[derive(Debug, thiserror::Error)]
6469
#[non_exhaustive]
6570
pub enum JsonReadError {
71+
#[error("IO Error #{0}")]
72+
IoError(#[from] std::io::Error),
73+
6674
#[error("Failed to deserialize JSON: {error}")]
6775
JsonElementReadError {
6876
#[from]
6977
error: serde_json::Error,
7078
},
79+
#[error("Invalid input format found: {error}")]
80+
BomError {
81+
#[from]
82+
error: BomError,
83+
},
7184
}
7285

7386
#[derive(Debug, thiserror::Error)]

cyclonedx-bom/src/models/bom.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
use std::collections::HashSet;
2020
use std::convert::TryInto;
2121
use std::fmt;
22+
use std::str::FromStr;
2223

2324
use once_cell::sync::Lazy;
2425
use regex::Regex;
26+
use serde::{Deserialize, Serialize};
2527
use serde_json::Value;
2628
use xml::{EmitterConfig, EventReader, EventWriter, ParserConfig};
2729

30+
use crate::errors::BomError;
2831
use crate::models::component::{Component, Components};
2932
use crate::models::composition::{BomReference, Compositions};
3033
use crate::models::dependency::Dependencies;
@@ -39,6 +42,38 @@ use crate::validation::{
3942
};
4043
use crate::xml::{FromXmlDocument, ToXml};
4144

45+
/// Represents the spec version of a BOM.
46+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
47+
#[non_exhaustive]
48+
pub enum SpecVersion {
49+
#[serde(rename = "1.3")]
50+
V1_3,
51+
#[serde(rename = "1.4")]
52+
V1_4,
53+
}
54+
55+
impl FromStr for SpecVersion {
56+
type Err = BomError;
57+
58+
fn from_str(input: &str) -> Result<Self, Self::Err> {
59+
match input {
60+
"1.3" => Ok(SpecVersion::V1_3),
61+
"1.4" => Ok(SpecVersion::V1_4),
62+
s => Err(BomError::UnsupportedSpecVersion(s.to_string())),
63+
}
64+
}
65+
}
66+
67+
impl ToString for SpecVersion {
68+
fn to_string(&self) -> String {
69+
let s = match self {
70+
SpecVersion::V1_3 => "1.3",
71+
SpecVersion::V1_4 => "1.4",
72+
};
73+
s.to_string()
74+
}
75+
}
76+
4277
#[derive(Debug, PartialEq, Eq)]
4378
pub struct Bom {
4479
pub version: u32,

cyclonedx-bom/src/specs/v1_3/bom.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
use crate::errors::BomError;
20+
use crate::models::bom::SpecVersion;
2021
use crate::{
2122
models::{self},
2223
utilities::{convert_optional, try_convert_optional},
@@ -46,7 +47,7 @@ struct Vulnerabilities();
4647
#[serde(rename_all = "camelCase")]
4748
pub(crate) struct Bom {
4849
bom_format: BomFormat,
49-
spec_version: String,
50+
spec_version: SpecVersion,
5051
version: Option<u32>,
5152
serial_number: Option<UrnUuid>,
5253
#[serde(skip_serializing_if = "Option::is_none")]
@@ -72,7 +73,7 @@ impl From<models::bom::Bom> for Bom {
7273
fn from(other: models::bom::Bom) -> Self {
7374
Self {
7475
bom_format: BomFormat::CycloneDX,
75-
spec_version: "1.3".to_string(),
76+
spec_version: SpecVersion::V1_3,
7677
version: Some(other.version),
7778
serial_number: convert_optional(other.serial_number),
7879
metadata: convert_optional(other.metadata),
@@ -94,7 +95,7 @@ impl TryFrom<models::bom::Bom> for Bom {
9495
fn try_from(other: models::bom::Bom) -> Result<Self, Self::Error> {
9596
Ok(Self {
9697
bom_format: BomFormat::CycloneDX,
97-
spec_version: "1.3".to_string(),
98+
spec_version: SpecVersion::V1_3,
9899
version: Some(other.version),
99100
serial_number: convert_optional(other.serial_number),
100101
metadata: try_convert_optional(other.metadata)?,
@@ -329,7 +330,7 @@ impl FromXmlDocument for Bom {
329330
})?;
330331
Ok(Self {
331332
bom_format: BomFormat::CycloneDX,
332-
spec_version: "1.3".to_string(),
333+
spec_version: SpecVersion::V1_3,
333334
version,
334335
serial_number,
335336
metadata,
@@ -387,7 +388,7 @@ pub(crate) mod test {
387388
pub(crate) fn minimal_bom_example() -> Bom {
388389
Bom {
389390
bom_format: BomFormat::CycloneDX,
390-
spec_version: "1.3".to_string(),
391+
spec_version: SpecVersion::V1_3,
391392
version: Some(1),
392393
serial_number: Some(UrnUuid("fake-uuid".to_string())),
393394
metadata: None,
@@ -404,7 +405,7 @@ pub(crate) mod test {
404405
pub(crate) fn full_bom_example() -> Bom {
405406
Bom {
406407
bom_format: BomFormat::CycloneDX,
407-
spec_version: "1.3".to_string(),
408+
spec_version: SpecVersion::V1_3,
408409
version: Some(1),
409410
serial_number: Some(UrnUuid("fake-uuid".to_string())),
410411
metadata: Some(example_metadata()),

cyclonedx-bom/src/specs/v1_3/component.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use std::convert::TryFrom;
2020

2121
use crate::errors::BomError;
22-
use crate::errors::BomError::BomV13SerializationError;
22+
use crate::errors::BomError::BomSerializationError;
2323
use crate::utilities::try_convert_optional;
2424
use crate::{
2525
errors::XmlReadError,
@@ -166,7 +166,10 @@ impl TryFrom<models::component::Component> for Component {
166166

167167
fn try_from(other: models::component::Component) -> Result<Self, Self::Error> {
168168
match other.version {
169-
None => Err(BomV13SerializationError("version missing".to_string())),
169+
None => Err(BomSerializationError(
170+
models::bom::SpecVersion::V1_3,
171+
"version missing".to_string(),
172+
)),
170173
Some(version) => Ok(Self {
171174
component_type: other.component_type.to_string(),
172175
mime_type: other.mime_type.map(|m| MimeType(m.0)),
@@ -1171,6 +1174,7 @@ impl From<MimeType> for models::component::MimeType {
11711174
#[cfg(test)]
11721175
pub(crate) mod test {
11731176
use crate::{
1177+
models::bom::SpecVersion,
11741178
specs::v1_3::{
11751179
attached_text::test::{corresponding_attached_text, example_attached_text},
11761180
code::test::{
@@ -1463,4 +1467,16 @@ pub(crate) mod test {
14631467
let expected = example_components();
14641468
assert_eq!(actual, expected);
14651469
}
1470+
1471+
#[test]
1472+
fn it_should_fail_conversion_without_version_field() {
1473+
let mut component = corresponding_component();
1474+
component.version = None;
1475+
1476+
let result = Component::try_from(component);
1477+
assert!(matches!(
1478+
result,
1479+
Err(BomError::BomSerializationError(SpecVersion::V1_3, _))
1480+
));
1481+
}
14661482
}

cyclonedx-bom/src/specs/v1_4/bom.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
use crate::{
20-
models::{self},
20+
models::{self, bom::SpecVersion},
2121
utilities::convert_optional,
2222
xml::{
2323
expected_namespace_or_error, optional_attribute, read_lax_validation_tag,
@@ -40,7 +40,7 @@ use xml::{reader, writer::XmlEvent};
4040
#[serde(rename_all = "camelCase")]
4141
pub(crate) struct Bom {
4242
bom_format: BomFormat,
43-
spec_version: String,
43+
spec_version: SpecVersion,
4444
version: Option<u32>,
4545
serial_number: Option<UrnUuid>,
4646
#[serde(skip_serializing_if = "Option::is_none")]
@@ -65,7 +65,7 @@ impl From<models::bom::Bom> for Bom {
6565
fn from(other: models::bom::Bom) -> Self {
6666
Self {
6767
bom_format: BomFormat::CycloneDX,
68-
spec_version: "1.4".to_string(),
68+
spec_version: SpecVersion::V1_4,
6969
version: Some(other.version),
7070
serial_number: convert_optional(other.serial_number),
7171
metadata: convert_optional(other.metadata),
@@ -316,7 +316,7 @@ impl FromXmlDocument for Bom {
316316
})?;
317317
Ok(Self {
318318
bom_format: BomFormat::CycloneDX,
319-
spec_version: "1.4".to_string(),
319+
spec_version: SpecVersion::V1_4,
320320
version,
321321
serial_number,
322322
metadata,
@@ -376,7 +376,7 @@ pub(crate) mod test {
376376
pub(crate) fn minimal_bom_example() -> Bom {
377377
Bom {
378378
bom_format: BomFormat::CycloneDX,
379-
spec_version: "1.4".to_string(),
379+
spec_version: SpecVersion::V1_4,
380380
version: Some(1),
381381
serial_number: Some(UrnUuid("fake-uuid".to_string())),
382382
metadata: None,
@@ -393,7 +393,7 @@ pub(crate) mod test {
393393
pub(crate) fn full_bom_example() -> Bom {
394394
Bom {
395395
bom_format: BomFormat::CycloneDX,
396-
spec_version: "1.4".to_string(),
396+
spec_version: SpecVersion::V1_4,
397397
version: Some(1),
398398
serial_number: Some(UrnUuid("fake-uuid".to_string())),
399399
metadata: Some(example_metadata()),

0 commit comments

Comments
 (0)