fix: Number comparison for `enum` and `const` keywords

Ref: #149
This commit is contained in:
Dmitry Dygalo 2020-12-10 17:13:31 +01:00 committed by Dmitry Dygalo
parent 51d71ceb6d
commit 509c14c7a0
6 changed files with 81 additions and 8 deletions

View File

@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Number comparison for `enum` and `const` keywords. [#149](https://github.com/Stranger6667/jsonschema-rs/issues/149)
### Performance
- Some performance related changes were rolled back, due to increased complexity.

View File

@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Number comparison for `enum` and `const` keywords. [#149](https://github.com/Stranger6667/jsonschema-rs/issues/149)
## [0.4.1] - 2020-12-09
### Fixed

View File

@ -1,3 +1,4 @@
use crate::keywords::helpers;
use crate::{
compilation::{context::CompilationContext, JSONSchema},
error::{error, no_error, ErrorIterator, ValidationError},
@ -31,7 +32,7 @@ impl Validate for ConstArrayValidator {
#[inline]
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
if let Value::Array(instance_value) = instance {
&self.value == instance_value
helpers::equal_arrays(&self.value, instance_value)
} else {
false
}
@ -181,7 +182,7 @@ impl Validate for ConstObjectValidator {
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
if let Value::Object(item) = instance {
&self.value == item
helpers::equal_objects(&self.value, item)
} else {
false
}

View File

@ -27,10 +27,6 @@ impl EnumValidator {
}
impl Validate for EnumValidator {
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
self.items.iter().any(|item| helpers::equal(instance, item))
}
fn validate<'a>(&self, schema: &'a JSONSchema, instance: &'a Value) -> ErrorIterator<'a> {
if !self.is_valid(schema, instance) {
error(ValidationError::enumeration(instance, &self.options))
@ -38,6 +34,10 @@ impl Validate for EnumValidator {
no_error()
}
}
fn is_valid(&self, _: &JSONSchema, instance: &Value) -> bool {
self.items.iter().any(|item| helpers::equal(instance, item))
}
}
impl ToString for EnumValidator {

View File

@ -1,8 +1,47 @@
use serde_json::Value;
use num_cmp::NumCmp;
use serde_json::{Map, Value};
macro_rules! num_cmp {
($left:expr, $right:expr) => {
if let Some(b) = $right.as_u64() {
NumCmp::num_eq($left, b)
} else if let Some(b) = $right.as_i64() {
NumCmp::num_eq($left, b)
} else {
NumCmp::num_eq($left, $right.as_f64().expect("Always valid"))
}
};
}
#[inline]
pub(crate) fn equal(left: &Value, right: &Value) -> bool {
match (left, right) {
(Value::Number(left), Value::Number(right)) => left.as_f64() == right.as_f64(),
(Value::Number(left), Value::Number(right)) => {
if let Some(a) = left.as_u64() {
num_cmp!(a, right)
} else if let Some(a) = left.as_i64() {
num_cmp!(a, right)
} else {
let a = left.as_f64().expect("Always valid");
num_cmp!(a, right)
}
}
(Value::Array(left), Value::Array(right)) => equal_arrays(left, right),
(Value::Object(left), Value::Object(right)) => equal_objects(left, right),
(_, _) => left == right,
}
}
#[inline]
pub(crate) fn equal_arrays(left: &[Value], right: &[Value]) -> bool {
left.len() == right.len() && left.iter().zip(right.iter()).all(|(a, b)| equal(a, b))
}
#[inline]
pub(crate) fn equal_objects(left: &Map<String, Value>, right: &Map<String, Value>) -> bool {
left.len() == right.len()
&& left
.iter()
.zip(right)
.all(|((ka, va), (kb, vb))| ka == kb && equal(va, vb))
}

View File

@ -254,4 +254,29 @@ mod tests {
let compiled = JSONSchema::compile(schema).unwrap();
assert!(compiled.is_valid(instance))
}
// enum: Number
#[test_case(&json!({"enum": [0.0]}), &json!(0))]
// enum: Array
#[test_case(&json!({"enum": [[1.0]]}), &json!([1]))]
// enum: Object
#[test_case(&json!({"enum": [{"a": 1.0}]}), &json!({"a": 1}))]
// enum:: Object in Array
#[test_case(&json!({"enum": [[{"b": 1.0}]]}), &json!([{"b": 1}]))]
// enum:: Array in Object
#[test_case(&json!({"enum": [{"c": [1.0]}]}), &json!({"c": [1]}))]
// const: Number
#[test_case(&json!({"const": 0.0}), &json!(0))]
// const: Array
#[test_case(&json!({"const": [1.0]}), &json!([1]))]
// const: Object
#[test_case(&json!({"const": {"a": 1.0}}), &json!({"a": 1}))]
// const:: Object in Array
#[test_case(&json!({"const": [{"b": 1.0}]}), &json!([{"b": 1}]))]
// const:: Array in Object
#[test_case(&json!({"const": {"c": [1.0]}}), &json!({"c": [1]}))]
fn numeric_equivalence(schema: &Value, instance: &Value) {
// Regression: https://github.com/Stranger6667/jsonschema-rs/issues/149
let compiled = JSONSchema::compile(schema).unwrap();
assert!(compiled.is_valid(instance))
}
}