Skip to content

Commit

Permalink
Explicitly handle if the discriminator property value is null (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
justin-tay authored Mar 16, 2024
1 parent 0f983b0 commit 6f44455
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 15 deletions.
7 changes: 5 additions & 2 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ && canShortCircuit() && canShortCircuit(executionContext)) {
// 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
Expand Down Expand Up @@ -137,7 +139,8 @@ && canShortCircuit() && canShortCircuit(executionContext)) {
}

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(
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/networknt/schema/BaseJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ protected static void checkDiscriminatorMatch(final DiscriminatorContext current
final String discriminatorPropertyValue,
final JsonSchema jsonSchema) {
if (discriminatorPropertyValue == null) {
currentDiscriminatorContext.markMatch();
currentDiscriminatorContext.markIgnore();
return;
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/networknt/schema/DiscriminatorContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -26,10 +28,25 @@ public void markMatch() {
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
*
Expand Down
37 changes: 26 additions & 11 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,23 +110,37 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
// 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
Expand All @@ -140,7 +154,8 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

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(
Expand Down
149 changes: 148 additions & 1 deletion src/test/java/com/networknt/schema/DiscriminatorValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ void discriminatorInOneOfShouldOnlyReportErrorsInMatchingDiscriminator() {
assertEquals("required", list.get(1).getType());
assertEquals("numberOfBeds", list.get(1).getProperty());
}

@Test
void discriminatorMappingInOneOfShouldOnlyReportErrorsInMatchingDiscriminator() {
String schemaData = "{\r\n"
Expand Down Expand Up @@ -640,4 +640,151 @@ void discriminatorMappingInOneOfShouldOnlyReportErrorsInMatchingDiscriminator()
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());
}
}

0 comments on commit 6f44455

Please sign in to comment.