Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions csaf-rs/src/converter/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,58 @@ fn visit_product_tree_value(value: &mut Value, callback: &mut impl FnMut(&mut Ma
_ => {},
}
}

#[derive(Debug)]
pub(super) enum BranchTraversalWarning {
InvalidProductTree,
InvalidBranches,
}

/// Runs `callback` for every branch below `/product_tree/branches`, depth first,
/// passing the branch object and its JSON pointer.
///
/// Returns `Ok(())` both when branches were traversed and when there is legitimately
/// nothing to do: `product_tree` and `branches` are optional in CSAF 2.0, so their
/// absence is a valid document.
///
/// `Err` is returned only when one of those nodes exists but deviates from the CSAF 2.0 schema.
pub(super) fn for_each_branch_mut(
doc: &mut loose::Document,
mut callback: impl FnMut(&mut Map<String, Value>, &str),
) -> Result<(), BranchTraversalWarning> {
let Some(product_tree) = doc.v20_view_mut().product_tree_mut() else {
return Ok(());
};

let product_tree = product_tree
.caution_as_object_mut()
.map_err(|_| BranchTraversalWarning::InvalidProductTree)?;

let Some(branches) = product_tree.get_mut("branches") else {
return Ok(());
};

let Value::Array(branches) = branches else {
return Err(BranchTraversalWarning::InvalidBranches);
};

visit_branches_mut(branches, "/product_tree/branches", &mut callback);

Ok(())
}

fn visit_branches_mut(branches: &mut [Value], path: &str, callback: &mut impl FnMut(&mut Map<String, Value>, &str)) {
for (index, branch) in branches.iter_mut().enumerate() {
let Value::Object(branch) = branch else {
continue;
};

let branch_path = format!("{path}/{index}");

callback(branch, &branch_path);

if let Some(Value::Array(children)) = branch.get_mut("branches") {
visit_branches_mut(children, &format!("{branch_path}/branches"), callback);
}
}
}
14 changes: 14 additions & 0 deletions csaf-rs/src/converter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod deduplicate_x_generic_uris;
mod helpers;
mod lowercase_hash_value;
mod move_purl_into_purls_array;
mod replace_legacy_branch_category;
mod set_csaf_21_schema;
mod set_initial_release_date;
mod settings;
Expand All @@ -36,6 +37,7 @@ pub fn convert(raw: serde_json::Value, settings: &Settings, diagnostics: &mut dy
const CONVERSIONS: &[&dyn Convert] = &[
// conversions that work on leaf nodes
&lowercase_hash_value::LowercaseHashValue,
&replace_legacy_branch_category::ReplaceLegacyBranchCategory,
&set_csaf_21_schema::SetCsaf21Schema,
&update_csaf_version::UpdateCsafVersion,
&update_tlp_label::UpdateTlpLabel,
Expand Down Expand Up @@ -71,6 +73,9 @@ pub enum Warning {
InvalidCsafVersion,
InvalidTlpLabel,
TlpIsMissingLabel,
LegacyBranchCategoryReplaced,
InvalidProductTree,
InvalidBranches,
PublisherCategoryOther,
InconsistentInitialReleaseDate { old: String, new: String },
PublisherContactOpenPgpKeyUrlRequiresEmail { url: String },
Expand All @@ -90,6 +95,15 @@ impl fmt::Display for Warning {
Warning::InvalidCsafVersion => f.write_str("CSAF version node contained an invalid value"),
Warning::InvalidTlpLabel => f.write_str("TLP label contained an invalid value"),
Warning::TlpIsMissingLabel => f.write_str("TLP node was present but did not contain the `label` property"),
Warning::LegacyBranchCategoryReplaced => f.write_str(
"Branch category 'legacy' does not exist in CSAF 2.1 \
and was replaced with 'product_name'. This replacement \
may be incorrect or another category may be a better fit. \
Please review the converted document to ensure the \
conversion is correct and not misleading.",
),
Warning::InvalidProductTree => f.write_str("Skipped branch traversal: product_tree is not a JSON object"),
Warning::InvalidBranches => f.write_str("Skipped branch traversal: branches is not a JSON array"),
Warning::PublisherCategoryOther => f.write_str(
"Some publishers previously categorized as 'other' \
have been regrouped into the new 'multiplier' category in CSAF 2.1. \
Expand Down
Loading
Loading