Skip to content

Commit

Permalink
fix: Support integer-valued numbers in relevant validators
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <[email protected]>
  • Loading branch information
Stranger6667 committed Sep 10, 2024
1 parent d27f9be commit 64b08ac
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed

- Ignoring `$schema` in resolved references.
- Support integer-valued numbers for `maxItems`, `maxLength`, `maxProperties`, `maxContains`, `minItems`, `minLength`, `minProperties`, `minContains`.

### Deprecated

Expand Down
1 change: 1 addition & 0 deletions bindings/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed

- Ignoring ``$schema`` in resolved references.
- Support integer-valued numbers for `maxItems`, `maxLength`, `maxProperties`, `maxContains`, `minItems`, `minLength`, `minProperties`, `minContains`.

### Deprecated

Expand Down
24 changes: 24 additions & 0 deletions jsonschema/src/keywords/contains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,30 @@ mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn max_contains_with_decimal() {
tests_util::is_valid(
&json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": {"const": 1},
"maxContains": 1.0
}),
&json!([1]),
);
}

#[test]
fn min_contains_with_decimal() {
tests_util::is_valid(
&json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"contains": {"const": 1},
"minContains": 2.0
}),
&json!([1, 1]),
);
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(&json!({"contains": {"const": 2}}), &json!([]), "/contains")
Expand Down
21 changes: 15 additions & 6 deletions jsonschema/src/keywords/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,21 @@ pub(crate) fn map_get_u64<'a>(
value,
0.into(),
))),
None => Some(Err(ValidationError::single_type_error(
JSONPointer::default(),
context.clone().into_pointer(),
value,
PrimitiveType::Integer,
))),
None => {
if let Some(value) = value.as_f64() {
if value.trunc() == value {
// NOTE: Imprecise cast as big integers are not supported yet
#[allow(clippy::cast_possible_truncation)]
return Some(Ok(value as u64));
}
}
Some(Err(ValidationError::single_type_error(
JSONPointer::default(),
context.clone().into_pointer(),
value,
PrimitiveType::Integer,
)))
}
}
}

Expand Down
35 changes: 30 additions & 5 deletions jsonschema/src/keywords/max_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
keywords::{helpers::fail_on_non_positive_integer, CompilationResult},
paths::{JSONPointer, JsonPointerNode},
validator::Validate,
Draft,
};
use serde_json::{Map, Value};

Expand All @@ -14,12 +15,27 @@ pub(crate) struct MaxItemsValidator {

impl MaxItemsValidator {
#[inline]
pub(crate) fn compile(schema: &Value, schema_path: JSONPointer) -> CompilationResult {
pub(crate) fn compile(
schema: &Value,
schema_path: JSONPointer,
draft: Draft,
) -> CompilationResult {
if let Some(limit) = schema.as_u64() {
Ok(Box::new(MaxItemsValidator { limit, schema_path }))
} else {
Err(fail_on_non_positive_integer(schema, schema_path))
return Ok(Box::new(MaxItemsValidator { limit, schema_path }));
}
if !matches!(draft, Draft::Draft4) {
if let Some(limit) = schema.as_f64() {
if limit.trunc() == limit {
#[allow(clippy::cast_possible_truncation)]
return Ok(Box::new(MaxItemsValidator {
// NOTE: Imprecise cast as big integers are not supported yet
limit: limit as u64,
schema_path,
}));
}
}
}
Err(fail_on_non_positive_integer(schema, schema_path))
}
}

Expand Down Expand Up @@ -65,14 +81,23 @@ pub(crate) fn compile<'a>(
context: &CompilationContext,
) -> Option<CompilationResult<'a>> {
let schema_path = context.as_pointer_with("maxItems");
Some(MaxItemsValidator::compile(schema, schema_path))
Some(MaxItemsValidator::compile(
schema,
schema_path,
context.config.draft(),
))
}

#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn with_decimal() {
tests_util::is_valid(&json!({"maxItems": 2.0}), &json!([1]));
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(&json!({"maxItems": 1}), &json!([1, 2]), "/maxItems")
Expand Down
35 changes: 30 additions & 5 deletions jsonschema/src/keywords/max_length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
keywords::{helpers::fail_on_non_positive_integer, CompilationResult},
paths::{JSONPointer, JsonPointerNode},
validator::Validate,
Draft,
};
use serde_json::{Map, Value};

Expand All @@ -14,12 +15,27 @@ pub(crate) struct MaxLengthValidator {

impl MaxLengthValidator {
#[inline]
pub(crate) fn compile(schema: &Value, schema_path: JSONPointer) -> CompilationResult {
pub(crate) fn compile(
schema: &Value,
schema_path: JSONPointer,
draft: Draft,
) -> CompilationResult {
if let Some(limit) = schema.as_u64() {
Ok(Box::new(MaxLengthValidator { limit, schema_path }))
} else {
Err(fail_on_non_positive_integer(schema, schema_path))
return Ok(Box::new(MaxLengthValidator { limit, schema_path }));
}
if !matches!(draft, Draft::Draft4) {
if let Some(limit) = schema.as_f64() {
if limit.trunc() == limit {
#[allow(clippy::cast_possible_truncation)]
return Ok(Box::new(MaxLengthValidator {
// NOTE: Imprecise cast as big integers are not supported yet
limit: limit as u64,
schema_path,
}));
}
}
}
Err(fail_on_non_positive_integer(schema, schema_path))
}
}

Expand Down Expand Up @@ -65,14 +81,23 @@ pub(crate) fn compile<'a>(
context: &CompilationContext,
) -> Option<CompilationResult<'a>> {
let schema_path = context.as_pointer_with("maxLength");
Some(MaxLengthValidator::compile(schema, schema_path))
Some(MaxLengthValidator::compile(
schema,
schema_path,
context.config.draft(),
))
}

#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn with_decimal() {
tests_util::is_valid(&json!({"maxLength": 2.0}), &json!("f"));
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(&json!({"maxLength": 1}), &json!("ab"), "/maxLength")
Expand Down
35 changes: 30 additions & 5 deletions jsonschema/src/keywords/max_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
keywords::{helpers::fail_on_non_positive_integer, CompilationResult},
paths::{JSONPointer, JsonPointerNode},
validator::Validate,
Draft,
};
use serde_json::{Map, Value};

Expand All @@ -14,12 +15,27 @@ pub(crate) struct MaxPropertiesValidator {

impl MaxPropertiesValidator {
#[inline]
pub(crate) fn compile(schema: &Value, schema_path: JSONPointer) -> CompilationResult {
pub(crate) fn compile(
schema: &Value,
schema_path: JSONPointer,
draft: Draft,
) -> CompilationResult {
if let Some(limit) = schema.as_u64() {
Ok(Box::new(MaxPropertiesValidator { limit, schema_path }))
} else {
Err(fail_on_non_positive_integer(schema, schema_path))
return Ok(Box::new(MaxPropertiesValidator { limit, schema_path }));
}
if !matches!(draft, Draft::Draft4) {
if let Some(limit) = schema.as_f64() {
if limit.trunc() == limit {
#[allow(clippy::cast_possible_truncation)]
return Ok(Box::new(MaxPropertiesValidator {
// NOTE: Imprecise cast as big integers are not supported yet
limit: limit as u64,
schema_path,
}));
}
}
}
Err(fail_on_non_positive_integer(schema, schema_path))
}
}

Expand Down Expand Up @@ -65,14 +81,23 @@ pub(crate) fn compile<'a>(
context: &CompilationContext,
) -> Option<CompilationResult<'a>> {
let schema_path = context.as_pointer_with("maxProperties");
Some(MaxPropertiesValidator::compile(schema, schema_path))
Some(MaxPropertiesValidator::compile(
schema,
schema_path,
context.config.draft(),
))
}

#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn with_decimal() {
tests_util::is_valid(&json!({"maxProperties": 2.0}), &json!({"foo": 1}));
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(
Expand Down
35 changes: 30 additions & 5 deletions jsonschema/src/keywords/min_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
keywords::{helpers::fail_on_non_positive_integer, CompilationResult},
paths::{JSONPointer, JsonPointerNode},
validator::Validate,
Draft,
};
use serde_json::{Map, Value};

Expand All @@ -14,12 +15,27 @@ pub(crate) struct MinItemsValidator {

impl MinItemsValidator {
#[inline]
pub(crate) fn compile(schema: &Value, schema_path: JSONPointer) -> CompilationResult {
pub(crate) fn compile(
schema: &Value,
schema_path: JSONPointer,
draft: Draft,
) -> CompilationResult {
if let Some(limit) = schema.as_u64() {
Ok(Box::new(MinItemsValidator { limit, schema_path }))
} else {
Err(fail_on_non_positive_integer(schema, schema_path))
return Ok(Box::new(MinItemsValidator { limit, schema_path }));
}
if !matches!(draft, Draft::Draft4) {
if let Some(limit) = schema.as_f64() {
if limit.trunc() == limit {
#[allow(clippy::cast_possible_truncation)]
return Ok(Box::new(MinItemsValidator {
// NOTE: Imprecise cast as big integers are not supported yet
limit: limit as u64,
schema_path,
}));
}
}
}
Err(fail_on_non_positive_integer(schema, schema_path))
}
}

Expand Down Expand Up @@ -65,14 +81,23 @@ pub(crate) fn compile<'a>(
context: &CompilationContext,
) -> Option<CompilationResult<'a>> {
let schema_path = context.as_pointer_with("minItems");
Some(MinItemsValidator::compile(schema, schema_path))
Some(MinItemsValidator::compile(
schema,
schema_path,
context.config.draft(),
))
}

#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn with_decimal() {
tests_util::is_valid(&json!({"minItems": 1.0}), &json!([1, 2]));
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(&json!({"minItems": 1}), &json!([]), "/minItems")
Expand Down
35 changes: 30 additions & 5 deletions jsonschema/src/keywords/min_length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
keywords::{helpers::fail_on_non_positive_integer, CompilationResult},
paths::{JSONPointer, JsonPointerNode},
validator::Validate,
Draft,
};
use serde_json::{Map, Value};

Expand All @@ -14,12 +15,27 @@ pub(crate) struct MinLengthValidator {

impl MinLengthValidator {
#[inline]
pub(crate) fn compile(schema: &Value, schema_path: JSONPointer) -> CompilationResult {
pub(crate) fn compile(
schema: &Value,
schema_path: JSONPointer,
draft: Draft,
) -> CompilationResult {
if let Some(limit) = schema.as_u64() {
Ok(Box::new(MinLengthValidator { limit, schema_path }))
} else {
Err(fail_on_non_positive_integer(schema, schema_path))
return Ok(Box::new(MinLengthValidator { limit, schema_path }));
}
if !matches!(draft, Draft::Draft4) {
if let Some(limit) = schema.as_f64() {
if limit.trunc() == limit {
#[allow(clippy::cast_possible_truncation)]
return Ok(Box::new(MinLengthValidator {
// NOTE: Imprecise cast as big integers are not supported yet
limit: limit as u64,
schema_path,
}));
}
}
}
Err(fail_on_non_positive_integer(schema, schema_path))
}
}

Expand Down Expand Up @@ -65,14 +81,23 @@ pub(crate) fn compile<'a>(
context: &CompilationContext,
) -> Option<CompilationResult<'a>> {
let schema_path = context.as_pointer_with("minLength");
Some(MinLengthValidator::compile(schema, schema_path))
Some(MinLengthValidator::compile(
schema,
schema_path,
context.config.draft(),
))
}

#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::json;

#[test]
fn with_decimal() {
tests_util::is_valid(&json!({"minLength": 2.0}), &json!("foo"));
}

#[test]
fn schema_path() {
tests_util::assert_schema_path(&json!({"minLength": 1}), &json!(""), "/minLength")
Expand Down
Loading

0 comments on commit 64b08ac

Please sign in to comment.