summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Tay <49700559+justin-tay@users.noreply.github.com>2024-03-17 06:06:43 +0800
committerGitHub <noreply@github.com>2024-03-16 18:06:43 -0400
commit6f4445591e5a1f410a9494863a4132f52c6e507a (patch)
tree6510a9bebe3b05aa915d71cf0a1150939da03c91
parent0f983b05ed7478c55db1dc0d820f60f0becaf787 (diff)
downloadjson-schema-validator-6f4445591e5a1f410a9494863a4132f52c6e507a.tar.gz
Explicitly handle if the discriminator property value is null (#988)
-rw-r--r--src/main/java/com/networknt/schema/AnyOfValidator.java7
-rw-r--r--src/main/java/com/networknt/schema/BaseJsonValidator.java2
-rw-r--r--src/main/java/com/networknt/schema/DiscriminatorContext.java17
-rw-r--r--src/main/java/com/networknt/schema/OneOfValidator.java37
-rw-r--r--src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java149
5 files changed, 197 insertions, 15 deletions
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
index 7df6501..931c4e3 100644
--- a/src/main/java/com/networknt/schema/AnyOfValidator.java
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -106,7 +106,9 @@ public class AnyOfValidator extends BaseJsonValidator {
// return empty errors.
return errors;
} else if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
- if (executionContext.getCurrentDiscriminatorContext().isDiscriminatorMatchFound()) {
+ DiscriminatorContext currentDiscriminatorContext = executionContext.getCurrentDiscriminatorContext();
+ if (currentDiscriminatorContext.isDiscriminatorMatchFound()
+ || currentDiscriminatorContext.isDiscriminatorIgnore()) {
if (!errors.isEmpty()) {
// The following is to match the previous logic adding to all errors
// which is generally discarded as it returns errors but the allErrors
@@ -137,7 +139,8 @@ public class AnyOfValidator extends BaseJsonValidator {
}
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()
- && executionContext.getCurrentDiscriminatorContext().isActive()) {
+ && executionContext.getCurrentDiscriminatorContext().isActive()
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorIgnore()) {
return Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.arguments(
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index 75c2a08..eebcd9c 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -114,7 +114,7 @@ public abstract class BaseJsonValidator extends ValidationMessageHandler impleme
final String discriminatorPropertyValue,
final JsonSchema jsonSchema) {
if (discriminatorPropertyValue == null) {
- currentDiscriminatorContext.markMatch();
+ currentDiscriminatorContext.markIgnore();
return;
}
diff --git a/src/main/java/com/networknt/schema/DiscriminatorContext.java b/src/main/java/com/networknt/schema/DiscriminatorContext.java
index 5ffeac3..1ab2ddd 100644
--- a/src/main/java/com/networknt/schema/DiscriminatorContext.java
+++ b/src/main/java/com/networknt/schema/DiscriminatorContext.java
@@ -10,6 +10,8 @@ public class DiscriminatorContext {
private boolean discriminatorMatchFound = false;
+ private boolean discriminatorIgnore = false;
+
public void registerDiscriminator(final SchemaLocation schemaLocation, final ObjectNode discriminator) {
this.discriminators.put("#" + schemaLocation.getFragment().toString(), discriminator);
}
@@ -26,10 +28,25 @@ public class DiscriminatorContext {
this.discriminatorMatchFound = true;
}
+ /**
+ * Indicate that discriminator processing should be ignored.
+ * <p>
+ * This is used when the discriminator property value is missing from the data.
+ * <p>
+ * See issue #436 for background.
+ */
+ public void markIgnore() {
+ this.discriminatorIgnore = true;
+ }
+
public boolean isDiscriminatorMatchFound() {
return this.discriminatorMatchFound;
}
+ public boolean isDiscriminatorIgnore() {
+ return this.discriminatorIgnore;
+ }
+
/**
* Returns true if we have a discriminator active. In this case no valid match in anyOf should lead to validation failure
*
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
index 3a5b91a..3ba564a 100644
--- a/src/main/java/com/networknt/schema/OneOfValidator.java
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -110,23 +110,37 @@ public class OneOfValidator extends BaseJsonValidator {
// matching discriminator to be discarded. Note that the discriminator cannot
// affect the actual validation result.
if (discriminator != null && !discriminator.getPropertyName().isEmpty()) {
- String discriminatorPropertyValue = node.get(discriminator.getPropertyName()).asText();
- discriminatorPropertyValue = discriminator.getMapping().getOrDefault(discriminatorPropertyValue,
- discriminatorPropertyValue);
- JsonNode refNode = schema.getSchemaNode().get("$ref");
- if (refNode != null) {
- String ref = refNode.asText();
- if (ref.equals(discriminatorPropertyValue) || ref.endsWith("/" + discriminatorPropertyValue)) {
- executionContext.getCurrentDiscriminatorContext().markMatch();
+ JsonNode discriminatorPropertyNode = node.get(discriminator.getPropertyName());
+ if (discriminatorPropertyNode != null) {
+ String discriminatorPropertyValue = discriminatorPropertyNode.asText();
+ discriminatorPropertyValue = discriminator.getMapping().getOrDefault(discriminatorPropertyValue,
+ discriminatorPropertyValue);
+ JsonNode refNode = schema.getSchemaNode().get("$ref");
+ if (refNode != null) {
+ String ref = refNode.asText();
+ if (ref.equals(discriminatorPropertyValue) || ref.endsWith("/" + discriminatorPropertyValue)) {
+ executionContext.getCurrentDiscriminatorContext().markMatch();
+ }
}
+ } else {
+ // See issue 436 where the condition was relaxed to not cause an assertion
+ // due to missing discriminator property value
+ // Also see BaseJsonValidator#checkDiscriminatorMatch
+ executionContext.getCurrentDiscriminatorContext().markIgnore();
}
}
- boolean discriminatorMatchFound = executionContext.getCurrentDiscriminatorContext().isDiscriminatorMatchFound();
- if (discriminatorMatchFound && childErrors == null) {
+ DiscriminatorContext currentDiscriminatorContext = executionContext.getCurrentDiscriminatorContext();
+ if (currentDiscriminatorContext.isDiscriminatorMatchFound() && childErrors == null) {
// Note that the match is set if found and not reset so checking if childErrors
// found is null triggers on the correct schema
childErrors = new SetView<>();
childErrors.union(schemaErrors);
+ } else if (currentDiscriminatorContext.isDiscriminatorIgnore()) {
+ // This is the normal handling when discriminators aren't enabled
+ if (childErrors == null) {
+ childErrors = new SetView<>();
+ }
+ childErrors.union(schemaErrors);
}
} else if (!schemaErrors.isEmpty() && reportChildErrors(executionContext)) {
// This is the normal handling when discriminators aren't enabled
@@ -140,7 +154,8 @@ public class OneOfValidator extends BaseJsonValidator {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()
&& (discriminator != null || executionContext.getCurrentDiscriminatorContext().isActive())
- && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorMatchFound()) {
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorMatchFound()
+ && !executionContext.getCurrentDiscriminatorContext().isDiscriminatorIgnore()) {
errors = Collections.singleton(message().instanceNode(node).instanceLocation(instanceLocation)
.locale(executionContext.getExecutionConfig().getLocale())
.arguments(
diff --git a/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java
index f6a95f5..eb2f92f 100644
--- a/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java
+++ b/src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java
@@ -538,7 +538,7 @@ public class DiscriminatorValidatorTest {
assertEquals("required", list.get(1).getType());
assertEquals("numberOfBeds", list.get(1).getProperty());
}
-
+
@Test
void discriminatorMappingInOneOfShouldOnlyReportErrorsInMatchingDiscriminator() {
String schemaData = "{\r\n"
@@ -640,4 +640,151 @@ public class DiscriminatorValidatorTest {
assertEquals("numberOfBeds", list.get(1).getProperty());
}
+ /**
+ * See issue 436 and 985.
+ */
+ @Test
+ void oneOfMissingDiscriminatorValue() {
+ String schemaData = " {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"discriminator\": { \"propertyName\": \"name\" },\r\n"
+ + " \"oneOf\": [\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/defs/Foo\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/defs/Bar\"\r\n"
+ + " }\r\n"
+ + " ],\r\n"
+ + " \"defs\": {\r\n"
+ + " \"Foo\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"const\": \"Foo\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"name\" ],\r\n"
+ + " \"additionalProperties\": false\r\n"
+ + " },\r\n"
+ + " \"Bar\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"name\": {\r\n"
+ + " \"const\": \"Bar\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [ \"name\" ],\r\n"
+ + " \"additionalProperties\": false\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }";
+
+ String inputData = "{}";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ assertEquals(3, messages.size());
+ List<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("oneOf", list.get(0).getType());
+ assertEquals("required", list.get(1).getType());
+ assertEquals("required", list.get(2).getType());
+ }
+
+ /**
+ * See issue 436.
+ */
+ @Test
+ void anyOfMissingDiscriminatorValue() {
+ String schemaData = "{\r\n"
+ + " \"type\": \"array\",\r\n"
+ + " \"items\": {\r\n"
+ + " \"anyOf\": [\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/components/schemas/Kitchen\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/components/schemas/BedRoom\"\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + " },\r\n"
+ + " \"components\": {\r\n"
+ + " \"schemas\": {\r\n"
+ + " \"Room\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"@type\": {\r\n"
+ + " \"type\": \"string\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [\r\n"
+ + " \"@type\"\r\n"
+ + " ],\r\n"
+ + " \"discriminator\": {\r\n"
+ + " \"propertyName\": \"@type\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"BedRoom\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"allOf\": [\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/components/schemas/Room\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"numberOfBeds\": {\r\n"
+ + " \"type\": \"integer\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [\r\n"
+ + " \"numberOfBeds\"\r\n"
+ + " ]\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + " },\r\n"
+ + " \"Kitchen\": {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"allOf\": [\r\n"
+ + " {\r\n"
+ + " \"$ref\": \"#/components/schemas/Room\"\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"type\": \"object\",\r\n"
+ + " \"properties\": {\r\n"
+ + " \"hasMicrowaveOven\": {\r\n"
+ + " \"type\": \"boolean\"\r\n"
+ + " }\r\n"
+ + " },\r\n"
+ + " \"required\": [\r\n"
+ + " \"hasMicrowaveOven\"\r\n"
+ + " ]\r\n"
+ + " }\r\n"
+ + " ]\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + " }\r\n"
+ + "}";
+
+ String inputData = "[\r\n"
+ + " {\r\n"
+ + " \"hasMicrowaveOven\": true\r\n"
+ + " },\r\n"
+ + " {\r\n"
+ + " \"@type\": \"BedRoom\",\r\n"
+ + " \"numberOfBeds\": 4\r\n"
+ + " }\r\n"
+ + "]";
+
+ JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
+ SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+ config.setOpenAPI3StyleDiscriminators(true);
+ JsonSchema schema = factory.getSchema(schemaData, config);
+ Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+ List<ValidationMessage> list = messages.stream().collect(Collectors.toList());
+ assertEquals("required", list.get(0).getType());
+ }
}