diff --git a/README.md b/README.md index 5c60e8e..ff2cb56 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ This is work-in-progress. If you want to build `csaf-validator` on your own, please install Rust (see https://rustup.rs) and then run ```bash -# make sure, submodules are up-to-date +# make sure submodules are up-to-date +git submodule init git submodule update --remote # run the tests @@ -35,8 +36,8 @@ Arguments: Options: -c, --csaf-version Version of CSAF to use [default: 2.0] - -p, --profile The profile to use [default: basic] - -o, --only-test Run only the selected test + -p, --preset The validation preset to use [default: basic] + -t, --test-id Run only the selected tests, may be specified multiple times -h, --help Print help -V, --version Print version ``` @@ -48,8 +49,8 @@ Some examples to use are included below. Please note that the validation is not csaf-validator --csaf-version 2.0 my-csaf-2-0-document.json # validate a CSAF 2.0 document with profile full -csaf-validator --csaf-version 2.0 --profile full my-csaf-2-0-document.json +csaf-validator --csaf-version 2.0 --preset full my-csaf-2-0-document.json -# validate a CSAF 2.1 document with a specific test -csaf-validator --csaf-version 2.1 --only-test 6.1.34 my-csaf-2-1-document.json +# validate a CSAF 2.1 document with one specific test +csaf-validator --csaf-version 2.1 --test-id 6.1.34 my-csaf-2-1-document.json ``` diff --git a/csaf b/csaf index b01a0a7..ed6e732 160000 --- a/csaf +++ b/csaf @@ -1 +1 @@ -Subproject commit b01a0a7a176f257e47bf6e4e3ac42d0def9620b1 +Subproject commit ed6e7320fb8cd7819df44638c1cb3c4803bd3c55 diff --git a/csaf-lib/src/csaf/csaf2_0/validation.rs b/csaf-lib/src/csaf/csaf2_0/validation.rs index 3a30948..8a6da0a 100644 --- a/csaf-lib/src/csaf/csaf2_0/validation.rs +++ b/csaf-lib/src/csaf/csaf2_0/validation.rs @@ -1,15 +1,15 @@ use super::product_helper::*; use super::schema::CommonSecurityAdvisoryFramework; -use crate::csaf::validation::{Test, Validatable, ValidationProfile}; +use crate::csaf::validation::{Test, Validatable, ValidationPreset}; use std::collections::{HashMap, HashSet}; use crate::csaf::helpers::find_duplicates; impl Validatable for CommonSecurityAdvisoryFramework { - fn profiles(&self) -> HashMap> { + fn presets(&self) -> HashMap> { HashMap::from([ - (ValidationProfile::Basic, Vec::from(["6.1.1", "6.1.2"])), - (ValidationProfile::Extended, Vec::from(["6.1.1", "6.1.2"])), - (ValidationProfile::Full, Vec::from(["6.1.1", "6.1.2"])), + (ValidationPreset::Basic, Vec::from(["6.1.1", "6.1.2"])), + (ValidationPreset::Extended, Vec::from(["6.1.1", "6.1.2"])), + (ValidationPreset::Full, Vec::from(["6.1.1", "6.1.2"])), ]) } diff --git a/csaf-lib/src/csaf/csaf2_1/validation.rs b/csaf-lib/src/csaf/csaf2_1/validation.rs index 9eea792..24e88ff 100644 --- a/csaf-lib/src/csaf/csaf2_1/validation.rs +++ b/csaf-lib/src/csaf/csaf2_1/validation.rs @@ -1,18 +1,18 @@ use super::product_helper::*; use super::schema::CommonSecurityAdvisoryFramework; use crate::csaf::helpers::find_duplicates; -use crate::csaf::validation::{Test, Validatable, ValidationProfile}; +use crate::csaf::validation::{Test, Validatable, ValidationPreset}; use std::collections::{HashMap, HashSet}; impl Validatable for CommonSecurityAdvisoryFramework { - fn profiles(&self) -> HashMap> { + fn presets(&self) -> HashMap> { HashMap::from([ ( - ValidationProfile::Basic, + ValidationPreset::Basic, Vec::from(["6.1.1", "6.1.2", "6.1.34"]), ), - (ValidationProfile::Extended, Vec::from(["6.1.1", "6.1.2"])), - (ValidationProfile::Full, Vec::from(["6.1.1", "6.1.2"])), + (ValidationPreset::Extended, Vec::from(["6.1.1", "6.1.2"])), + (ValidationPreset::Full, Vec::from(["6.1.1", "6.1.2"])), ]) } diff --git a/csaf-lib/src/csaf/validation.rs b/csaf-lib/src/csaf/validation.rs index 4da8782..c255ab9 100644 --- a/csaf-lib/src/csaf/validation.rs +++ b/csaf-lib/src/csaf/validation.rs @@ -4,28 +4,28 @@ use std::str::FromStr; pub enum ValidationError {} #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum ValidationProfile { +pub enum ValidationPreset { Basic, Extended, Full, } -impl FromStr for ValidationProfile { +impl FromStr for ValidationPreset { type Err = (); - fn from_str(input: &str) -> Result { + fn from_str(input: &str) -> Result { match input { - "basic" => Ok(ValidationProfile::Basic), - "extended" => Ok(ValidationProfile::Extended), - "full" => Ok(ValidationProfile::Full), + "basic" => Ok(ValidationPreset::Basic), + "extended" => Ok(ValidationPreset::Extended), + "full" => Ok(ValidationPreset::Full), _ => Err(()), } } } pub trait Validate { - /// Validates this object according to a validation profile - fn validate_profile(&'static self, profile: ValidationProfile); + /// Validates this object according to a validation preset + fn validate_preset(&'static self, preset: ValidationPreset); /// Validates this object according to a specific test ID. fn validate_by_test(&self, version: &str); @@ -38,10 +38,10 @@ pub type Test = /// This trait MUST be implemented by the struct that represents a CSAF document /// in the respective version. /// -/// It can then be used to validate documents with either [validate_by_profile] or [validate_by_test]. +/// It can then be used to validate documents with either [validate_by_preset] or [validate_by_test]. pub trait Validatable { - /// Returns a hashmap containing the test ID per profile - fn profiles(&self) -> HashMap>; + /// Returns a hashmap containing the test ID per preset + fn presets(&self) -> HashMap>; /// Returns a hashmap containing the test function per test ID fn tests(&self) -> HashMap<&str, Test>; @@ -49,16 +49,16 @@ pub trait Validatable { fn doc(&self) -> &VersionedDocument; } -/// Executes all tests of the specified [profile] against the [target] +/// Executes all tests of the specified [preset] against the [target] /// (which is of type [VersionedDocument], e.g. a CSAF 2.0 document). -pub fn validate_by_profile( +pub fn validate_by_preset( target: &impl Validatable, - profile: ValidationProfile, + preset: ValidationPreset, ) { - println!("Validating document with {:?} profile... \n", profile); + println!("Validating document with {:?} preset... \n", preset); // Loop through tests - if let Some(tests) = target.profiles().get(&profile) { + if let Some(tests) = target.presets().get(&preset) { for test_id in tests { println!("Executing Test {}... ", test_id); validate_by_test(target, test_id); @@ -66,7 +66,7 @@ pub fn validate_by_profile( println!() } } else { - println!("No tests found for profile") + println!("No tests found for preset") } } diff --git a/csaf-validator/src/main.rs b/csaf-validator/src/main.rs index 64fb9c5..77642a9 100644 --- a/csaf-validator/src/main.rs +++ b/csaf-validator/src/main.rs @@ -1,7 +1,8 @@ +use std::str::FromStr; use anyhow::{bail, Result}; use csaf_lib::csaf::csaf2_0::loader::load_document as load_document_2_0; use csaf_lib::csaf::csaf2_1::loader::load_document as load_document_2_1; -use csaf_lib::csaf::validation::{validate_by_profile, validate_by_test, ValidationProfile}; +use csaf_lib::csaf::validation::{validate_by_preset, validate_by_test, Validatable, ValidationPreset}; use clap::Parser; /// A validator for CSAF documents @@ -15,44 +16,45 @@ struct Args { #[arg(short, long, default_value = "2.0")] csaf_version: String, - /// The profile to use + /// The validation preset to use #[arg(short, long, default_value = "basic")] - profile: String, + preset: String, - /// Run only the selected test - #[arg(short, long)] - only_test: Option, + /// Run only the selected tests, may be specified multiple times + #[arg(short, long, action = clap::ArgAction::Append)] + test_id: Vec, } fn main() -> Result<()> { let args = Args::parse(); - let profile = ValidationProfile::Basic; - // TODO: it would be nice to return the validatable from this match, but this is beyond my - // rust generics knowledge, so a little bit of duplicate code here - if let Some(test_id) = args.only_test { - let result = match args.csaf_version.as_str() { - "2.0" => { - validate_by_test(&load_document_2_0(args.path.as_str())?, test_id.as_str()) - } - "2.1" => { - validate_by_test(&load_document_2_1(args.path.as_str())?, test_id.as_str()) - } - _ => bail!("invalid version"), - }; + match args.csaf_version.as_str() { + "2.0" => { + process_document(load_document_2_0(args.path.as_str())?, &args) + } + "2.1" => { + process_document(load_document_2_1(args.path.as_str())?, &args) + } + _ => bail!(format!("Invalid CSAF version: {}", args.csaf_version)), + } +} - Ok(result) +fn process_document(document: T, args: &Args) -> Result<()> +where + T: Validatable, +{ + if !args.test_id.is_empty() { + for test_id in &args.test_id { + println!("\nExecuting Test {}... ", test_id); + validate_by_test(&document, test_id.as_str()); + } + Ok(()) } else { - let result = match args.csaf_version.as_str() { - "2.0" => { - validate_by_profile(&load_document_2_0(args.path.as_str())?, profile) - } - "2.1" => { - validate_by_profile(&load_document_2_1(args.path.as_str())?, profile) - } - _ => bail!("invalid version"), + let preset = match ValidationPreset::from_str(args.preset.as_str()) { + Ok(preset) => preset, + Err(_) => bail!(format!("Invalid validation preset: {}", args.preset)), }; - - Ok(result) + validate_by_preset(&document, preset); + Ok(()) } }