diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java index d964defa..1807305b 100644 --- a/src/main/java/com/networknt/schema/OneOfValidator.java +++ b/src/main/java/com/networknt/schema/OneOfValidator.java @@ -16,13 +16,19 @@ package com.networknt.schema; -import com.fasterxml.jackson.databind.JsonNode; -import com.networknt.schema.utils.SetView; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.networknt.schema.utils.SetView; /** * {@link JsonValidator} for oneOf. @@ -75,10 +81,15 @@ protected Set validate(ExecutionContext executionContext, Jso if (this.validationContext.getConfig().isDiscriminatorKeywordEnabled()) { DiscriminatorContext discriminatorContext = new DiscriminatorContext(); executionContext.enterDiscriminatorContext(discriminatorContext, instanceLocation); - + // check if discriminator present discriminator = (DiscriminatorValidator) this.getParentSchema().getValidators().stream() .filter(v -> "discriminator".equals(v.getKeyword())).findFirst().orElse(null); + if (discriminator != null) { + // this is just to make the discriminator context active + discriminatorContext.registerDiscriminator(discriminator.getSchemaLocation(), + (ObjectNode) discriminator.getSchemaNode()); + } } executionContext.setFailFast(false); for (JsonSchema schema : this.schemas) { @@ -135,7 +146,8 @@ protected Set validate(ExecutionContext executionContext, Jso // found is null triggers on the correct schema childErrors = new SetView<>(); childErrors.union(schemaErrors); - } else if (currentDiscriminatorContext.isDiscriminatorIgnore()) { + } else if (currentDiscriminatorContext.isDiscriminatorIgnore() + || !currentDiscriminatorContext.isActive()) { // This is the normal handling when discriminators aren't enabled if (childErrors == null) { childErrors = new SetView<>(); diff --git a/src/test/java/com/networknt/schema/OneOfValidatorTest.java b/src/test/java/com/networknt/schema/OneOfValidatorTest.java index 70b4f13f..4fdb14a0 100644 --- a/src/test/java/com/networknt/schema/OneOfValidatorTest.java +++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java @@ -284,4 +284,189 @@ void fixedSwaggerIoExample() { + "}"; assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN)); } + + /** + * Test for when the discriminator keyword is enabled but no discriminator is + * present in the schema. This should process as a normal oneOf and return the + * error messages. + */ + @Test + void oneOfDiscriminatorEnabled() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " ]\r\n" + + "}"; + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, + SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build()); + String inputData = "{}"; + Set messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(3, messages.size()); + } + + /** + * Standard case where the discriminator is in the same schema as oneOf. + *

+ * Note that discriminators do not affect the validation result and can only + * affect the messages returned. + */ + @Test + void oneOfDiscriminatorEnabledWithDiscriminator() { + String schemaData = "{\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"type\",\r\n" + + " \"mapping\": {\r\n" + + " \"string\": \"#/$defs/string\",\r\n" + + " \"number\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " },\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"string\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"number\": {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, + SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build()); + // Valid + String inputData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": 1\r\n" + + "}"; + Set messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Invalid only 1 message returned for number + String inputData2 = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": {}\r\n" + + "}"; + Set messages2 = schema.validate(inputData2, InputFormat.JSON); + assertEquals(2, messages2.size()); + + // Invalid both messages for string and object returned + JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, + SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build()); + Set messages3 = schema2.validate(inputData2, InputFormat.JSON); + assertEquals(3, messages3.size()); + } + + /** + * Subclass case where the discriminator is in an allOf inside one of the oneOf references. + *

+ * Note that discriminators do not affect the validation result and can only + * affect the messages returned. + */ + @Test + void oneOfDiscriminatorEnabledWithDiscriminatorInSubclass() { + String schemaData = "{\r\n" + + " \"oneOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/string\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " ],\r\n" + + " \"$defs\": {\r\n" + + " \"typed\": {\r\n" + + " \"discriminator\": {\r\n" + + " \"propertyName\": \"type\",\r\n" + + " \"mapping\": {\r\n" + + " \"string\": \"#/$defs/string\",\r\n" + + " \"number\": \"#/$defs/number\"\r\n" + + " }\r\n" + + " }\r\n" + + " },\r\n" + + " \"string\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/typed\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"string\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + " },\r\n" + + " \"number\": {\r\n" + + " \"allOf\": [\r\n" + + " {\r\n" + + " \"$ref\": \"#/$defs/typed\"\r\n" + + " },\r\n" + + " {\r\n" + + " \"properties\": {\r\n" + + " \"type\": {\r\n" + + " \"type\": \"string\"\r\n" + + " },\r\n" + + " \"value\": {\r\n" + + " \"type\": \"number\"\r\n" + + " }\r\n" + + " }\r\n" + + " }\r\n" + + " ]\r\n" + + " }\r\n" + + " }\r\n" + + "}"; + JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, + SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build()); + // Valid + String inputData = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": 1\r\n" + + "}"; + Set messages = schema.validate(inputData, InputFormat.JSON); + assertEquals(0, messages.size()); + + // Invalid only 1 message returned for number + String inputData2 = "{\r\n" + + " \"type\": \"number\",\r\n" + + " \"value\": {}\r\n" + + "}"; + Set messages2 = schema.validate(inputData2, InputFormat.JSON); + assertEquals(2, messages2.size()); + + // Invalid both messages for string and object returned + JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, + SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build()); + Set messages3 = schema2.validate(inputData2, InputFormat.JSON); + assertEquals(3, messages3.size()); + } + }