Skip to content

Commit

Permalink
Fix oneOf when discriminator enabled but no discriminator (#1078)
Browse files Browse the repository at this point in the history
  • Loading branch information
justin-tay authored Jul 1, 2024
1 parent 92bef22 commit bbbbd1c
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 5 deletions.
22 changes: 17 additions & 5 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -75,10 +81,15 @@ protected Set<ValidationMessage> 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) {
Expand Down Expand Up @@ -135,7 +146,8 @@ protected Set<ValidationMessage> 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<>();
Expand Down
185 changes: 185 additions & 0 deletions src/test/java/com/networknt/schema/OneOfValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
assertEquals(3, messages.size());
}

/**
* Standard case where the discriminator is in the same schema as oneOf.
* <p>
* 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<ValidationMessage> 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<ValidationMessage> 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<ValidationMessage> 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.
* <p>
* 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<ValidationMessage> 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<ValidationMessage> 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<ValidationMessage> messages3 = schema2.validate(inputData2, InputFormat.JSON);
assertEquals(3, messages3.size());
}

}

0 comments on commit bbbbd1c

Please sign in to comment.