diff --git a/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java index b072accf6..ea073f8ea 100644 --- a/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java +++ b/src/main/java/com/networknt/schema/UnevaluatedItemsValidator.java @@ -16,19 +16,11 @@ package com.networknt.schema; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - +import com.fasterxml.jackson.databind.JsonNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.utils.JsonNodeUtil; +import java.util.*; public class UnevaluatedItemsValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(UnevaluatedItemsValidator.class); @@ -49,7 +41,7 @@ public UnevaluatedItemsValidator(String schemaPath, JsonNode schemaNode, JsonSch @Override public Set validate(JsonNode node, JsonNode rootNode, String at) { - if (this.disabled) return Collections.emptySet(); + if (this.disabled || !node.isArray()) return Collections.emptySet(); debug(logger, node, rootNode, at); CollectorContext collectorContext = CollectorContext.getInstance(); @@ -92,21 +84,15 @@ public Set validate(JsonNode node, JsonNode rootNode, String } } - private static final Pattern NUMERIC = Pattern.compile("^\\d+$"); - private Set allPaths(JsonNode node, String at) { - return JsonNodeUtil.allPaths(getPathType(), at, node) - .stream() - .filter(this::isArray) - .collect(Collectors.toSet()); - } - - private boolean isArray(String path) { - String jsonPointer = getPathType().convertToJsonPointer(path); - String[] segment = jsonPointer.split("/"); - if (0 == segment.length) return false; - String lastSegment = segment[segment.length - 1]; - return NUMERIC.matcher(lastSegment).matches(); + PathType pathType = getPathType(); + Set collector = new HashSet<>(); + int size = node.size(); + for (int i = 0; i < size; ++i) { + String path = pathType.append(at, i); + collector.add(path); + } + return collector; } private Set reportUnevaluatedPaths(Set unevaluatedPaths) { diff --git a/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java index bac597486..82998a882 100644 --- a/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java +++ b/src/main/java/com/networknt/schema/UnevaluatedPropertiesValidator.java @@ -17,14 +17,10 @@ package com.networknt.schema; import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.utils.JsonNodeUtil; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; -import java.util.regex.Pattern; -import java.util.stream.Collectors; public class UnevaluatedPropertiesValidator extends BaseJsonValidator { private static final Logger logger = LoggerFactory.getLogger(UnevaluatedPropertiesValidator.class); @@ -45,7 +41,7 @@ public UnevaluatedPropertiesValidator(String schemaPath, JsonNode schemaNode, Js @Override public Set validate(JsonNode node, JsonNode rootNode, String at) { - if (this.disabled) return Collections.emptySet(); + if (this.disabled || !node.isObject()) return Collections.emptySet(); debug(logger, node, rootNode, at); CollectorContext collectorContext = CollectorContext.getInstance(); @@ -88,21 +84,14 @@ public Set validate(JsonNode node, JsonNode rootNode, String } } - private static final Pattern NUMERIC = Pattern.compile("^\\d+$"); - private Set allPaths(JsonNode node, String at) { - return JsonNodeUtil.allPaths(getPathType(), at, node) - .stream() - .filter(this::isProperty) - .collect(Collectors.toSet()); - } - - private boolean isProperty(String path) { - String jsonPointer = getPathType().convertToJsonPointer(path); - String[] segment = jsonPointer.split("/"); - if (0 == segment.length) return false; - String lastSegment = segment[segment.length - 1]; - return !NUMERIC.matcher(lastSegment).matches(); + PathType pathType = getPathType(); + Set collector = new HashSet<>(); + node.fields().forEachRemaining(entry -> { + String path = pathType.append(at, entry.getKey()); + collector.add(path); + }); + return collector; } private Set reportUnevaluatedPaths(Set unevaluatedPaths) { diff --git a/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java b/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java new file mode 100644 index 000000000..1b6a923be --- /dev/null +++ b/src/test/java/com/networknt/schema/UnevaluatedItemsTest.java @@ -0,0 +1,19 @@ +package com.networknt.schema; + +import com.networknt.schema.SpecVersion.VersionFlag; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import java.util.stream.Stream; + +@DisplayName("Unevaluated Items") +public class UnevaluatedItemsTest extends AbstractJsonSchemaTestSuite { + + @TestFactory + @DisplayName("Draft 2019-09") + Stream draft201909() { + return createTests(VersionFlag.V201909, "src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json"); + } + +} diff --git a/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json b/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json new file mode 100644 index 000000000..f9eadfc36 --- /dev/null +++ b/src/test/resources/schema/unevaluatedTests/unevaluated-items-tests.json @@ -0,0 +1,21 @@ +[ + { + "description": "unevaluatedItems should not affect sub-schemas", + "schema": { + "unevaluatedItems": { + "type": "object" + } + }, + "tests": [ + { + "description": "unevaluated item bar is in sub-schema", + "data": [ + { + "foo": ["bar"] + } + ], + "valid": true + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json b/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json index 1103bc7d9..05867ff6a 100644 --- a/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json +++ b/src/test/resources/schema/unevaluatedTests/unevaluated-tests.json @@ -27,15 +27,18 @@ "unevaluatedProperties": false }, "residence": { - "flatNumber": { - "type": "string" - }, - "flatName": { - "type": "string" + "properties": { + "flatNumber": { + "type": "string" + }, + "flatName": { + "type": "string" + }, + "landmark": { + "type": "string" + } }, - "landmark": { - "type": "string" - } + "unevaluatedProperties": false } }, "properties": { @@ -202,10 +205,11 @@ } } } - ] - } - }, - "unevaluatedProperties": false + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } }, "tests": [ { @@ -220,6 +224,22 @@ }, "valid": true }, + { + "description": "Data which satisfies 1 oneOf schemas, but fails due to unevaluated prop", + "data": { + "firstName": "First Name", + "age": 18, + "lastName": "Last Name", + "vehicle": { + "pontoons": "pontoons", + "wheels": "wheels" + } + }, + "valid": false, + "validationMessages": [ + "There are unevaluated properties at the following paths $.vehicle.wheels" + ] + }, { "description": "Data which satisfies 2 oneOf schemas", "data": { @@ -328,10 +348,11 @@ } } } - ] - } - }, - "unevaluatedProperties": false + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } }, "tests": [ { @@ -453,10 +474,11 @@ } } } - ] - } - }, - "unevaluatedProperties": false + ], + "unevaluatedProperties": false + }, + "unevaluatedProperties": false + } }, "tests": [ { @@ -634,6 +656,26 @@ } ] }, + { + "description": "unevaluatedProperties should not affect sub-schemas", + "schema": { + "properties": { + "foo": {} + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluated property bar is in sub-schema", + "data": { + "foo": { + "bar": "baz" + } + }, + "valid": true + } + ] + }, { "description": "unevaluatedProperties true", "schema": {