Skip to content

Commit d764e4e

Browse files
authored
Merge pull request #639 from justahero/sebastian/refactor-validation
Refactor validation logic to be more concise
2 parents 20ce3c4 + 90ee6fd commit d764e4e

37 files changed

+2442
-3977
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cargo-cyclonedx/src/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ impl Args {
168168
accept_named: HashSet::from_iter(self.license_accept_named.clone()),
169169
});
170170

171-
let describe = self.describe.clone();
172-
let spec_version = self.spec_version.clone();
171+
let describe = self.describe;
172+
let spec_version = self.spec_version;
173173

174174
Ok(SbomConfig {
175175
format: self.format,

cargo-cyclonedx/src/config.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,8 @@ impl SbomConfig {
5555
.clone()
5656
.map(|other| self.license_parser.clone().unwrap_or_default().merge(other))
5757
.or_else(|| self.license_parser.clone()),
58-
describe: other.describe.clone().or_else(|| self.describe.clone()),
59-
spec_version: other
60-
.spec_version
61-
.clone()
62-
.or_else(|| self.spec_version.clone()),
58+
describe: other.describe.or(self.describe),
59+
spec_version: other.spec_version.or(self.spec_version),
6360
}
6461
}
6562

cargo-cyclonedx/src/generator.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ use cyclonedx_bom::models::metadata::MetadataError;
5050
use cyclonedx_bom::models::organization::OrganizationalContact;
5151
use cyclonedx_bom::models::tool::{Tool, Tools};
5252
use cyclonedx_bom::validation::Validate;
53-
use cyclonedx_bom::validation::ValidationResult;
5453
use once_cell::sync::Lazy;
5554
use regex::Regex;
5655

@@ -714,8 +713,11 @@ impl GeneratedSbom {
714713
// If running in debug mode, validate that the SBOM is self-consistent and well-formed
715714
if cfg!(debug_assertions) {
716715
let result = bom.validate();
717-
if let ValidationResult::Failed { reasons } = result {
718-
panic!("The generated SBOM failed validation: {:?}", &reasons);
716+
if result.has_errors() {
717+
panic!(
718+
"The generated SBOM failed validation: {:?}",
719+
result.errors()
720+
);
719721
}
720722
}
721723

@@ -796,7 +798,7 @@ impl GeneratedSbom {
796798

797799
fn filename(&self, binary_name: Option<&str>, target_kind: &[String]) -> String {
798800
let output_options = self.sbom_config.output_options();
799-
let describe = self.sbom_config.describe.clone().unwrap_or_default();
801+
let describe = self.sbom_config.describe.unwrap_or_default();
800802

801803
let mut prefix = match describe {
802804
Describe::Crate => self.package_name.clone(),

cargo-cyclonedx/src/purl.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::str::FromStr;
22

33
use cargo_metadata::{camino::Utf8Path, Package};
4-
use cyclonedx_bom::prelude::Purl as CdxPurl;
4+
use cyclonedx_bom::{external_models::uri::validate_purl, prelude::Purl as CdxPurl};
55
use pathdiff::diff_utf8_paths;
66
use purl::{PackageError, PackageType, PurlBuilder};
77

@@ -81,8 +81,7 @@ fn to_purl_subpath(path: &Utf8Path) -> String {
8181
}
8282

8383
fn assert_validation_passes(purl: &CdxPurl) {
84-
use cyclonedx_bom::validation::{Validate, ValidationResult};
85-
assert_eq!(purl.validate(), ValidationResult::Passed);
84+
assert!(validate_purl(purl).is_ok());
8685
}
8786

8887
#[cfg(test)]

cyclonedx-bom/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rust-version.workspace = true
1616
[dependencies]
1717
base64 = "0.21.2"
1818
fluent-uri = "0.1.4"
19+
indexmap = "2.2.2"
1920
once_cell = "1.18.0"
2021
ordered-float = { version = "4.2.0", default-features = false }
2122
packageurl = "0.3.0"

cyclonedx-bom/src/external_models/date_time.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::convert::TryFrom;
2121
use thiserror::Error;
2222
use time::{format_description::well_known::Iso8601, OffsetDateTime};
2323

24-
use crate::validation::{Validate, ValidationContext, ValidationResult};
24+
use crate::validation::ValidationError;
2525

2626
/// For the purposes of CycloneDX SBOM documents, `DateTime` is a ISO8601 formatted timestamp
2727
///
@@ -41,6 +41,13 @@ use crate::validation::{Validate, ValidationContext, ValidationResult};
4141
#[derive(Clone, Debug, PartialEq, Eq)]
4242
pub struct DateTime(pub(crate) String);
4343

44+
pub fn validate_date_time(date_time: &DateTime) -> Result<(), ValidationError> {
45+
if OffsetDateTime::parse(&date_time.0, &Iso8601::DEFAULT).is_err() {
46+
return Err("DateTime does not conform to ISO 8601".into());
47+
}
48+
Ok(())
49+
}
50+
4451
impl DateTime {
4552
pub fn now() -> Result<Self, DateTimeError> {
4653
let now = OffsetDateTime::now_utc()
@@ -64,15 +71,6 @@ impl TryFrom<String> for DateTime {
6471
}
6572
}
6673

67-
impl Validate for DateTime {
68-
fn validate_with_context(&self, context: ValidationContext) -> ValidationResult {
69-
match OffsetDateTime::parse(&self.0.to_string(), &Iso8601::DEFAULT) {
70-
Ok(_) => ValidationResult::Passed,
71-
Err(_) => ValidationResult::failure("DateTime does not conform to ISO 8601", context),
72-
}
73-
}
74-
}
75-
7674
impl ToString for DateTime {
7775
fn to_string(&self) -> String {
7876
self.0.clone()
@@ -90,26 +88,25 @@ pub enum DateTimeError {
9088

9189
#[cfg(test)]
9290
mod test {
93-
use super::*;
9491
use pretty_assertions::assert_eq;
9592

93+
use crate::{external_models::validate_date_time, prelude::DateTime};
94+
9695
#[test]
9796
fn valid_datetimes_should_pass_validation() {
98-
let validation_result = DateTime("1969-06-28T01:20:00.00-04:00".to_string()).validate();
97+
let validation_result =
98+
validate_date_time(&DateTime("1969-06-28T01:20:00.00-04:00".to_string()));
9999

100-
assert_eq!(validation_result, ValidationResult::Passed)
100+
assert!(validation_result.is_ok());
101101
}
102102

103103
#[test]
104104
fn invalid_datetimes_should_fail_validation() {
105-
let validation_result = DateTime("invalid date".to_string()).validate();
105+
let validation_result = validate_date_time(&DateTime("invalid date".to_string()));
106106

107107
assert_eq!(
108108
validation_result,
109-
ValidationResult::failure(
110-
"DateTime does not conform to ISO 8601",
111-
ValidationContext::default()
112-
)
113-
)
109+
Err("DateTime does not conform to ISO 8601".into()),
110+
);
114111
}
115112
}

cyclonedx-bom/src/external_models/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ pub mod date_time;
2020
pub mod normalized_string;
2121
pub mod spdx;
2222
pub mod uri;
23+
24+
pub(crate) use date_time::validate_date_time;

cyclonedx-bom/src/external_models/normalized_string.rs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* SPDX-License-Identifier: Apache-2.0
1717
*/
1818

19-
use crate::validation::{Validate, ValidationContext, ValidationResult};
19+
use crate::validation::ValidationError;
2020
use std::fmt::Display;
2121
use std::ops::Deref;
2222

@@ -53,6 +53,12 @@ impl Deref for NormalizedString {
5353
}
5454
}
5555

56+
impl AsRef<NormalizedString> for NormalizedString {
57+
fn as_ref(&self) -> &NormalizedString {
58+
self
59+
}
60+
}
61+
5662
impl AsRef<str> for NormalizedString {
5763
fn as_ref(&self) -> &str {
5864
&self.0
@@ -65,21 +71,21 @@ impl Display for NormalizedString {
6571
}
6672
}
6773

68-
impl Validate for NormalizedString {
69-
fn validate_with_context(&self, context: ValidationContext) -> ValidationResult {
70-
if self.0.contains("\r\n")
71-
|| self.0.contains('\r')
72-
|| self.0.contains('\n')
73-
|| self.0.contains('\t')
74-
{
75-
return ValidationResult::failure(
76-
"NormalizedString contains invalid characters \\r \\n \\t or \\r\\n",
77-
context,
78-
);
79-
}
80-
81-
ValidationResult::Passed
74+
/// Validates a [`NormalizedString`].
75+
pub fn validate_normalized_string(
76+
normalized_string: &NormalizedString,
77+
) -> Result<(), ValidationError> {
78+
if normalized_string.contains("\r\n")
79+
|| normalized_string.contains('\r')
80+
|| normalized_string.contains('\n')
81+
|| normalized_string.contains('\t')
82+
{
83+
return Err(ValidationError::new(
84+
"NormalizedString contains invalid characters \\r \\n \\t or \\r\\n",
85+
));
8286
}
87+
88+
Ok(())
8389
}
8490

8591
#[cfg(test)]
@@ -105,21 +111,18 @@ mod test {
105111

106112
#[test]
107113
fn it_should_pass_validation() {
108-
let validation_result = NormalizedString("no_whitespace".to_string()).validate();
109-
110-
assert_eq!(validation_result, ValidationResult::Passed);
114+
assert!(validate_normalized_string(&NormalizedString("no_whitespace".to_string())).is_ok());
111115
}
112116

113117
#[test]
114118
fn it_should_fail_validation() {
115-
let validation_result = NormalizedString("spaces and\ttabs".to_string()).validate();
119+
let result = validate_normalized_string(&NormalizedString("spaces and\ttabs".to_string()));
116120

117121
assert_eq!(
118-
validation_result,
119-
ValidationResult::failure(
122+
result,
123+
Err(ValidationError::new(
120124
"NormalizedString contains invalid characters \\r \\n \\t or \\r\\n",
121-
ValidationContext::default()
122-
)
125+
))
123126
);
124127
}
125128
}

cyclonedx-bom/src/external_models/spdx.rs

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::convert::TryFrom;
2121
use spdx::{Expression, ParseMode};
2222
use thiserror::Error;
2323

24-
use crate::validation::{Validate, ValidationResult};
24+
use crate::validation::ValidationError;
2525

2626
/// An identifier for a single, specific license
2727
///
@@ -80,15 +80,10 @@ impl ToString for SpdxIdentifier {
8080
}
8181
}
8282

83-
impl Validate for SpdxIdentifier {
84-
fn validate_with_context(
85-
&self,
86-
context: crate::validation::ValidationContext,
87-
) -> ValidationResult {
88-
match Self::try_from(self.0.clone()) {
89-
Ok(_) => ValidationResult::Passed,
90-
Err(_) => ValidationResult::failure("SPDX identifier is not valid", context),
91-
}
83+
pub fn validate_spdx_identifier(identifier: &SpdxIdentifier) -> Result<(), ValidationError> {
84+
match SpdxIdentifier::try_from(identifier.0.to_string()) {
85+
Err(_error) => Err(ValidationError::new("SPDX identifier is not valid")),
86+
_ => Ok(()),
9287
}
9388
}
9489

@@ -175,16 +170,11 @@ impl ToString for SpdxExpression {
175170
}
176171
}
177172

178-
impl Validate for SpdxExpression {
179-
fn validate_with_context(
180-
&self,
181-
context: crate::validation::ValidationContext,
182-
) -> ValidationResult {
183-
match SpdxExpression::try_from(self.0.clone()) {
184-
Ok(_) => ValidationResult::Passed,
185-
Err(_) => ValidationResult::failure("SPDX expression is not valid", context),
186-
}
173+
pub fn validate_spdx_expression(expression: &SpdxExpression) -> Result<(), ValidationError> {
174+
if Expression::parse(&expression.0).is_err() {
175+
return Err(ValidationError::new("SPDX expression is not valid"));
187176
}
177+
Ok(())
188178
}
189179

190180
#[derive(Debug, Error, PartialEq, Eq)]
@@ -198,8 +188,6 @@ pub enum SpdxExpressionError {
198188

199189
#[cfg(test)]
200190
mod test {
201-
use crate::validation::{ValidationContext, ValidationResult};
202-
203191
use super::*;
204192
use pretty_assertions::assert_eq;
205193

@@ -247,18 +235,19 @@ mod test {
247235

248236
#[test]
249237
fn valid_spdx_identifiers_should_pass_validation() {
250-
let validation_result = SpdxIdentifier("MIT".to_string()).validate();
238+
let validaton_result = validate_spdx_identifier(&SpdxIdentifier("MIT".to_string()));
251239

252-
assert_eq!(validation_result, ValidationResult::Passed);
240+
assert!(validaton_result.is_ok());
253241
}
254242

255243
#[test]
256244
fn invalid_spdx_identifiers_should_fail_validation() {
257-
let validation_result = SpdxIdentifier("MIT OR Apache-2.0".to_string()).validate();
245+
let validation_result =
246+
validate_spdx_identifier(&SpdxIdentifier("MIT OR Apache-2.0".to_string()));
258247

259248
assert_eq!(
260249
validation_result,
261-
ValidationResult::failure("SPDX identifier is not valid", ValidationContext::default()),
250+
Err("SPDX identifier is not valid".into()),
262251
);
263252
}
264253

@@ -288,18 +277,20 @@ mod test {
288277

289278
#[test]
290279
fn valid_spdx_expressions_should_pass_validation() {
291-
let validation_result = SpdxExpression("MIT OR Apache-2.0".to_string()).validate();
280+
let validation_result =
281+
validate_spdx_expression(&SpdxExpression("MIT OR Apache-2.0".to_string()));
292282

293-
assert_eq!(validation_result, ValidationResult::Passed);
283+
assert!(validation_result.is_ok());
294284
}
295285

296286
#[test]
297287
fn invalid_spdx_expressions_should_fail_validation() {
298-
let validation_result = SpdxExpression("not a real license".to_string()).validate();
288+
let validation_result =
289+
validate_spdx_expression(&SpdxExpression("not a real license".to_string()));
299290

300291
assert_eq!(
301292
validation_result,
302-
ValidationResult::failure("SPDX expression is not valid", ValidationContext::default())
293+
Err("SPDX expression is not valid".into()),
303294
);
304295
}
305296
}

0 commit comments

Comments
 (0)