From f8df20afb6631e1b6a9f5057531bde10e9211cb6 Mon Sep 17 00:00:00 2001 From: Jens Reimann Date: Fri, 20 Jun 2025 17:30:44 +0200 Subject: [PATCH] chore: make clippy happy, and have a CI ensuring it stays that way --- .github/workflows/build.yml | 4 +- csaf-rs/build.rs | 12 +- .../csaf/csaf2_0/getter_implementations.rs | 127 ++++++++++++------ csaf-rs/src/csaf/csaf2_0/loader.rs | 5 +- csaf-rs/src/csaf/csaf2_0/schema.rs | 4 + .../csaf/csaf2_1/getter_implementations.rs | 112 ++++++++++----- csaf-rs/src/csaf/csaf2_1/loader.rs | 19 ++- csaf-rs/src/csaf/csaf2_1/schema.rs | 4 + csaf-rs/src/csaf/csaf2_1/ssvc_dp_schema.rs | 4 + csaf-rs/src/csaf/csaf2_1/ssvc_schema.rs | 4 + csaf-rs/src/csaf/getter_traits.rs | 47 ++++--- csaf-rs/src/csaf/helpers.rs | 91 ++++++++----- csaf-rs/src/csaf/test_helper.rs | 100 ++++++++------ csaf-rs/src/csaf/validation.rs | 5 +- csaf-rs/src/csaf/validations/test_6_1_06.rs | 4 +- csaf-rs/src/csaf/validations/test_6_1_07.rs | 18 ++- csaf-rs/src/csaf/validations/test_6_1_39.rs | 39 +++--- csaf-rs/src/csaf/validations/test_6_1_40.rs | 62 ++++++--- 18 files changed, 423 insertions(+), 238 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48c90b4..bdf638e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ on: - v*.** merge_group: pull_request: - types: [opened, synchronize, reopened] + types: [ opened, synchronize, reopened ] env: CARGO_TERM_COLOR: always @@ -44,6 +44,8 @@ jobs: - name: Publish dry-run run: cargo publish --dry-run -p csaf-rs --target=${{ matrix.target.arch }} --verbose if: ${{ matrix.target.skip-test != true }} + - name: Clippy + run: cargo clippy --target=${{ matrix.target.arch }} --tests --bins --all -- -D warnings - name: Archive csaf-validator (${{ matrix.target.arch }}) uses: actions/upload-artifact@v4 with: diff --git a/csaf-rs/build.rs b/csaf-rs/build.rs index 732fb06..b4f5957 100644 --- a/csaf-rs/build.rs +++ b/csaf-rs/build.rs @@ -58,7 +58,7 @@ fn main() -> Result<(), BuildError> { } fn build(input: &str, output: &str, no_date_time: bool) -> Result<(), BuildError> { - let content = fs::read_to_string(&input)?; + let content = fs::read_to_string(input)?; let mut schema_value = serde_json::from_str(&content)?; if no_date_time { // Recursively search for "format": "date-time" and remove this format @@ -74,7 +74,15 @@ fn build(input: &str, output: &str, no_date_time: bool) -> Result<(), BuildError ); type_space.add_root_schema(schema)?; - let content = prettyplease::unparse(&syn::parse2::(type_space.to_stream())?); + let mut content = prettyplease::unparse(&syn::parse2::(type_space.to_stream())?); + content.insert_str( + 0, + r#" +#![allow(clippy::clone_on_copy)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::len_zero)] +"#, + ); let mut out_file = Path::new("src").to_path_buf(); out_file.push(output); diff --git a/csaf-rs/src/csaf/csaf2_0/getter_implementations.rs b/csaf-rs/src/csaf/csaf2_0/getter_implementations.rs index 7135e96..64a32b7 100644 --- a/csaf-rs/src/csaf/csaf2_0/getter_implementations.rs +++ b/csaf-rs/src/csaf/csaf2_0/getter_implementations.rs @@ -1,15 +1,32 @@ -use crate::csaf::csaf2_0::schema::{Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, DocumentGenerator, DocumentLevelMetaData, DocumentStatus, Flag, FullProductNameT, HelperToIdentifyTheProduct, Id, Involvement, LabelOfTlp, Note, ProductGroup, ProductStatus, ProductTree, Relationship, Remediation, Revision, RulesForSharingDocument, Score, Threat, Tracking, TrafficLightProtocolTlp, Vulnerability}; -use crate::csaf::csaf2_1::schema::{CategoryOfTheRemediation as Remediation21, DocumentStatus as Status21, Epss, LabelOfTlp as Tlp21}; -use crate::csaf::getter_traits::{BranchTrait, CsafTrait, DistributionTrait, DocumentTrait, FlagTrait, ProductTrait, GeneratorTrait, InvolvementTrait, MetricTrait, ProductGroupTrait, ProductIdentificationHelperTrait, ProductStatusTrait, ProductTreeTrait, RelationshipTrait, RemediationTrait, RevisionTrait, SharingGroupTrait, ThreatTrait, TlpTrait, TrackingTrait, VulnerabilityTrait, ContentTrait, VulnerabilityIdTrait, NoteTrait, WithGroupIds}; -use std::ops::Deref; -use serde::de::Error; -use serde_json::{Map, Value}; +use crate::csaf::csaf2_0::schema::{ + Branch, CategoryOfTheRemediation, CommonSecurityAdvisoryFramework, DocumentGenerator, + DocumentLevelMetaData, DocumentStatus, Flag, FullProductNameT, HelperToIdentifyTheProduct, Id, + Involvement, LabelOfTlp, Note, ProductGroup, ProductStatus, ProductTree, Relationship, + Remediation, Revision, RulesForSharingDocument, Score, Threat, Tracking, + TrafficLightProtocolTlp, Vulnerability, +}; +use crate::csaf::csaf2_1::schema::{ + CategoryOfTheRemediation as Remediation21, DocumentStatus as Status21, Epss, + LabelOfTlp as Tlp21, +}; use crate::csaf::csaf2_1::ssvc_schema::SsvcV1; +use crate::csaf::getter_traits::{ + BranchTrait, ContentTrait, CsafTrait, DistributionTrait, DocumentTrait, FlagTrait, + GeneratorTrait, InvolvementTrait, MetricTrait, NoteTrait, ProductGroupTrait, + ProductIdentificationHelperTrait, ProductStatusTrait, ProductTrait, ProductTreeTrait, + RelationshipTrait, RemediationTrait, RevisionTrait, SharingGroupTrait, ThreatTrait, TlpTrait, + TrackingTrait, VulnerabilityIdTrait, VulnerabilityTrait, WithGroupIds, +}; use crate::csaf::validation::ValidationError; +use serde::de::Error; +use serde_json::{Map, Value}; +use std::ops::Deref; impl WithGroupIds for Remediation { - fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + fn get_group_ids(&self) -> Option + '_> { + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } @@ -35,7 +52,9 @@ impl RemediationTrait for Remediation { } fn get_product_ids(&self) -> Option + '_> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.product_ids + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_date(&self) -> &Option { @@ -45,11 +64,15 @@ impl RemediationTrait for Remediation { impl ProductStatusTrait for ProductStatus { fn get_first_affected(&self) -> Option + '_> { - self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.first_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_first_fixed(&self) -> Option + '_> { - self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.first_fixed + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_fixed(&self) -> Option + '_> { @@ -57,27 +80,37 @@ impl ProductStatusTrait for ProductStatus { } fn get_known_affected(&self) -> Option + '_> { - self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.known_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_known_not_affected(&self) -> Option + '_> { - self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.known_not_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_last_affected(&self) -> Option + '_> { - self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.last_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_recommended(&self) -> Option + '_> { - self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.recommended + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_under_investigation(&self) -> Option + '_> { - self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.under_investigation + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } /// Not specified for CSAF 2.0, so `None` - fn get_unknown(&self) -> Option + '_> { + fn get_unknown(&self) -> Option + '_> { None::> } } @@ -104,7 +137,9 @@ impl ContentTrait for Score { } fn get_ssvc_v1(&self) -> Result { - Err(serde_json::Error::custom("SSVC metrics are not implemented in CSAF 2.0")) + Err(serde_json::Error::custom( + "SSVC metrics are not implemented in CSAF 2.0", + )) } fn get_cvss_v2(&self) -> Option<&Map> { @@ -141,13 +176,17 @@ impl ContentTrait for Score { impl WithGroupIds for Threat { fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } impl ThreatTrait for Threat { fn get_product_ids(&self) -> Option + '_> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.product_ids + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_date(&self) -> &Option { @@ -199,15 +238,15 @@ impl VulnerabilityTrait for Vulnerability { } fn get_cve(&self) -> Option<&String> { - self.cve.as_ref().map(|x| x.deref()) + self.cve.as_deref() } - + fn get_ids(&self) -> &Option> { &self.ids } fn get_notes(&self) -> Option<&Vec> { - self.notes.as_ref().map(|x| x.deref()) + self.notes.as_deref() } } @@ -222,8 +261,10 @@ impl VulnerabilityIdTrait for Id { } impl WithGroupIds for Flag { - fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + fn get_group_ids(&self) -> Option + '_> { + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } @@ -275,15 +316,16 @@ impl DocumentTrait for DocumentLevelMetaData { fn get_distribution_21(&self) -> Result<&Self::DistributionType, ValidationError> { match self.distribution.as_ref() { None => Err(ValidationError { - message: "CSAF 2.1 requires the distribution property, but it is not set.".to_string(), - instance_path: "/document/distribution".to_string() + message: "CSAF 2.1 requires the distribution property, but it is not set." + .to_string(), + instance_path: "/document/distribution".to_string(), }), - Some(distribution) => Ok(distribution) + Some(distribution) => Ok(distribution), } } fn get_notes(&self) -> Option<&Vec> { - self.notes.as_ref().map(|x| x.deref()) + self.notes.as_deref() } } @@ -305,15 +347,15 @@ impl DistributionTrait for RulesForSharingDocument { match self.tlp.as_ref() { None => Err(ValidationError { message: "CSAF 2.1 requires the TLP property, but it is not set.".to_string(), - instance_path: "/document/distribution/sharing_group/tlp".to_string() + instance_path: "/document/distribution/sharing_group/tlp".to_string(), }), - Some(tlp) => Ok(tlp) + Some(tlp) => Ok(tlp), } } } impl WithGroupIds for Note { - fn get_group_ids(&self) -> Option + '_> { + fn get_group_ids(&self) -> Option + '_> { None::> } } @@ -408,7 +450,7 @@ impl ProductTreeTrait for ProductTree { type FullProductNameType = FullProductNameT; fn get_branches(&self) -> Option<&Vec> { - self.branches.as_ref().map(|branches| branches.deref()) + self.branches.as_deref() } fn get_product_groups(&self) -> &Vec { @@ -423,14 +465,17 @@ impl ProductTreeTrait for ProductTree { &self.full_product_names } - fn visit_all_products(&self, callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>) -> Result<(), ValidationError> { + fn visit_all_products( + &self, + callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>, + ) -> Result<(), ValidationError> { self.visit_all_products_generic(callback) } } impl BranchTrait for Branch { fn get_branches(&self) -> Option<&Vec> { - self.branches.as_ref().map(|branches| branches.deref()) + self.branches.as_deref() } fn get_product(&self) -> &Option { @@ -476,14 +521,18 @@ impl ProductTrait for FullProductNameT { impl ProductIdentificationHelperTrait for HelperToIdentifyTheProduct { fn get_purls(&self) -> Option<&[String]> { - self.purl.as_ref().map(|purl| std::slice::from_ref(purl)) + self.purl.as_ref().map(std::slice::from_ref) } fn get_model_numbers(&self) -> Option + '_> { - self.model_numbers.as_ref().map(|v| v.iter().map(|x| x.deref())) + self.model_numbers + .as_ref() + .map(|v| v.iter().map(|x| x.deref())) } fn get_serial_numbers(&self) -> Option + '_> { - self.serial_numbers.as_ref().map(|v| v.iter().map(|x| x.deref())) + self.serial_numbers + .as_ref() + .map(|v| v.iter().map(|x| x.deref())) } -} \ No newline at end of file +} diff --git a/csaf-rs/src/csaf/csaf2_0/loader.rs b/csaf-rs/src/csaf/csaf2_0/loader.rs index cef5102..0097422 100644 --- a/csaf-rs/src/csaf/csaf2_0/loader.rs +++ b/csaf-rs/src/csaf/csaf2_0/loader.rs @@ -7,10 +7,7 @@ pub fn load_document(path: &str) -> std::io::Result Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } @@ -17,7 +31,9 @@ impl RemediationTrait for Remediation { } fn get_product_ids(&self) -> Option + '_> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.product_ids + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_date(&self) -> &Option { @@ -27,11 +43,15 @@ impl RemediationTrait for Remediation { impl ProductStatusTrait for ProductStatus { fn get_first_affected(&self) -> Option + '_> { - self.first_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.first_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_first_fixed(&self) -> Option + '_> { - self.first_fixed.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.first_fixed + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_fixed(&self) -> Option + '_> { @@ -39,27 +59,39 @@ impl ProductStatusTrait for ProductStatus { } fn get_known_affected(&self) -> Option + '_> { - self.known_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.known_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_known_not_affected(&self) -> Option + '_> { - self.known_not_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.known_not_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_last_affected(&self) -> Option + '_> { - self.last_affected.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.last_affected + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_recommended(&self) -> Option + '_> { - self.recommended.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.recommended + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_under_investigation(&self) -> Option + '_> { - self.under_investigation.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.under_investigation + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } - fn get_unknown(&self) -> Option + '_> { - self.unknown.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + fn get_unknown(&self) -> Option + '_> { + self.unknown + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } } @@ -120,21 +152,24 @@ impl ContentTrait for Content { fn get_content_json_path(&self, vulnerability_idx: usize, metric_idx: usize) -> String { format!( "/vulnerabilities/{}/metrics/{}/content", - vulnerability_idx, - metric_idx, + vulnerability_idx, metric_idx, ) } } impl WithGroupIds for Threat { fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } impl ThreatTrait for Threat { fn get_product_ids(&self) -> Option + '_> { - self.product_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + self.product_ids + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } fn get_date(&self) -> &Option { @@ -185,7 +220,7 @@ impl VulnerabilityTrait for Vulnerability { } fn get_cve(&self) -> Option<&String> { - self.cve.as_ref().map(|x| x.deref()) + self.cve.as_deref() } fn get_ids(&self) -> &Option> { @@ -193,7 +228,7 @@ impl VulnerabilityTrait for Vulnerability { } fn get_notes(&self) -> Option<&Vec> { - self.notes.as_ref().map(|x| x.deref()) + self.notes.as_deref() } } @@ -208,8 +243,10 @@ impl VulnerabilityIdTrait for Id { } impl WithGroupIds for Flag { - fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|g| (*g).iter().map(|x| x.deref())) + fn get_group_ids(&self) -> Option + '_> { + self.group_ids + .as_ref() + .map(|g| (*g).iter().map(|x| x.deref())) } } @@ -263,7 +300,7 @@ impl DocumentTrait for DocumentLevelMetaData { } fn get_notes(&self) -> Option<&Vec> { - self.notes.as_ref().map(|x| x.deref()) + self.notes.as_deref() } } @@ -287,8 +324,10 @@ impl DistributionTrait for RulesForSharingDocument { } impl WithGroupIds for Note { - fn get_group_ids(&self) -> Option + '_> { - self.group_ids.as_ref().map(|p| (*p).iter().map(|x| x.deref())) + fn get_group_ids(&self) -> Option + '_> { + self.group_ids + .as_ref() + .map(|p| (*p).iter().map(|x| x.deref())) } } @@ -300,7 +339,7 @@ impl SharingGroupTrait for SharingGroup { } fn get_name(&self) -> Option<&String> { - self.name.as_ref().map(|x| x.deref()) + self.name.as_deref() } } @@ -364,7 +403,7 @@ impl ProductTreeTrait for ProductTree { type FullProductNameType = FullProductNameT; fn get_branches(&self) -> Option<&Vec> { - self.branches.as_ref().map(|branches| branches.deref()) + self.branches.as_deref() } fn get_product_groups(&self) -> &Vec { @@ -379,14 +418,17 @@ impl ProductTreeTrait for ProductTree { &self.full_product_names } - fn visit_all_products(&self, callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>) -> Result<(), ValidationError> { + fn visit_all_products( + &self, + callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>, + ) -> Result<(), ValidationError> { self.visit_all_products_generic(callback) } } impl BranchTrait for Branch { fn get_branches(&self) -> Option<&Vec> { - self.branches.as_ref().map(|branches| branches.deref()) + self.branches.as_deref() } fn get_product(&self) -> &Option { @@ -432,14 +474,18 @@ impl ProductTrait for FullProductNameT { impl ProductIdentificationHelperTrait for HelperToIdentifyTheProduct { fn get_purls(&self) -> Option<&[String]> { - self.purls.as_ref().map(|v| v.as_slice()) + self.purls.as_deref() } fn get_model_numbers(&self) -> Option + '_> { - self.model_numbers.as_ref().map(|v| v.iter().map(|x| x.deref())) + self.model_numbers + .as_ref() + .map(|v| v.iter().map(|x| x.deref())) } fn get_serial_numbers(&self) -> Option + '_> { - self.serial_numbers.as_ref().map(|v| v.iter().map(|x| x.deref())) + self.serial_numbers + .as_ref() + .map(|v| v.iter().map(|x| x.deref())) } } diff --git a/csaf-rs/src/csaf/csaf2_1/loader.rs b/csaf-rs/src/csaf/csaf2_1/loader.rs index a718f5b..2411191 100644 --- a/csaf-rs/src/csaf/csaf2_1/loader.rs +++ b/csaf-rs/src/csaf/csaf2_1/loader.rs @@ -1,5 +1,5 @@ -use std::{fs::File, io::BufReader}; use super::schema::CommonSecurityAdvisoryFramework; +use std::{fs::File, io::BufReader}; pub fn load_document(path: &str) -> std::io::Result { println!("Trying to load document {}", path); @@ -7,17 +7,18 @@ pub fn load_document(path: &str) -> std::io::Result CommonSecurityAdvisoryFramework { let now = chrono::Utc::now().to_string(); @@ -27,12 +28,8 @@ mod tests { .csaf_version("2.1") .distribution( RulesForSharingDocument::builder() - .tlp( - TrafficLightProtocolTlp::builder() - .label(LabelOfTlp::Clear) - ) + .tlp(TrafficLightProtocolTlp::builder().label(LabelOfTlp::Clear)), ) - .publisher( Publisher::builder() .category(CategoryOfPublisher::Coordinator) diff --git a/csaf-rs/src/csaf/csaf2_1/schema.rs b/csaf-rs/src/csaf/csaf2_1/schema.rs index a062bd1..f9de0b4 100644 --- a/csaf-rs/src/csaf/csaf2_1/schema.rs +++ b/csaf-rs/src/csaf/csaf2_1/schema.rs @@ -1,3 +1,7 @@ + +#![allow(clippy::clone_on_copy)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::len_zero)] /// Error types. pub mod error { /// Error from a TryFrom or FromStr implementation. diff --git a/csaf-rs/src/csaf/csaf2_1/ssvc_dp_schema.rs b/csaf-rs/src/csaf/csaf2_1/ssvc_dp_schema.rs index b5f03e9..96b91ad 100644 --- a/csaf-rs/src/csaf/csaf2_1/ssvc_dp_schema.rs +++ b/csaf-rs/src/csaf/csaf2_1/ssvc_dp_schema.rs @@ -1,3 +1,7 @@ + +#![allow(clippy::clone_on_copy)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::len_zero)] /// Error types. pub mod error { /// Error from a TryFrom or FromStr implementation. diff --git a/csaf-rs/src/csaf/csaf2_1/ssvc_schema.rs b/csaf-rs/src/csaf/csaf2_1/ssvc_schema.rs index f409168..cf920ea 100644 --- a/csaf-rs/src/csaf/csaf2_1/ssvc_schema.rs +++ b/csaf-rs/src/csaf/csaf2_1/ssvc_schema.rs @@ -1,3 +1,7 @@ + +#![allow(clippy::clone_on_copy)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::len_zero)] /// Error types. pub mod error { /// Error from a TryFrom or FromStr implementation. diff --git a/csaf-rs/src/csaf/getter_traits.rs b/csaf-rs/src/csaf/getter_traits.rs index e42e026..1876fca 100644 --- a/csaf-rs/src/csaf/getter_traits.rs +++ b/csaf-rs/src/csaf/getter_traits.rs @@ -1,8 +1,8 @@ -use std::collections::{BTreeSet, HashSet}; use crate::csaf::csaf2_1::schema::{CategoryOfTheRemediation, DocumentStatus, Epss, LabelOfTlp}; use crate::csaf::csaf2_1::ssvc_schema::SsvcV1; use crate::csaf::helpers::resolve_product_groups; use crate::csaf::validation::ValidationError; +use std::collections::{BTreeSet, HashSet}; /// Trait representing an abstract Common Security Advisory Framework (CSAF) document. /// @@ -132,7 +132,6 @@ pub trait RevisionTrait { fn get_summary(&self) -> &String; } - /// Trait representing an abstract vulnerability in a CSAF document. /// /// The `VulnerabilityTrait` defines the structure of a vulnerability and includes @@ -283,7 +282,7 @@ pub trait ProductStatusTrait { /// Returns a reference to the list of product IDs currently under investigation. fn get_under_investigation(&self) -> Option + '_>; - + /// Return a reference to the list of product IDs with unknown status. fn get_unknown(&self) -> Option + '_>; @@ -349,13 +348,13 @@ pub trait MetricTrait { fn get_products(&self) -> impl Iterator + '_; fn get_content(&self) -> &Self::ContentType; - + fn get_source(&self) -> &Option; } pub trait ContentTrait { fn has_ssvc_v1(&self) -> bool; - + fn get_ssvc_v1(&self) -> Result; fn get_cvss_v2(&self) -> Option<&serde_json::Map>; @@ -425,26 +424,26 @@ pub trait ProductTreeTrait { /// * `Err(ValidationError)` if the callback returned an error for any product fn visit_all_products_generic( &self, - callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError> + callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>, ) -> Result<(), ValidationError> { // Visit products in branches if let Some(branches) = self.get_branches().as_ref() { for (i, branch) in branches.iter().enumerate() { - branch.visit_branches_rec(&format!("/product_tree/branches/{}", i), &mut |branch: &Self::BranchType, path| { - if let Some(product_ref) = branch.get_product() { - callback(product_ref, &format!("{}/product", path))?; - } - Ok(()) - })?; + branch.visit_branches_rec( + &format!("/product_tree/branches/{}", i), + &mut |branch: &Self::BranchType, path| { + if let Some(product_ref) = branch.get_product() { + callback(product_ref, &format!("{}/product", path))?; + } + Ok(()) + }, + )?; } } // Visit full_product_names for (i, fpn) in self.get_full_product_names().iter().enumerate() { - callback( - fpn, - &format!("/product_tree/full_product_names/{}", i), - )?; + callback(fpn, &format!("/product_tree/full_product_names/{}", i))?; } // Visit relationships @@ -479,12 +478,12 @@ pub trait ProductTreeTrait { /// `visit_all_products_generic()` with the same callback. fn visit_all_products( &self, - callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError> + callback: &mut impl FnMut(&Self::FullProductNameType, &str) -> Result<(), ValidationError>, ) -> Result<(), ValidationError>; } /// Trait representing an abstract branch in a product tree. -pub trait BranchTrait : Sized { +pub trait BranchTrait: Sized { /// Returns an optional reference to the child branches of this branch. fn get_branches(&self) -> Option<&Vec>; @@ -501,13 +500,17 @@ pub trait BranchTrait : Sized { /// # Parameters /// * `path` - A string representing the current path in the branch hierarchy /// * `callback` - A mutable function that takes a reference to Self and the - /// current path string, and returns a Result + /// current path string, and returns a Result /// /// # Returns /// * `Ok(())` if the traversal completes successfully /// * `Err(ValidationError)` if the callback returns an error for any branch - fn visit_branches_rec(&self, path: &str, callback: &mut impl FnMut(&Self, &str) -> Result<(), ValidationError>) -> Result<(), ValidationError> { - callback(self, &path)?; + fn visit_branches_rec( + &self, + path: &str, + callback: &mut impl FnMut(&Self, &str) -> Result<(), ValidationError>, + ) -> Result<(), ValidationError> { + callback(self, path)?; if let Some(branches) = self.get_branches().as_ref() { for (i, branch) in branches.iter().enumerate() { branch.visit_branches_rec(&format!("{}/branches/{}", path, i), callback)?; @@ -594,4 +597,4 @@ pub trait ProductIdentificationHelperTrait { pub trait WithGroupIds { /// Returns the product group IDs associated with this vulnerability flag fn get_group_ids(&self) -> Option + '_>; -} \ No newline at end of file +} diff --git a/csaf-rs/src/csaf/helpers.rs b/csaf-rs/src/csaf/helpers.rs index 48f9aee..0e63a77 100644 --- a/csaf-rs/src/csaf/helpers.rs +++ b/csaf-rs/src/csaf/helpers.rs @@ -1,14 +1,17 @@ +use crate::csaf::csaf2_1::ssvc_dp_schema::DecisionPoint; use crate::csaf::getter_traits::{CsafTrait, ProductGroupTrait, ProductTreeTrait}; +use glob::glob; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fs; use std::ops::Deref; use std::sync::LazyLock; -use glob::glob; -use crate::csaf::csaf2_1::ssvc_dp_schema::DecisionPoint; -pub fn resolve_product_groups<'a, I>(doc: &impl CsafTrait, product_groups: I) -> Option> +pub fn resolve_product_groups<'a, I>( + doc: &impl CsafTrait, + product_groups: I, +) -> Option> where - I: IntoIterator + I: IntoIterator, { let product_groups: Vec<&String> = product_groups.into_iter().collect(); @@ -17,8 +20,11 @@ where .get_product_groups() .iter() .filter(|x| product_groups.iter().any(|g| *g == x.get_group_id())) - .map(|x| x.get_product_ids().map(|p| p.to_string()).collect::>()) - .flatten() + .flat_map(|x| { + x.get_product_ids() + .map(|p| p.to_string()) + .collect::>() + }) .collect() }) } @@ -49,18 +55,19 @@ pub fn count_unescaped_stars(s: &str) -> u32 { /// Recursively loads all decision point JSON descriptions from ../ssvc/data/json/decision_points. /// Entries are stored in a `HashMap` indexed by their respective (name, version) tuple for lookup. -pub static SSVC_DECISION_POINTS: LazyLock> = LazyLock::new(|| { - let mut decision_points = HashMap::new(); +pub static SSVC_DECISION_POINTS: LazyLock> = + LazyLock::new(|| { + let mut decision_points = HashMap::new(); - // Use glob to find all JSON files that might contain decision point data - match glob("../ssvc/data/json/decision_points/**/*.json") { - Ok(paths) => { - for path_res in paths { - match path_res { - Ok(path) => { - match fs::read_to_string(&path) { - Ok(content) => { - match serde_json::from_str::(&content) { + // Use glob to find all JSON files that might contain decision point data + match glob("../ssvc/data/json/decision_points/**/*.json") { + Ok(paths) => { + for path_res in paths { + match path_res { + Ok(path) => { + match fs::read_to_string(&path) { + Ok(content) => { + match serde_json::from_str::(&content) { Ok(dp) => { println!("Loaded SSVC decision point '{}' (version {})", dp.name.deref(), dp.version.deref()); // Insert using (name, key) tuple as the key @@ -73,35 +80,45 @@ pub static SSVC_DECISION_POINTS: LazyLock eprintln!("Warning: Failed to parse decision point from file {:?}: {}", path, err), } - }, - Err(err) => eprintln!("Warning: Failed to read file {:?}: {}", path, err), + } + Err(err) => { + eprintln!("Warning: Failed to read file {:?}: {}", path, err) + } + } } - }, - Err(ref err) => eprintln!("Warning: Failed to read glob result {:?}: {}", path_res, err), + Err(ref err) => eprintln!( + "Warning: Failed to read glob result {:?}: {}", + path_res, err + ), + } } } - }, - Err(err) => eprintln!("Warning: Failed to search for decision point files: {}", err), - } + Err(err) => eprintln!( + "Warning: Failed to search for decision point files: {}", + err + ), + } - decision_points -}); + decision_points + }); /// Derives lookup maps for all observed SSVC decision points that can be used /// to verify the order of values within the respective decision points. -pub static DP_VAL_LOOKUP: LazyLock>> = LazyLock::new(|| { - let mut lookups = HashMap::new(); +#[allow(clippy::type_complexity)] +pub static DP_VAL_LOOKUP: LazyLock>> = + LazyLock::new(|| { + let mut lookups = HashMap::new(); - for (key, dp) in SSVC_DECISION_POINTS.iter() { - let mut lookup_map = HashMap::new(); - for (i, v) in dp.values.iter().enumerate() { - lookup_map.insert(v.name.deref().to_owned(), i as i32); + for (key, dp) in SSVC_DECISION_POINTS.iter() { + let mut lookup_map = HashMap::new(); + for (i, v) in dp.values.iter().enumerate() { + lookup_map.insert(v.name.deref().to_owned(), i as i32); + } + lookups.insert(key.clone(), lookup_map); } - lookups.insert(key.clone(), lookup_map); - } - lookups -}); + lookups + }); /// Collects all "registered" namespaces from known decision points. We assume that each namespace /// that occurs in at least one decision point in the SSVC repository is a "registered" namespace. @@ -113,4 +130,4 @@ pub static REGISTERED_SSVC_NAMESPACES: LazyLock> = LazyLock::new } namespaces -}); \ No newline at end of file +}); diff --git a/csaf-rs/src/csaf/test_helper.rs b/csaf-rs/src/csaf/test_helper.rs index cd70113..492493e 100644 --- a/csaf-rs/src/csaf/test_helper.rs +++ b/csaf-rs/src/csaf/test_helper.rs @@ -1,3 +1,5 @@ +#![cfg(test)] + use crate::csaf::csaf2_0::loader::load_document as load_document_20; use crate::csaf::csaf2_0::schema::CommonSecurityAdvisoryFramework as Csaf20; use crate::csaf::csaf2_1::loader::load_document as load_document_21; @@ -12,7 +14,7 @@ use std::collections::HashMap; /// * `test_number` - The test number to run (e.g., "36" for 6.1.36 tests) /// * `test_function` - The test function to execute against each document /// * `negative_cases` - A slice of tuples containing (file_suffix, expected_validation_error) -/// for negative test cases (starting with "0") +/// for negative test cases (starting with "0") /// /// This function assumes tests with filenames ending with numbers starting with "0" /// are negative tests, and those starting with "1" are positive tests. @@ -26,41 +28,45 @@ fn run_csaf_tests( use glob::glob; // Load and test each file - for entry in glob(pattern).expect("Failed to parse glob pattern") { - if let Ok(path) = entry { - // Extract the file suffix (e.g., "01", "02", etc.) - let file_name = path.file_name().unwrap().to_string_lossy(); - println!("{}", file_name); - let test_num = file_name - .strip_prefix(file_prefix) - .unwrap() - .strip_suffix(".json") - .unwrap(); + for path in glob(pattern) + .expect("Failed to parse glob pattern") + .flatten() + { + // Extract the file suffix (e.g., "01", "02", etc.) + let file_name = path.file_name().unwrap().to_string_lossy(); + println!("{}", file_name); + let test_num = file_name + .strip_prefix(file_prefix) + .unwrap() + .strip_suffix(".json") + .unwrap(); - // Load the document - let doc = document_loader(path.to_string_lossy().as_ref()).unwrap(); + // Load the document + let doc = document_loader(path.to_string_lossy().as_ref()).unwrap(); - // Check if this is expected to be a negative or positive test case - if test_num.starts_with('0') { - // Negative test case - should fail with a specific error - let expected_error = expected_errors.get(test_num).expect( - &format!("Missing expected error definition for negative test case {}", test_num) - ); - assert_eq!( - Err((*expected_error).clone()), - test_function(&doc), - "Negative test case {} should have failed with the expected error", test_num - ); - } else if test_num.starts_with('1') { - // Positive test case - should succeed - assert_eq!( - Ok(()), - test_function(&doc), - "Positive test case {} should have succeeded", test_num - ); - } else { - panic!("Unexpected test case number format: {}", test_num); - } + // Check if this is expected to be a negative or positive test case + if test_num.starts_with('0') { + // Negative test case - should fail with a specific error + let expected_error = expected_errors.get(test_num).expect(&format!( + "Missing expected error definition for negative test case {}", + test_num + )); + assert_eq!( + Err((*expected_error).clone()), + test_function(&doc), + "Negative test case {} should have failed with the expected error", + test_num + ); + } else if test_num.starts_with('1') { + // Positive test case - should succeed + assert_eq!( + Ok(()), + test_function(&doc), + "Positive test case {} should have succeeded", + test_num + ); + } else { + panic!("Unexpected test case number format: {}", test_num); } } } @@ -72,9 +78,18 @@ pub fn run_csaf20_tests( ) { // Find all test files matching the pattern let file_prefix = &format!("oasis_csaf_tc-csaf_2_0-2021-6-1-{}-", test_number); - let pattern = &format!("../csaf/csaf_2.0/test/validator/data/mandatory/{}*.json", file_prefix); + let pattern = &format!( + "../csaf/csaf_2.0/test/validator/data/mandatory/{}*.json", + file_prefix + ); - run_csaf_tests(pattern, file_prefix, load_document_20, test_function, expected_errors); + run_csaf_tests( + pattern, + file_prefix, + load_document_20, + test_function, + expected_errors, + ); } pub fn run_csaf21_tests( @@ -84,7 +99,16 @@ pub fn run_csaf21_tests( ) { // Find all test files matching the pattern let file_prefix = &format!("oasis_csaf_tc-csaf_2_1-2024-6-1-{}-", test_number); - let pattern = &format!("../csaf/csaf_2.1/test/validator/data/mandatory/{}*.json", file_prefix); + let pattern = &format!( + "../csaf/csaf_2.1/test/validator/data/mandatory/{}*.json", + file_prefix + ); - run_csaf_tests(pattern, file_prefix, load_document_21, test_function, expected_errors); + run_csaf_tests( + pattern, + file_prefix, + load_document_21, + test_function, + expected_errors, + ); } diff --git a/csaf-rs/src/csaf/validation.rs b/csaf-rs/src/csaf/validation.rs index c636504..99b4b7b 100644 --- a/csaf-rs/src/csaf/validation.rs +++ b/csaf-rs/src/csaf/validation.rs @@ -46,8 +46,7 @@ pub trait Validate { fn validate_by_test(&self, version: &str); } -pub type Test = - fn(&VersionedDocument) -> Result<(), ValidationError>; +pub type Test = fn(&VersionedDocument) -> Result<(), ValidationError>; /// Represents something which is validatable according to the CSAF standard. /// This trait MUST be implemented by the struct that represents a CSAF document @@ -90,7 +89,7 @@ pub fn validate_by_test( test_id: &str, ) { if let Some(test_fn) = target.tests().get(test_id) { - let _ = match test_fn(target.doc()) { + match test_fn(target.doc()) { Ok(()) => println!("> Test Success"), Err(e) => println!("> Error: {}", e), }; diff --git a/csaf-rs/src/csaf/validations/test_6_1_06.rs b/csaf-rs/src/csaf/validations/test_6_1_06.rs index a8b4fbb..a63ab7a 100644 --- a/csaf-rs/src/csaf/validations/test_6_1_06.rs +++ b/csaf-rs/src/csaf/validations/test_6_1_06.rs @@ -107,8 +107,8 @@ fn check_status_group<'a>( message: format!( "Product {} is marked with product status group \"{}\" but has conflicting product status belonging to group \"{}\"", pid, - status_group.to_string(), - existing_status.to_string() + status_group, + *existing_status ), instance_path: format!("/vulnerabilities/{}/product_status/{}/{}", v_i, field_name, i_pid), }); diff --git a/csaf-rs/src/csaf/validations/test_6_1_07.rs b/csaf-rs/src/csaf/validations/test_6_1_07.rs index a5946e6..bc55f65 100644 --- a/csaf-rs/src/csaf/validations/test_6_1_07.rs +++ b/csaf-rs/src/csaf/validations/test_6_1_07.rs @@ -1,6 +1,8 @@ use crate::csaf::getter_traits::{ContentTrait, CsafTrait, MetricTrait, VulnerabilityTrait}; use crate::csaf::validation::ValidationError; -use crate::csaf::validations::test_6_1_07::VulnerabilityMetrics::{CvssV2, CvssV30, CvssV31, CvssV4, Epss, SsvcV1}; +use crate::csaf::validations::test_6_1_07::VulnerabilityMetrics::{ + CvssV2, CvssV30, CvssV31, CvssV4, Epss, SsvcV1, +}; use std::collections::{HashMap, HashSet}; use std::fmt::{Display, Formatter}; @@ -47,7 +49,8 @@ pub fn test_6_1_07_multiple_same_scores_per_product( doc: &impl CsafTrait, ) -> Result<(), ValidationError> { for (v_i, v) in doc.get_vulnerabilities().iter().enumerate() { - let mut seen_metrics: HashMap)>> = HashMap::new(); + let mut seen_metrics: HashMap)>> = + HashMap::new(); if let Some(metrics) = v.get_metrics() { for (m_i, m) in metrics.iter().enumerate() { let content = m.get_content(); @@ -83,7 +86,7 @@ pub fn test_6_1_07_multiple_same_scores_per_product( content_metrics.push((Epss, m.get_source())); } for p in m.get_products() { - let metrics_set = seen_metrics.entry(p.to_string()).or_insert_with(|| HashSet::new()); + let metrics_set = seen_metrics.entry(p.to_string()).or_default(); for cm_src in content_metrics.iter() { if metrics_set.contains(cm_src) { return Err(ValidationError { @@ -128,12 +131,13 @@ mod tests { run_csaf20_tests( "07", test_6_1_07_multiple_same_scores_per_product, - &HashMap::from([ - ("01", &ValidationError { + &HashMap::from([( + "01", + &ValidationError { message: cvss_v31_error_message.to_string(), instance_path: format!("{}/cvss_v3", csaf_20_path_prefix), - }), - ]), + }, + )]), ); run_csaf21_tests( "07", diff --git a/csaf-rs/src/csaf/validations/test_6_1_39.rs b/csaf-rs/src/csaf/validations/test_6_1_39.rs index 59be9f5..74a01c4 100644 --- a/csaf-rs/src/csaf/validations/test_6_1_39.rs +++ b/csaf-rs/src/csaf/validations/test_6_1_39.rs @@ -1,10 +1,12 @@ use crate::csaf::csaf2_1::schema::DocumentStatus; -use crate::csaf::getter_traits::{CsafTrait, DistributionTrait, DocumentTrait, SharingGroupTrait, TlpTrait, TrackingTrait}; -use crate::csaf::validation::ValidationError; use crate::csaf::csaf2_1::schema::LabelOfTlp::Clear; +use crate::csaf::getter_traits::{ + CsafTrait, DistributionTrait, DocumentTrait, SharingGroupTrait, TlpTrait, TrackingTrait, +}; +use crate::csaf::validation::ValidationError; -static MAX_UUID: &str = "ffffffff-ffff-ffff-ffff-ffffffffffff"; -static NIL_UUID: &str = "00000000-0000-0000-0000-000000000000"; +const MAX_UUID: &str = "ffffffff-ffff-ffff-ffff-ffffffffffff"; +const NIL_UUID: &str = "00000000-0000-0000-0000-000000000000"; /// Validates that when a document is marked with TLP CLEAR, any associated sharing group /// must either have a `MAX_UUID` as its ID or a `NIL_UUID` accompanied by the document status being "Draft". @@ -30,15 +32,15 @@ pub fn test_6_1_39_public_sharing_group_with_no_max_uuid( if distribution.get_tlp_21()?.get_label() == Clear { if let Some(sharing_group) = distribution.get_sharing_group() { let sharing_group_id = sharing_group.get_id(); - return if sharing_group_id == MAX_UUID { - Ok(()) - } else if sharing_group_id == NIL_UUID && doc.get_document().get_tracking().get_status() == DocumentStatus::Draft { - Ok(()) - } else { - Err(ValidationError { - message: "Document with TLP CLEAR and sharing group must use max UUID or nil UUID plus draft status.".to_string(), - instance_path: "/document/distribution/sharing_group/id".to_string(), - }) + return match sharing_group_id.as_str() { + MAX_UUID => Ok(()), + NIL_UUID if doc.get_document().get_tracking().get_status() == DocumentStatus::Draft => Ok(()), + _ => { + Err(ValidationError { + message: "Document with TLP CLEAR and sharing group must use max UUID or nil UUID plus draft status.".to_string(), + instance_path: "/document/distribution/sharing_group/id".to_string(), + }) + } }; } } @@ -48,10 +50,10 @@ pub fn test_6_1_39_public_sharing_group_with_no_max_uuid( #[cfg(test)] mod tests { - use std::collections::HashMap; use crate::csaf::test_helper::run_csaf21_tests; use crate::csaf::validation::ValidationError; use crate::csaf::validations::test_6_1_39::test_6_1_39_public_sharing_group_with_no_max_uuid; + use std::collections::HashMap; #[test] fn test_test_6_1_39() { @@ -60,9 +62,10 @@ mod tests { instance_path: "/document/distribution/sharing_group/id".to_string(), }; - run_csaf21_tests("39", test_6_1_39_public_sharing_group_with_no_max_uuid, &HashMap::from([ - ("01", &expected_error), - ("02", &expected_error), - ])); + run_csaf21_tests( + "39", + test_6_1_39_public_sharing_group_with_no_max_uuid, + &HashMap::from([("01", &expected_error), ("02", &expected_error)]), + ); } } diff --git a/csaf-rs/src/csaf/validations/test_6_1_40.rs b/csaf-rs/src/csaf/validations/test_6_1_40.rs index 64e7638..ab96cf6 100644 --- a/csaf-rs/src/csaf/validations/test_6_1_40.rs +++ b/csaf-rs/src/csaf/validations/test_6_1_40.rs @@ -25,9 +25,8 @@ static NIL_UUID: &str = "00000000-0000-0000-0000-000000000000"; /// * `Ok(())` if the validation passes. /// * `Err(ValidationError)` if the validation fails, with a message explaining the reason /// and the JSON path to the invalid element. -pub fn test_6_1_40_invalid_sharing_group_name( - doc: &impl CsafTrait, -) -> Result<(), ValidationError> { +#[allow(clippy::collapsible_if)] +pub fn test_6_1_40_invalid_sharing_group_name(doc: &impl CsafTrait) -> Result<(), ValidationError> { let distribution = doc.get_document().get_distribution_21()?; if let Some(sharing_group) = distribution.get_sharing_group() { @@ -35,16 +34,22 @@ pub fn test_6_1_40_invalid_sharing_group_name( if sharing_group_name == NAME_PUBLIC { if sharing_group.get_id() != MAX_UUID { return Err(ValidationError { - message: format!("Sharing group name \"{}\" is prohibited without max UUID.", NAME_PUBLIC), - instance_path: "/document/distribution/sharing_group/name".to_string() - }) + message: format!( + "Sharing group name \"{}\" is prohibited without max UUID.", + NAME_PUBLIC + ), + instance_path: "/document/distribution/sharing_group/name".to_string(), + }); } } else if sharing_group_name == NAME_PRIVATE { if sharing_group.get_id() != NIL_UUID { return Err(ValidationError { - message: format!("Sharing group name \"{}\" is prohibited without nil UUID.", NAME_PRIVATE), - instance_path: "/document/distribution/sharing_group/name".to_string() - }) + message: format!( + "Sharing group name \"{}\" is prohibited without nil UUID.", + NAME_PRIVATE + ), + instance_path: "/document/distribution/sharing_group/name".to_string(), + }); } } } @@ -55,25 +60,40 @@ pub fn test_6_1_40_invalid_sharing_group_name( #[cfg(test)] mod tests { - use std::collections::HashMap; use crate::csaf::test_helper::run_csaf21_tests; use crate::csaf::validation::ValidationError; - use crate::csaf::validations::test_6_1_40::{test_6_1_40_invalid_sharing_group_name, NAME_PRIVATE, NAME_PUBLIC}; + use crate::csaf::validations::test_6_1_40::{ + test_6_1_40_invalid_sharing_group_name, NAME_PRIVATE, NAME_PUBLIC, + }; + use std::collections::HashMap; #[test] fn test_test_6_1_40() { run_csaf21_tests( "40", - test_6_1_40_invalid_sharing_group_name, &HashMap::from([ - ("01", &ValidationError { - message: format!("Sharing group name \"{}\" is prohibited without max UUID.", NAME_PUBLIC), - instance_path: "/document/distribution/sharing_group/name".to_string() - }), - ("02", &ValidationError { - message: format!("Sharing group name \"{}\" is prohibited without nil UUID.", NAME_PRIVATE), - instance_path: "/document/distribution/sharing_group/name".to_string() - }), - ]) + test_6_1_40_invalid_sharing_group_name, + &HashMap::from([ + ( + "01", + &ValidationError { + message: format!( + "Sharing group name \"{}\" is prohibited without max UUID.", + NAME_PUBLIC + ), + instance_path: "/document/distribution/sharing_group/name".to_string(), + }, + ), + ( + "02", + &ValidationError { + message: format!( + "Sharing group name \"{}\" is prohibited without nil UUID.", + NAME_PRIVATE + ), + instance_path: "/document/distribution/sharing_group/name".to_string(), + }, + ), + ]), ); } }