1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use crate::csaf::types::purl::csaf_purl::CsafPurl::{Invalid, Valid};
use crate::csaf_traits::{CsafTrait, ProductIdentificationHelperTrait, ProductTrait, ProductTreeTrait};
use crate::validation::ValidationError;
use std::collections::HashMap;
fn create_purl_consistency_error(path: &str, index: usize) -> ValidationError {
ValidationError {
message: String::from("PURLs within the same product_identification_helper must only differ in qualifiers"),
instance_path: format!("{path}/product_identification_helper/purls/{index}"),
}
}
/// 6.1.42 PURL Consistency
/// Checks the consistency of PURLs within the same product_identification_helper. PURLs must only differ in qualifiers.
pub fn test_6_1_42_purl_consistency(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let mut errors: Option<Vec<ValidationError>> = None;
if let Some(product_tree) = doc.get_product_tree() {
product_tree.visit_all_products(&mut |product, path| {
if let Some(helper) = product.get_product_identification_helper()
&& let Some(purls) = helper.get_purls()
{
// break early if there are 0 or 1 PURLs, as consistency is not an issue
if purls.len() <= 1 {
return;
}
let mut bases_map: Option<HashMap<String, Vec<usize>>> = None;
for (i, purl) in purls.into_iter().enumerate() {
// check purl validation result
match purl {
Valid(p) => {
bases_map
// create hashmap if it does not exist
.get_or_insert_default()
// create entry for base if it does not exist
.entry(p.base_without_qualifiers().to_owned())
// create vec if it does not exist
.or_default()
// push path index into vec
.push(i);
},
Invalid(_) => {
// ToDo #409 create precondition failed warning
continue;
},
};
}
// if there were any valid purls
if let Some(bases) = bases_map {
// Collect values and sort by length descending
let mut sorted_values: Vec<Vec<usize>> = bases.into_values().collect();
// Sort by group size descending, then by first index ascending for determinism
sorted_values.sort_by(|a, b| b.len().cmp(&a.len()).then_with(|| a[0].cmp(&b[0])));
// If there is more than one group, the PURLs differ in more than qualifiers.
// Skip the first (largest) group and report errors for all indices in the remaining groups.
for group in sorted_values.iter().skip(1) {
for &i in group {
errors
.get_or_insert_default()
.push(create_purl_consistency_error(path, i));
}
}
}
}
});
}
errors.map_or(Ok(()), Err)
}
crate::test_validation::impl_validator!(csaf2_1, ValidatorForTest6_1_42, test_6_1_42_purl_consistency);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_1::testcases::TESTS_2_1;
#[test]
fn test_test_6_1_42() {
TESTS_2_1.test_6_1_42.expect(
Err(vec![create_purl_consistency_error(
"/product_tree/full_product_names/0",
1,
)]),
Err(vec![create_purl_consistency_error(
"/product_tree/branches/0/branches/0/branches/0/product",
2,
)]),
Err(vec![create_purl_consistency_error(
"/product_tree/full_product_names/0",
1,
)]),
Ok(()),
Ok(()),
);
}
}