diff --git a/ion-schema-tests-runner/src/generator.rs b/ion-schema-tests-runner/src/generator.rs index fdb25ce..5200410 100644 --- a/ion-schema-tests-runner/src/generator.rs +++ b/ion-schema-tests-runner/src/generator.rs @@ -121,8 +121,8 @@ fn generate_test_cases_for_file(ctx: Context) -> TokenStream { // get the schema content from given schema file path let ion_content = fs::read(ctx.current_dir.as_path()) .unwrap_or_else(|e| panic!("Unable to read {path_string} – {e}")); - let schema_content = Element::read_all(&ion_content) - .unwrap_or_else(|e| panic!("Error in {path_string} – {e:?}")); + let schema_content = + Element::read_all(ion_content).unwrap_or_else(|e| panic!("Error in {path_string} – {e:?}")); let isl_version = find_isl_version(&schema_content); diff --git a/ion-schema/examples/schema.rs b/ion-schema/examples/schema.rs index bca693a..f50964e 100644 --- a/ion-schema/examples/schema.rs +++ b/ion-schema/examples/schema.rs @@ -65,7 +65,7 @@ fn validate(command_args: &ArgMatches) -> IonSchemaResult<()> { // Extract Ion value provided by user let input_file = command_args.value_of("input").unwrap(); let value = fs::read(input_file).expect("Can not load given ion file"); - let owned_elements = Element::read_all(&value).expect("parsing failed unexpectedly"); + let owned_elements = Element::read_all(value).expect("parsing failed unexpectedly"); // Set up authorities vector let mut document_authorities: Vec> = vec![]; diff --git a/ion-schema/src/ion_path.rs b/ion-schema/src/ion_path.rs index 5ea65bd..fef0f41 100644 --- a/ion-schema/src/ion_path.rs +++ b/ion-schema/src/ion_path.rs @@ -4,7 +4,7 @@ use std::fmt; use std::fmt::{Debug, Formatter}; /// Represents a single element in Ion Path which is either an index value or a field name depending on its parent container type -#[derive(Clone, PartialEq, PartialOrd)] +#[derive(Clone, PartialEq, PartialOrd, Eq, Hash)] pub enum IonPathElement { Index(usize), Field(String), @@ -57,7 +57,7 @@ impl fmt::Display for IonPathElement { /// ```ion /// ( greetings 1 ) // here 1 represents the index of "hi" in the Ion list value /// ``` -#[derive(Default, Clone, PartialEq, PartialOrd)] +#[derive(Default, Clone, PartialEq, PartialOrd, Eq, Hash)] pub struct IonPath { ion_path_elements: Vec, } @@ -70,6 +70,10 @@ impl IonPath { pub fn pop(&mut self) -> Option { self.ion_path_elements.pop() } + + pub(crate) fn new(ion_path_elements: Vec) -> Self { + Self { ion_path_elements } + } } impl From for Element { diff --git a/ion-schema/src/isl/isl_constraint.rs b/ion-schema/src/isl/isl_constraint.rs index 74343a0..e4aec45 100644 --- a/ion-schema/src/isl/isl_constraint.rs +++ b/ion-schema/src/isl/isl_constraint.rs @@ -86,7 +86,7 @@ pub mod v_1_0 { { IslConstraint::new( IslVersion::V1_0, - IslConstraintValue::Fields(fields.map(|(s, t)| (s, t)).collect(), false), + IslConstraintValue::Fields(fields.collect(), false), ) } @@ -278,7 +278,7 @@ pub mod v_2_0 { { IslConstraint::new( IslVersion::V2_0, - IslConstraintValue::Fields(fields.map(|(s, t)| (s, t)).collect(), false), + IslConstraintValue::Fields(fields.collect(), false), ) } diff --git a/ion-schema/src/violation.rs b/ion-schema/src/violation.rs index 215e17e..6846457 100644 --- a/ion-schema/src/violation.rs +++ b/ion-schema/src/violation.rs @@ -4,7 +4,11 @@ use std::fmt::Formatter; use thiserror::Error; /// Represents [Violation] found during validation with detailed error message, error code and the constraint for which the validation failed -#[derive(Debug, Clone, PartialEq, Error)] +/// Equivalence of `Violation` is not supported due to its tree structure of having children `Violation`s. +/// Please use macro `assert_equivalent_violations!(violation1, violation2)` for comparing if two violations are equal. +/// This macro uses `flattened_violations` and does not depend on the order of the children `Violation`s. +/// For non-equivalent violations use macro `assert_non_equivalent_violations!(violation1, violation2)`. +#[derive(Debug, Clone, Error)] pub struct Violation { constraint: String, // represents the constraint that created this violation code: ViolationCode, // represents an error code that indicates the type of the violation @@ -25,7 +29,7 @@ impl Violation { code, message: message.as_ref().to_owned(), ion_path: ion_path.to_owned(), - violations: vec![], + violations: Vec::new(), } } @@ -77,8 +81,8 @@ impl Violation { } } - pub fn violations(&self) -> &[Violation] { - &self.violations + pub fn violations(&self) -> impl Iterator { + self.violations.iter() } } @@ -90,7 +94,7 @@ impl fmt::Display for Violation { } /// Represents violation code that indicates the type of the violation -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ViolationCode { AllTypesNotMatched, AnnotationMismatched, @@ -146,3 +150,341 @@ impl fmt::Display for ViolationCode { ) } } + +#[macro_export] +/// Equivalence for `Violation`s is not supported due to its tree structure of having children violations. +/// This macro can be used for comparing if two violations are equal and uses `flattened_violations` for the comparison. +macro_rules! assert_equivalent_violations { + ($left:expr, $right:expr $(,)?) => { + let mut left_strings: Vec = $left + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + left_strings.sort(); + let mut right_strings: Vec = $right + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + right_strings.sort(); + assert_eq!(left_strings, right_strings); + }; + ($left:expr, $right:expr, $($arg:tt)+) => { + let mut left_strings: Vec = $left + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + left_strings.sort(); + let mut right_strings: Vec = $right + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + right_strings.sort(); + assert_eq!(left_strings, right_strings, $($arg)+); + }; +} + +#[macro_export] + +/// Equivalence for `Violation`s is not supported due to its tree structure of having children violations. +/// This macro can be used for comparing if two violations are not equal and uses `flattened_violations` for the comparison. +macro_rules! assert_non_equivalent_violations { + ($left:expr, $right:expr $(,)?) => { + let mut left_strings: Vec = $left + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + left_strings.sort(); + let mut right_strings: Vec = $right + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + right_strings.sort(); + assert_ne!(left_strings, right_strings); + }; + ($left:expr, $right:expr, $($arg:tt)+) => { + let mut left_strings: Vec = $left + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + left_strings.sort(); + let mut right_strings: Vec = $right + .flattened_violations() + .into_iter() + .map(|x| format!("{:?}", x)) + .collect(); + right_strings.sort(); + assert_ne!(left_strings, right_strings, $($arg)+); + }; +} + +#[cfg(test)] +mod violation_tests { + use crate::ion_path::{IonPath, IonPathElement}; + use crate::violation::{Violation, ViolationCode}; + use rstest::rstest; + + #[rstest(violation1, violation2, + case::unordered_violations(Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "regex", + ViolationCode::RegexMismatched, + "regex mismatched", + &mut IonPath::default(), + ), + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + ], + ), Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + Violation::new( + "regex", + ViolationCode::RegexMismatched, + "regex mismatched", + &mut IonPath::default(), + ), + ], + ) + ), + case::nested_violations( + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::with_violations( + "regex", + ViolationCode::RegexMismatched, + "regex mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + Violation::new( + "codepoint_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ) + ] + ) + ], + ), + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::with_violations( + "regex", + ViolationCode::RegexMismatched, + "regex mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "codepoint_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ) + ] + ) + ], + ) + ), + case::empty_violations( + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![], + ), + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![], + ) + ), + case::multiple_violations_from_one_constraint( + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "element", + ViolationCode::ElementMismatched, + "element mismatched", + &mut IonPath::default(), + ), + Violation::new( + "element", + ViolationCode::ElementNotDistinct, + "element not distinct", + &mut IonPath::default(), + ), + ], + ), + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "element", + ViolationCode::ElementNotDistinct, + "element not distinct", + &mut IonPath::default(), + ), + Violation::new( + "element", + ViolationCode::ElementMismatched, + "element mismatched", + &mut IonPath::default(), + ), + ], + ), + ) + )] + fn violation_equivalence(violation1: Violation, violation2: Violation) { + assert_equivalent_violations!(violation1, violation2); + } + + #[rstest(violation1, violation2, + case::different_violations( + Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "regex", + ViolationCode::RegexMismatched, + "regex mismatched", + &mut IonPath::default(), + ), + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + ], + ), Violation::with_violations( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + vec![ + Violation::new( + "container_length", + ViolationCode::InvalidLength, + "invalid length", + &mut IonPath::default(), + ), + ], + ) + ), + case::different_constraints( + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + ), + Violation::new( + "regex", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + ) + ), + case::different_violation_code( + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + ), + Violation::new( + "type_constraint", + ViolationCode::RegexMismatched, + "type mismatched", + &mut IonPath::default(), + ) + ), + case::different_violation_message( + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "regex mismatched", + &mut IonPath::default(), + ), + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + ) + ), + case::different_ion_path( + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::new(vec![IonPathElement::Index(2)]), + ), + Violation::new( + "type_constraint", + ViolationCode::TypeMismatched, + "type mismatched", + &mut IonPath::default(), + ) + ))] + fn non_equivalent_violations(violation1: Violation, violation2: Violation) { + assert_non_equivalent_violations!(violation1, violation2); + } +} diff --git a/wasm-schema-sandbox/src/lib.rs b/wasm-schema-sandbox/src/lib.rs index 7b3da26..24ffb7d 100644 --- a/wasm-schema-sandbox/src/lib.rs +++ b/wasm-schema-sandbox/src/lib.rs @@ -6,6 +6,7 @@ use ion_schema::result::IonSchemaResult; use ion_schema::schema::Schema; use ion_schema::system::SchemaSystem; use ion_schema::types::TypeDefinition; +use ion_schema::violation::Violation; use ion_schema::IonSchemaElement; use js_sys::Array; use serde::{Deserialize, Serialize}; @@ -193,7 +194,7 @@ pub fn validate( log!("validation complete!"); - let violations = match &result { + let violations: Vec<&Violation> = match &result { Ok(_) => vec![], Err(violation) => violation.flattened_violations(), };