Skip to content

Commit

Permalink
Add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDiekmann committed Sep 26, 2024
1 parent 4456115 commit 120de69
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ fn float_less(lhs: f64, rhs: f64) -> bool {
float_less_eq(lhs, rhs) && !float_eq(lhs, rhs)
}

#[expect(
clippy::float_arithmetic,
reason = "Validation requires floating point arithmetic"
)]
fn float_multiple_of(lhs: f64, rhs: f64) -> bool {
if float_eq(rhs, 0.0) {
return false;
}
let quotient = lhs / rhs;
(quotient - quotient.floor()).abs() < f64::EPSILON
}

impl NumberSchema {
/// Validates the provided value against the number schema.
///
Expand Down Expand Up @@ -211,12 +223,7 @@ impl NumberConstraints {
}

if let Some(expected) = self.multiple_of {
#[expect(
clippy::float_arithmetic,
clippy::modulo_arithmetic,
reason = "Validation requires floating point arithmetic"
)]
if !float_eq(number % expected, 0.0) {
if !float_multiple_of(number, expected) {
status.capture(NumberValidationError::MultipleOf {
actual: number,
expected,
Expand Down Expand Up @@ -272,6 +279,20 @@ mod tests {
assert!(!float_less_eq(1.0, 1.0 - f64::EPSILON));
}

#[test]
fn compare_modulo() {
assert!(float_multiple_of(10.0, 5.0));
assert!(!float_multiple_of(10.0, 3.0));
assert!(float_multiple_of(10.0, 2.5));
assert!(float_multiple_of(1e9, 1e6));
assert!(float_multiple_of(0.0001, 0.00001));
assert!(float_multiple_of(-10.0, -5.0));
assert!(float_multiple_of(-10.0, 5.0));
assert!(!float_multiple_of(10.0, 0.0));
assert!(float_multiple_of(0.0, 5.0));
assert!(!float_multiple_of(0.1, 0.03));
}

#[test]
fn unconstrained() {
let number_schema = read_schema(&json!({
Expand All @@ -295,7 +316,8 @@ mod tests {
"maximum": 10.0,
}));

check_constraints(&number_schema, &json!(5));
check_constraints(&number_schema, &json!(0));
check_constraints(&number_schema, &json!(10));
check_constraints_error(&number_schema, &json!("2"), [
ConstraintError::InvalidType {
actual: JsonSchemaValueType::String,
Expand All @@ -316,6 +338,61 @@ mod tests {
]);
}

#[test]
fn simple_number_exclusive() {
let number_schema = read_schema(&json!({
"type": "number",
"minimum": 0.0,
"exclusiveMinimum": true,
"maximum": 10.0,
"exclusiveMaximum": true,
}));

check_constraints(&number_schema, &json!(0.1));
check_constraints(&number_schema, &json!(0.9));
check_constraints_error(&number_schema, &json!("2"), [
ConstraintError::InvalidType {
actual: JsonSchemaValueType::String,
expected: JsonSchemaValueType::Number,
},
]);
check_constraints_error(&number_schema, &json!(0), [
NumberValidationError::ExclusiveMinimum {
actual: 0.0,
expected: 0.0,
},
]);
check_constraints_error(&number_schema, &json!(10), [
NumberValidationError::ExclusiveMaximum {
actual: 10.0,
expected: 10.0,
},
]);
}

#[test]
fn multiple_of() {
let number_schema = read_schema(&json!({
"type": "number",
"multipleOf": 0.1,
}));

check_constraints(&number_schema, &json!(0.1));
check_constraints(&number_schema, &json!(0.9));
check_constraints_error(&number_schema, &json!("2"), [
ConstraintError::InvalidType {
actual: JsonSchemaValueType::String,
expected: JsonSchemaValueType::Number,
},
]);
check_constraints_error(&number_schema, &json!(0.11), [
NumberValidationError::MultipleOf {
actual: 0.11,
expected: 0.1,
},
]);
}

#[test]
fn constant() {
let number_schema = read_schema(&json!({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ pub enum StringTypeTag {

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))]
#[serde(untagged, rename_all = "camelCase")]
#[serde(untagged, rename_all = "camelCase", deny_unknown_fields)]
pub enum StringSchema {
Constrained(StringConstraints),
Const {
Expand Down Expand Up @@ -332,3 +332,128 @@ impl StringConstraints {
status.finish()
}
}

#[cfg(test)]
mod tests {
use serde_json::{from_value, json};

use super::*;
use crate::schema::{
JsonSchemaValueType,
data_type::constraint::{
ValueConstraints,
tests::{check_constraints, check_constraints_error, read_schema},
},
};
#[test]
fn unconstrained() {
let string_schema = read_schema(&json!({
"type": "string",
}));

check_constraints(&string_schema, &json!("NaN"));
check_constraints_error(&string_schema, &json!(10), [ConstraintError::InvalidType {
actual: JsonSchemaValueType::Number,
expected: JsonSchemaValueType::String,
}]);
}

#[test]
fn simple_string() {
let string_schema = read_schema(&json!({
"type": "string",
"minLength": 5,
"maxLength": 10,
}));

check_constraints(&string_schema, &json!("12345"));
check_constraints(&string_schema, &json!("1234567890"));
check_constraints_error(&string_schema, &json!(2), [ConstraintError::InvalidType {
actual: JsonSchemaValueType::Number,
expected: JsonSchemaValueType::String,
}]);
check_constraints_error(&string_schema, &json!("1234"), [
StringValidationError::MinLength {
actual: "1234".to_owned(),
expected: 5,
},
]);
check_constraints_error(&string_schema, &json!("12345678901"), [
StringValidationError::MaxLength {
actual: "12345678901".to_owned(),
expected: 10,
},
]);
}

#[test]
fn constant() {
let string_schema = read_schema(&json!({
"type": "string",
"const": "foo",
}));

check_constraints(&string_schema, &json!("foo"));
check_constraints_error(&string_schema, &json!("bar"), [
ConstraintError::InvalidConstValue {
actual: json!("bar"),
expected: json!("foo"),
},
]);
}

#[test]
fn enumeration() {
let string_schema = read_schema(&json!({
"type": "string",
"enum": ["foo"],
}));

check_constraints(&string_schema, &json!("foo"));
check_constraints_error(&string_schema, &json!("bar"), [
ConstraintError::InvalidEnumValue {
actual: json!("bar"),
expected: vec![json!("foo")],
},
]);
}

#[test]
fn missing_type() {
from_value::<ValueConstraints>(json!({
"minLength": 0.0,
}))
.expect_err("Deserialized string schema without type");
}

#[test]
fn additional_string_properties() {
from_value::<ValueConstraints>(json!({
"type": "string",
"additional": false,
}))
.expect_err("Deserialized string schema with additional properties");
}

#[test]
fn mixed() {
from_value::<ValueConstraints>(json!({
"type": "string",
"const": "foo",
"minLength": 5,
}))
.expect_err("Deserialized string schema with mixed properties");
from_value::<ValueConstraints>(json!({
"type": "string",
"enum": ["foo", "bar"],
"minLength": 5,
}))
.expect_err("Deserialized string schema with mixed properties");
from_value::<ValueConstraints>(json!({
"type": "string",
"const": "bar",
"enum": ["foo", "bar"],
}))
.expect_err("Deserialized string schema with mixed properties");
}
}

0 comments on commit 120de69

Please sign in to comment.