diff --git a/src/main/java/com/networknt/schema/keyword/ConstValidator.java b/src/main/java/com/networknt/schema/keyword/ConstValidator.java index 955e14d39..b2de2a8ed 100644 --- a/src/main/java/com/networknt/schema/keyword/ConstValidator.java +++ b/src/main/java/com/networknt/schema/keyword/ConstValidator.java @@ -20,6 +20,7 @@ import com.networknt.schema.Schema; import com.networknt.schema.SchemaLocation; import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonNodeTypes; import com.networknt.schema.SchemaContext; /** @@ -33,11 +34,27 @@ public ConstValidator(SchemaLocation schemaLocation, JsonNode schemaNode, public void validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, NodePath instanceLocation) { if (schemaNode.isNumber() && node.isNumber()) { - if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) { + boolean schemaIsNonFinite = JsonNodeTypes.isNonFiniteNumber(schemaNode); + boolean nodeIsNonFinite = JsonNodeTypes.isNonFiniteNumber(node); + if (schemaIsNonFinite != nodeIsNonFinite) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) - .evaluationPath(executionContext.getEvaluationPath()).locale(executionContext.getExecutionConfig().getLocale()) - .arguments(schemaNode.asString(schemaNode.toString()), node.asString()) - .build()); + .evaluationPath(executionContext.getEvaluationPath()) + .locale(executionContext.getExecutionConfig().getLocale()) + .arguments(schemaNode.asString(schemaNode.toString()), node.asString()).build()); + } else if (schemaIsNonFinite || nodeIsNonFinite) { + // Handle the NaN, Infinity and -Infinity cases + // Note that Double.compare(NaN, NaN) == 0 as this is comparing constants and not the numeric operation + if (Double.compare(schemaNode.doubleValue(), node.doubleValue()) != 0) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .locale(executionContext.getExecutionConfig().getLocale()) + .arguments(schemaNode.asString(schemaNode.toString()), node.asString()).build()); + } + } else if (schemaNode.decimalValue().compareTo(node.decimalValue()) != 0) { + executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) + .evaluationPath(executionContext.getEvaluationPath()) + .locale(executionContext.getExecutionConfig().getLocale()) + .arguments(schemaNode.asString(schemaNode.toString()), node.asString()).build()); } } else if (!schemaNode.equals(node)) { executionContext.addError(error().instanceNode(node).instanceLocation(instanceLocation) diff --git a/src/main/java/com/networknt/schema/keyword/EnumValidator.java b/src/main/java/com/networknt/schema/keyword/EnumValidator.java index ee56528df..0c9bbf3b3 100644 --- a/src/main/java/com/networknt/schema/keyword/EnumValidator.java +++ b/src/main/java/com/networknt/schema/keyword/EnumValidator.java @@ -19,11 +19,13 @@ import tools.jackson.databind.JsonNode; import tools.jackson.databind.node.ArrayNode; import tools.jackson.databind.node.DecimalNode; +import tools.jackson.databind.node.DoubleNode; import tools.jackson.databind.node.NullNode; import com.networknt.schema.ExecutionContext; import com.networknt.schema.Schema; import com.networknt.schema.SchemaLocation; import com.networknt.schema.path.NodePath; +import com.networknt.schema.utils.JsonNodeTypes; import com.networknt.schema.utils.JsonType; import com.networknt.schema.utils.TypeFactory; import com.networknt.schema.SchemaContext; @@ -129,6 +131,12 @@ private boolean isTypeLooseContainsInEnum(JsonNode node) { * @return the node */ protected JsonNode processNumberNode(JsonNode n) { + if (JsonNodeTypes.isNonFiniteNumber(n)) { + if (n.isDouble()) { // If it is already a DoubleNode don't create another one + return n; + } + return DoubleNode.valueOf(n.doubleValue()); + } return DecimalNode.valueOf(n.decimalValue().stripTrailingZeros()); } diff --git a/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java index 275e4703d..c3cf801fd 100644 --- a/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java +++ b/src/main/java/com/networknt/schema/keyword/MultipleOfValidator.java @@ -63,7 +63,7 @@ public void validate(ExecutionContext executionContext, JsonNode node, JsonNode protected BigDecimal getDivisor(JsonNode schemaNode) { if (schemaNode.isNumber()) { double divisor = schemaNode.doubleValue(); - if (divisor != 0) { + if (divisor != 0 && Double.isFinite(divisor)) { // convert to BigDecimal since double type is not accurate enough to do the // division and multiple return schemaNode.isBigDecimal() ? schemaNode.decimalValue().stripTrailingZeros() : BigDecimal.valueOf(divisor).stripTrailingZeros(); @@ -80,6 +80,11 @@ protected BigDecimal getDivisor(JsonNode schemaNode) { */ protected BigDecimal getDividend(JsonNode node) { if (node.isNumber()) { + // Handle NaN, Infinity and -Infinity + if (JsonNodeTypes.isNonFiniteNumber(node)) { + // Incorrect type as NaN, Infinity and -Infinity are not valid JSON numbers so return null + return null; + } // convert to BigDecimal since double type is not accurate enough to do the // division and multiple return node.isBigDecimal() ? node.decimalValue() : BigDecimal.valueOf(node.doubleValue()); diff --git a/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java index 51be22b37..ac33e4a23 100644 --- a/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java +++ b/src/main/java/com/networknt/schema/utils/JsonNodeTypes.java @@ -89,6 +89,9 @@ private static long detectVersion(SchemaContext schemaContext) { */ public static boolean isNumber(JsonNode node, SchemaRegistryConfig config) { if (node.isNumber()) { + if (isNonFiniteNumber(node)) { + return false; + } return true; } else if (config.isTypeLoose()) { if (TypeFactory.getValueNodeType(node, config) == JsonType.STRING) { @@ -98,6 +101,20 @@ public static boolean isNumber(JsonNode node, SchemaRegistryConfig config) { return false; } + /** + * Check if the node is a number and is one of NaN, Infinity or -Infinity + * + * @param node to check + * @return true if it is NaN, Infinity or -Infinity + */ + public static boolean isNonFiniteNumber(JsonNode node) { + if (node.isFloatingPointNumber() && !node.isBigDecimal() && !node.isBigInteger() + && !Double.isFinite(node.doubleValue())) { + return true; + } + return false; + } + private static boolean isEnumObjectSchema(Schema jsonSchema, ExecutionContext executionContext) { // There are three conditions for enum object schema diff --git a/src/main/java/com/networknt/schema/utils/TypeFactory.java b/src/main/java/com/networknt/schema/utils/TypeFactory.java index c4156e1ab..b5f446255 100644 --- a/src/main/java/com/networknt/schema/utils/TypeFactory.java +++ b/src/main/java/com/networknt/schema/utils/TypeFactory.java @@ -102,6 +102,10 @@ public static JsonType getValueNodeType(JsonNode node, SchemaRegistryConfig conf } else if (config != null && config.isLosslessNarrowing() && node.canConvertToExactIntegral()) { return JsonType.INTEGER; } else { + // Handle NaN, Infinity and -Infinity + if (JsonNodeTypes.isNonFiniteNumber(node)) { + return JsonType.UNKNOWN; + } return JsonType.NUMBER; } case BOOLEAN: diff --git a/src/test/java/com/networknt/schema/ConstValidatorTest.java b/src/test/java/com/networknt/schema/ConstValidatorTest.java index 585c0552a..c2c00dcb0 100644 --- a/src/test/java/com/networknt/schema/ConstValidatorTest.java +++ b/src/test/java/com/networknt/schema/ConstValidatorTest.java @@ -24,6 +24,10 @@ import org.junit.jupiter.api.Test; import com.networknt.schema.i18n.ResourceBundleMessageSource; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; /** * Test for ConstValidator. @@ -92,4 +96,70 @@ void invalidNumber() { assertFalse(messages.isEmpty()); } + @Test + void nan() { + String schemaData = "{\r\n" + + " \"const\": NaN\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "NaN"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); // Note that Double.compare(NaN, NaN) == 0 as this is comparing constants and + // not the numeric operation + } + + @Test + void infinity() { + String schemaData = "{\r\n" + + " \"const\": Infinity\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void negativeInfinity() { + String schemaData = "{\r\n" + + " \"const\": -Infinity\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "-Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"const\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "-Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + inputData = "Infinity"; + messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + inputData = "NaN"; + messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + } } diff --git a/src/test/java/com/networknt/schema/EnumValidatorTest.java b/src/test/java/com/networknt/schema/EnumValidatorTest.java index 2e78e3b5c..7fef622d6 100644 --- a/src/test/java/com/networknt/schema/EnumValidatorTest.java +++ b/src/test/java/com/networknt/schema/EnumValidatorTest.java @@ -22,6 +22,11 @@ import org.junit.jupiter.api.Test; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; + /** * EnumValidator test. */ @@ -108,4 +113,71 @@ void enumWithHeterogenousNodes() { Error message = messages.get(0); assertEquals(": does not have a value in the enumeration [6, \"foo\", [], true, {\"foo\":12}]", message.toString()); } + + @Test + void nan() { + String schemaData = "{\r\n" + + " \"enum\": [NaN]\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "NaN"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); // Note that Double.compare(NaN, NaN) == 0 as this is comparing constants and + // not the numeric operation + } + + @Test + void infinity() { + String schemaData = "{\r\n" + + " \"enum\": [Infinity]\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void negativeInfinity() { + String schemaData = "{\r\n" + + " \"enum\": [-Infinity]\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "-Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertTrue(messages.isEmpty()); + } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"enum\": [10]\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + String inputData = "-Infinity"; + List messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + inputData = "Infinity"; + messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + inputData = "NaN"; + messages = schema.validate(inputData, InputFormat.JSON); + assertFalse(messages.isEmpty()); + } } diff --git a/src/test/java/com/networknt/schema/ExclusiveMaximumValidatorTest.java b/src/test/java/com/networknt/schema/ExclusiveMaximumValidatorTest.java index 287b3e0be..9c9f70d62 100644 --- a/src/test/java/com/networknt/schema/ExclusiveMaximumValidatorTest.java +++ b/src/test/java/com/networknt/schema/ExclusiveMaximumValidatorTest.java @@ -17,9 +17,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.List; + import org.junit.jupiter.api.Test; import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; /** * Test ExclusiveMaximumValidator validator. @@ -37,4 +43,22 @@ void exclusiveMaximum() { final SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft7()); assertEquals(1, schemaRegistry.getSchema(schemaString).validate("10", InputFormat.JSON).size()); } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"exclusiveMaximum\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + } } diff --git a/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java b/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java index f5da433ed..d3dd1182b 100644 --- a/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java +++ b/src/test/java/com/networknt/schema/ExclusiveMinimumValidatorTest.java @@ -25,6 +25,10 @@ import com.networknt.schema.dialect.Dialect; import com.networknt.schema.dialect.Dialects; import com.networknt.schema.keyword.DisallowUnknownKeywordFactory; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; /** * Test ExclusiveMinimumValidator validator. @@ -105,4 +109,22 @@ void exclusiveMinimum() { final SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft7()); assertEquals(1, schemaRegistry.getSchema(schemaString).validate("10", InputFormat.JSON).size()); } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"exclusiveMinimum\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + } } diff --git a/src/test/java/com/networknt/schema/MaximumValidatorTest.java b/src/test/java/com/networknt/schema/MaximumValidatorTest.java index c7c96f4a5..99d4c10e6 100644 --- a/src/test/java/com/networknt/schema/MaximumValidatorTest.java +++ b/src/test/java/com/networknt/schema/MaximumValidatorTest.java @@ -16,12 +16,16 @@ package com.networknt.schema; +import tools.jackson.core.json.JsonReadFeature; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + import org.junit.jupiter.api.Test; import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.serialization.NodeReader; import java.io.IOException; import java.math.BigDecimal; @@ -35,6 +39,7 @@ class MaximumValidatorTest extends BaseJsonSchemaValidatorTest { private static final String INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s }"; private static final String NUMBER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": %s }"; + private static final String MAXIMUM = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"maximum\": %s }"; private static final String EXCLUSIVE_INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"maximum\": %s, \"exclusiveMaximum\": true}"; private static final SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); @@ -190,7 +195,7 @@ void negativeDoubleOverflowTest() throws IOException { for (String[] aTestCycle : values) { String maximum = aTestCycle[0]; String value = aTestCycle[1]; - String schema = format(NUMBER, maximum); + String schema = format(MAXIMUM, maximum); // Schema and document parsed with just double Schema v = factory.getSchema(mapper.readTree(schema)); @@ -260,7 +265,7 @@ void doubleValueCoarsing() throws IOException { */ @Test void doubleValueCoarsingExceedRange() throws IOException { - String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"maximum\": 1.7976931348623159e+308 }"; + String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"maximum\": 1.7976931348623159e+308 }"; String content = "1.7976931348623160e+308"; JsonNode doc = mapper.readTree(content); @@ -270,7 +275,7 @@ void doubleValueCoarsingExceedRange() throws IOException { assertTrue(messages.isEmpty(), "Validation should succeed as by default double values are used by mapper"); doc = bigDecimalMapper.readTree(content); - messages = v.validate(doc); + messages = v.validate(doc).stream().filter(e -> !e.getKeyword().equals("type")).toList();; // "1.7976931348623158e+308" == "1.7976931348623157e+308" == Double.MAX_VALUE // new BigDecimal("1.7976931348623158e+308").compareTo(new BigDecimal("1.7976931348623157e+308")) > 0 assertTrue(messages.isEmpty(), "Validation should success because the bug of bigDecimalMapper, it will treat 1.7976931348623159e+308 as INFINITY"); @@ -342,6 +347,24 @@ void maximumWithArrayType() { final SchemaRegistry schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft7()); assertEquals(1, schemaRegistry.getSchema(schemaString).validate("11", InputFormat.JSON).size()); } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"maximum\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + } } diff --git a/src/test/java/com/networknt/schema/MinimumValidatorTest.java b/src/test/java/com/networknt/schema/MinimumValidatorTest.java index d5ef94c71..423fbdc10 100644 --- a/src/test/java/com/networknt/schema/MinimumValidatorTest.java +++ b/src/test/java/com/networknt/schema/MinimumValidatorTest.java @@ -30,13 +30,17 @@ import org.junit.jupiter.api.Test; import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.serialization.NodeReader; +import tools.jackson.core.json.JsonReadFeature; import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; class MinimumValidatorTest { private static final String NUMBER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": %s }"; + private static final String MINIMUM = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"minimum\": %s }"; private static final String EXCLUSIVE_INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s, \"exclusiveMinimum\": true}"; private static final String INTEGER = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"integer\", \"minimum\": %s }"; private static final String NEGATIVE_MESSAGE_TEMPLATE = "Expecting validation errors, value %s is smaller than minimum %s"; @@ -174,7 +178,7 @@ void negativeDoubleOverflowTest() throws IOException { for (String[] aTestCycle : values) { String minimum = aTestCycle[0]; String value = aTestCycle[1]; - String schema = format(NUMBER, minimum); + String schema = format(MINIMUM, minimum); SchemaRegistryConfig config = SchemaRegistryConfig.builder().typeLoose(true).build(); SchemaRegistry factory = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, builder -> builder.schemaRegistryConfig(config)); @@ -249,7 +253,7 @@ void doubleValueCoarsing() throws IOException { */ @Test void doubleValueCoarsingExceedRange() throws IOException { - String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"type\": \"number\", \"minimum\": -1.7976931348623159e+308 }"; + String schema = "{ \"$schema\":\"http://json-schema.org/draft-04/schema#\", \"minimum\": -1.7976931348623159e+308 }"; String content = "-1.7976931348623160e+308"; JsonNode doc = mapper.readTree(content); @@ -315,6 +319,23 @@ void minimumWithArrayType() { assertEquals(1, schemaRegistry.getSchema(schemaString).validate("9", InputFormat.JSON).size()); } + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"minimum\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + } } diff --git a/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java b/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java index b18398e73..f00a25ce3 100644 --- a/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java +++ b/src/test/java/com/networknt/schema/MultipleOfValidatorTest.java @@ -21,6 +21,11 @@ import org.junit.jupiter.api.Test; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; + /** * Test MultipleOfValidator validator. */ @@ -100,4 +105,46 @@ void messageFormatPrecision() { List messages = schema.validate(inputData, InputFormat.JSON); assertEquals("must be multiple of 0.00001", messages.get(0).getMessage()); } + + @Test + void nonFinite() { + String schemaData = "{\r\n" + + " \"multipleOf\": 10\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(0, errors.size()); + } + + @Test + void nonFiniteSchemaShouldBeIgnored() { + String schemaData = "{\r\n" + + " \"multipleOf\": NaN\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(0, errors.size()); + } + + @Test + void stringSchemaShouldBeIgnored() { + String schemaData = "{\r\n" + + " \"multipleOf\": \"test\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4).getSchema(schemaData); + List errors = schema.validate("10", InputFormat.JSON); + assertEquals(0, errors.size()); + } } diff --git a/src/test/java/com/networknt/schema/TypeValidatorTest.java b/src/test/java/com/networknt/schema/TypeValidatorTest.java index 7c2feb91d..f3ad62330 100644 --- a/src/test/java/com/networknt/schema/TypeValidatorTest.java +++ b/src/test/java/com/networknt/schema/TypeValidatorTest.java @@ -22,6 +22,11 @@ import org.junit.jupiter.api.Test; +import com.networknt.schema.serialization.NodeReader; + +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.databind.json.JsonMapper; + /** * Test TypeValidator validator. */ @@ -179,4 +184,40 @@ void nullable() { final List errors = validator.validate(inputData, InputFormat.JSON); assertEquals(1, errors.size()); } + + @Test + void integerNonFinite() { + String schemaData = "{\r\n" + + " \"type\": \"integer\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(1, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(1, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(1, errors.size()); + } + + @Test + void numberNonFinite() { + String schemaData = "{\r\n" + + " \"type\": \"number\"\r\n" + + "}"; + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4, + builder -> builder.nodeReader(NodeReader.builder() + .jsonMapper(JsonMapper.builder().enable(JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS).build()) + .build())) + .getSchema(schemaData); + List errors = schema.validate("NaN", InputFormat.JSON); + assertEquals(1, errors.size()); + errors = schema.validate("Infinity", InputFormat.JSON); + assertEquals(1, errors.size()); + errors = schema.validate("-Infinity", InputFormat.JSON); + assertEquals(1, errors.size()); + } }