use crate::csaf_traits::{CsafTrait, ProductStatusTrait, RemediationTrait, VulnerabilityTrait, WithOptionalProductIds};
use crate::schema::csaf2_1::schema::CategoryOfTheRemediation;
use crate::validation::ValidationError;
use std::collections::HashSet;
fn create_missing_remediation_error(
vulnerability_index: usize,
status_group_name: &str,
status_group_product_index: usize,
product_id: &str,
) -> ValidationError {
ValidationError {
message: format!(
"Missing at least a remediation of category 'none_available' or 'no_fix_planned' for product ID '{product_id}' in product status group '{status_group_name}'",
),
instance_path: format!(
"/vulnerabilities/{vulnerability_index}/product_status/{status_group_name}/{status_group_product_index}"
),
}
}
fn check_product_status_group_for_missing_remediations<'a>(
errors: &mut Option<Vec<ValidationError>>,
status_group_product_ids: impl Iterator<Item = &'a str>,
remediation_product_ids: &HashSet<String>,
vulnerability_index: usize,
status_group_name: &str,
) {
for (sg_p_i, product_id) in status_group_product_ids.enumerate() {
if !remediation_product_ids.contains(product_id) {
errors.get_or_insert_default().push(create_missing_remediation_error(
vulnerability_index,
status_group_name,
sg_p_i,
product_id,
));
}
}
}
pub fn test_6_2_02_missing_remediations(doc: &impl CsafTrait) -> Result<(), Vec<ValidationError>> {
let mut errors: Option<Vec<ValidationError>> = None;
for (v_i, vuln) in doc.get_vulnerabilities().iter().enumerate() {
if let Some(product_status) = vuln.get_product_status() {
if product_status.get_first_affected().is_none()
&& product_status.get_known_affected().is_none()
&& product_status.get_last_affected().is_none()
&& product_status.get_under_investigation().is_none()
{
continue;
}
let mut remediation_product_ids = HashSet::<String>::new();
for remediation in vuln.get_remediations() {
if (remediation.get_category() == CategoryOfTheRemediation::NoneAvailable
|| remediation.get_category() == CategoryOfTheRemediation::NoFixPlanned)
&& let Some(product_ids) = remediation.get_product_ids()
{
for product_id in product_ids {
remediation_product_ids.insert(product_id.to_owned());
}
}
}
if let Some(first_affected) = product_status.get_first_affected() {
check_product_status_group_for_missing_remediations(
&mut errors,
first_affected,
&remediation_product_ids,
v_i,
"first_affected",
);
}
if let Some(known_affected) = product_status.get_known_affected() {
check_product_status_group_for_missing_remediations(
&mut errors,
known_affected,
&remediation_product_ids,
v_i,
"known_affected",
);
}
if let Some(last_affected) = product_status.get_last_affected() {
check_product_status_group_for_missing_remediations(
&mut errors,
last_affected,
&remediation_product_ids,
v_i,
"last_affected",
);
}
if let Some(under_investigation) = product_status.get_under_investigation() {
check_product_status_group_for_missing_remediations(
&mut errors,
under_investigation,
&remediation_product_ids,
v_i,
"under_investigation",
);
}
}
}
errors.map_or(Ok(()), Err)
}
crate::test_validation::impl_validator!(ValidatorForTest6_2_2, test_6_2_02_missing_remediations);
#[cfg(test)]
mod tests {
use super::*;
use crate::csaf2_0::testcases::TESTS_2_0;
use crate::csaf2_1::testcases::TESTS_2_1;
#[test]
fn test_test_6_2_02() {
let case_01 = Err(vec![create_missing_remediation_error(
0,
"last_affected",
0,
"CSAFPID-9080700",
)]);
TESTS_2_0.test_6_2_2.expect(case_01.clone());
TESTS_2_1.test_6_2_2.expect(case_01);
}
}