Skip to content

Commit

Permalink
Issue428 (networknt#431)
Browse files Browse the repository at this point in the history
* Issue428: Handle nullable for oneOf and the child node

* Issue428: Fix the duplicate validation for the oneOf type
  • Loading branch information
rongyj authored Aug 12, 2021
1 parent b50c7cf commit 36095ff
Show file tree
Hide file tree
Showing 7 changed files with 458 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ dist/
.settings
.metadata/
*.iml
*.ipr
*.iws
*.log
*.tmp
*.zip
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.57</version>
<version>1.0.58-SNAPHSOT</version>
<packaging>bundle</packaging>
<description>A json schema validator that supports draft v4, v6, v7 and v2019-09</description>
<url>https://github.com/networknt/json-schema-validator</url>
Expand Down Expand Up @@ -59,7 +59,7 @@
</repository>
</distributionManagement>
<properties>
<java.version>1.6</java.version>
<java.version>1.8</java.version>
<java.testversion>1.8</java.testversion>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.jackson>2.12.1</version.jackson>
Expand Down
27 changes: 19 additions & 8 deletions src/main/java/com/networknt/schema/OneOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.utils.JsonNodeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -154,6 +158,11 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
continue;
}*/

//Check to see if it is already validated.
if(!childErrors.isEmpty() && JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, super.config))){
continue;
}

// get the current validator
JsonSchema schema = validator.schema;
if (!state.isWalkEnabled()) {
Expand All @@ -162,25 +171,27 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabled());
}


// check if any validation errors have occurred
if (schemaErrors.isEmpty()) {
// check whether there are no errors HOWEVER we have validated the exact validator
if (!state.hasMatchedNode())
continue;

numberOfValidSchema++;
else
numberOfValidSchema++;
}
childErrors.addAll(schemaErrors);
}


// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
if(numberOfValidSchema > 1){
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
if( failFast ) {
throw new JsonSchemaException(message);
// check if the parent schema declares the fields as nullable
if (!JsonType.NULL.equals(TypeFactory.getValueNodeType(node,config)) || !JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode(),config) && !JsonNodeUtil.isChildNodeNullable((ArrayNode) schemaNode,config)) {
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
if (failFast) {
throw new JsonSchemaException(message);
}
errors.add(message);
}
errors.add(message);
}
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
else if (numberOfValidSchema < 1) {
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/com/networknt/schema/TypeValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.TextNode;
import com.networknt.schema.utils.JsonNodeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

public class TypeValidator extends BaseJsonValidator implements JsonValidator {
Expand Down Expand Up @@ -59,15 +63,18 @@ public boolean equalsToSchemaType(JsonNode node) {
if (schemaType == JsonType.ANY) {
return true;
}

if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) {
return true;
}
if (nodeType == JsonType.NULL) {
JsonNode nullable = this.getParentSchema().getSchemaNode().get("nullable");
if (nullable != null && nullable.asBoolean()) {

ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
if(JsonType.NULL.equals(nodeType) ){
if((state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), config)) || JsonNodeUtil.isNodeNullable(this.getParentSchema().getSchemaNode()) ){
return true;
}
}

// Skip the type validation when the schema is an enum object schema. Since the current type
// of node itself can be used for type validation.
if (isEnumObjectSchema(parentSchema)) {
Expand All @@ -94,6 +101,7 @@ public boolean equalsToSchemaType(JsonNode node) {
}
}
}

return false;
}
return true;
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/com/networknt/schema/utils/JsonNodeUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.networknt.schema.utils;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.networknt.schema.CollectorContext;
import com.networknt.schema.JsonType;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.ValidatorState;

import java.util.Iterator;

public class JsonNodeUtil {

public static boolean isNodeNullable(JsonNode schema){
JsonNode nullable = schema.get("nullable");
if (nullable != null && nullable.asBoolean()) {
return true;
}
return false;
}

//Check to see if a JsonNode is nullable with checking the isHandleNullableField
public static boolean isNodeNullable(JsonNode schema, SchemaValidatorsConfig config){
// check if the parent schema declares the fields as nullable
if (config.isHandleNullableField()) {
return isNodeNullable(schema);
}
return false;
}

//Check to see if any child node for the OneOf SchemaNode is nullable
public static boolean isChildNodeNullable(ArrayNode oneOfSchemaNode,SchemaValidatorsConfig config){
Iterator iterator = oneOfSchemaNode.elements();
while(iterator.hasNext()){
//If one of the child Node for oneOf is nullable, it means the whole oneOf is nullable
if (isNodeNullable((JsonNode)iterator.next(),config)) return true;
}
return false;
}

public static boolean matchOneOfTypeNode(JsonNode oneOfSchemaNode, JsonType nodeType ){
Iterator iterator = oneOfSchemaNode.elements();
while (iterator.hasNext()){
JsonNode oneOfTypeNode = (JsonNode) iterator.next();
JsonNode typeTextNode = oneOfTypeNode.get("type");
if(typeTextNode != null && typeTextNode.asText().equals(nodeType.toString())) //If the nodeType is oneOf the type defined in the oneOf , return true
return true;
}
return false;
}
}
125 changes: 125 additions & 0 deletions src/test/java/com/networknt/schema/Issue428Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import io.undertow.Undertow;
import io.undertow.server.handlers.resource.FileResourceManager;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import static io.undertow.Handlers.resource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

public class Issue428Test {
protected ObjectMapper mapper = new ObjectMapper();
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build();
protected static Undertow server = null;

public Issue428Test() {
}

@BeforeClass
public static void setUp() {
if (server == null) {
server = Undertow.builder()
.addHttpListener(1234, "localhost")
.setHandler(resource(new FileResourceManager(
new File("./src/test/resources/remotes"), 100)))
.build();
server.start();
}
}

@AfterClass
public static void tearDown() throws Exception {
if (server != null) {
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {

}
server.stop();
}
}

private void runTestFile(String testCaseFile) throws Exception {
final URI testCaseFileUri = URI.create("classpath:" + testCaseFile);
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(testCaseFile);
ArrayNode testCases = mapper.readValue(in, ArrayNode.class);

for (int j = 0; j < testCases.size(); j++) {
try {
JsonNode testCase = testCases.get(j);
SchemaValidatorsConfig config = new SchemaValidatorsConfig();

ArrayNode testNodes = (ArrayNode) testCase.get("tests");
for (int i = 0; i < testNodes.size(); i++) {
JsonNode test = testNodes.get(i);
System.out.println("=== " + test.get("description"));
JsonNode node = test.get("data");
JsonNode typeLooseNode = test.get("isTypeLoose");
// Configure the schemaValidator to set typeLoose's value based on the test file,
// if test file do not contains typeLoose flag, use default value: true.
config.setTypeLoose(typeLooseNode != null && typeLooseNode.asBoolean());
config.setOpenAPI3StyleDiscriminators(false);
JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config);

List<ValidationMessage> errors = new ArrayList<ValidationMessage>(schema.validate(node));

if (test.get("valid").asBoolean()) {
if (!errors.isEmpty()) {
System.out.println("---- test case failed ----");
System.out.println("schema: " + schema.toString());
System.out.println("data: " + test.get("data"));
System.out.println("errors:");
for (ValidationMessage error : errors) {
System.out.println(error);
}
}
//assertEquals(2, errors.size());
} else {
if (errors.isEmpty()) {
System.out.println("---- test case failed ----");
System.out.println("schema: " + schema);
System.out.println("data: " + test.get("data"));
} else {
JsonNode errorCount = test.get("errorCount");
if (errorCount != null && errorCount.isInt() && errors.size() != errorCount.asInt()) {
System.out.println("---- test case failed ----");
System.out.println("schema: " + schema);
System.out.println("data: " + test.get("data"));
System.out.println("errors: " + errors);
for (ValidationMessage error : errors) {
System.out.println(error);
}
assertEquals("expected error count", errorCount.asInt(), errors.size());
}
}
assertFalse(errors.isEmpty());
}

}


} catch (JsonSchemaException e) {
throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e);
}
}
}

@Test
public void testNullableOneOf() throws Exception {
runTestFile("data/issue428.json");
}
}
Loading

0 comments on commit 36095ff

Please sign in to comment.