Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/main/java/com/networknt/schema/keyword/ConstValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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) {
Comment thread
justin-tay marked this conversation as resolved.
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)
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/networknt/schema/keyword/EnumValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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());
Comment thread
justin-tay marked this conversation as resolved.
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/networknt/schema/utils/JsonNodeTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/networknt/schema/utils/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
70 changes: 70 additions & 0 deletions src/test/java/com/networknt/schema/ConstValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Error> 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<Error> 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<Error> 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<Error> 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());
}
}
72 changes: 72 additions & 0 deletions src/test/java/com/networknt/schema/EnumValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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<Error> 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<Error> 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<Error> 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<Error> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<Error> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Error> 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());
}
}
Loading
Loading