Skip to content

Commit 36095ff

Browse files
authored
Issue428 (networknt#431)
* Issue428: Handle nullable for oneOf and the child node * Issue428: Fix the duplicate validation for the oneOf type
1 parent b50c7cf commit 36095ff

File tree

7 files changed

+458
-13
lines changed

7 files changed

+458
-13
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ dist/
99
.settings
1010
.metadata/
1111
*.iml
12+
*.ipr
13+
*.iws
1214
*.log
1315
*.tmp
1416
*.zip

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<modelVersion>4.0.0</modelVersion>
2121
<groupId>com.networknt</groupId>
2222
<artifactId>json-schema-validator</artifactId>
23-
<version>1.0.57</version>
23+
<version>1.0.58-SNAPHSOT</version>
2424
<packaging>bundle</packaging>
2525
<description>A json schema validator that supports draft v4, v6, v7 and v2019-09</description>
2626
<url>https://github.com/networknt/json-schema-validator</url>
@@ -59,7 +59,7 @@
5959
</repository>
6060
</distributionManagement>
6161
<properties>
62-
<java.version>1.6</java.version>
62+
<java.version>1.8</java.version>
6363
<java.testversion>1.8</java.testversion>
6464
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
6565
<version.jackson>2.12.1</version.jackson>

src/main/java/com/networknt/schema/OneOfValidator.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
package com.networknt.schema;
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.ArrayNode;
21+
import com.fasterxml.jackson.databind.node.NullNode;
22+
import com.fasterxml.jackson.databind.node.ObjectNode;
23+
import com.networknt.schema.utils.JsonNodeUtil;
2024
import org.slf4j.Logger;
2125
import org.slf4j.LoggerFactory;
2226

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

161+
//Check to see if it is already validated.
162+
if(!childErrors.isEmpty() && JsonNodeUtil.matchOneOfTypeNode(schemaNode,TypeFactory.getValueNodeType(node, super.config))){
163+
continue;
164+
}
165+
157166
// get the current validator
158167
JsonSchema schema = validator.schema;
159168
if (!state.isWalkEnabled()) {
@@ -162,25 +171,27 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
162171
schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabled());
163172
}
164173

174+
165175
// check if any validation errors have occurred
166176
if (schemaErrors.isEmpty()) {
167177
// check whether there are no errors HOWEVER we have validated the exact validator
168178
if (!state.hasMatchedNode())
169179
continue;
170-
171-
numberOfValidSchema++;
180+
else
181+
numberOfValidSchema++;
172182
}
173183
childErrors.addAll(schemaErrors);
174184
}
175-
176-
177185
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
178186
if(numberOfValidSchema > 1){
179-
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
180-
if( failFast ) {
181-
throw new JsonSchemaException(message);
187+
// check if the parent schema declares the fields as nullable
188+
if (!JsonType.NULL.equals(TypeFactory.getValueNodeType(node,config)) || !JsonNodeUtil.isNodeNullable(parentSchema.getSchemaNode(),config) && !JsonNodeUtil.isChildNodeNullable((ArrayNode) schemaNode,config)) {
189+
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
190+
if (failFast) {
191+
throw new JsonSchemaException(message);
192+
}
193+
errors.add(message);
182194
}
183-
errors.add(message);
184195
}
185196
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
186197
else if (numberOfValidSchema < 1) {

src/main/java/com/networknt/schema/TypeValidator.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
package com.networknt.schema;
1818

1919
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.JsonNodeType;
21+
import com.fasterxml.jackson.databind.node.TextNode;
22+
import com.networknt.schema.utils.JsonNodeUtil;
2023
import org.slf4j.Logger;
2124
import org.slf4j.LoggerFactory;
2225

2326
import java.util.Collections;
27+
import java.util.Iterator;
2428
import java.util.Set;
2529

2630
public class TypeValidator extends BaseJsonValidator implements JsonValidator {
@@ -59,15 +63,18 @@ public boolean equalsToSchemaType(JsonNode node) {
5963
if (schemaType == JsonType.ANY) {
6064
return true;
6165
}
66+
6267
if (schemaType == JsonType.NUMBER && nodeType == JsonType.INTEGER) {
6368
return true;
6469
}
65-
if (nodeType == JsonType.NULL) {
66-
JsonNode nullable = this.getParentSchema().getSchemaNode().get("nullable");
67-
if (nullable != null && nullable.asBoolean()) {
70+
71+
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
72+
if(JsonType.NULL.equals(nodeType) ){
73+
if((state.isComplexValidator() && JsonNodeUtil.isNodeNullable(parentSchema.getParentSchema().getSchemaNode(), config)) || JsonNodeUtil.isNodeNullable(this.getParentSchema().getSchemaNode()) ){
6874
return true;
6975
}
7076
}
77+
7178
// Skip the type validation when the schema is an enum object schema. Since the current type
7279
// of node itself can be used for type validation.
7380
if (isEnumObjectSchema(parentSchema)) {
@@ -94,6 +101,7 @@ public boolean equalsToSchemaType(JsonNode node) {
94101
}
95102
}
96103
}
104+
97105
return false;
98106
}
99107
return true;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.networknt.schema.utils;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.ArrayNode;
5+
import com.networknt.schema.CollectorContext;
6+
import com.networknt.schema.JsonType;
7+
import com.networknt.schema.SchemaValidatorsConfig;
8+
import com.networknt.schema.ValidatorState;
9+
10+
import java.util.Iterator;
11+
12+
public class JsonNodeUtil {
13+
14+
public static boolean isNodeNullable(JsonNode schema){
15+
JsonNode nullable = schema.get("nullable");
16+
if (nullable != null && nullable.asBoolean()) {
17+
return true;
18+
}
19+
return false;
20+
}
21+
22+
//Check to see if a JsonNode is nullable with checking the isHandleNullableField
23+
public static boolean isNodeNullable(JsonNode schema, SchemaValidatorsConfig config){
24+
// check if the parent schema declares the fields as nullable
25+
if (config.isHandleNullableField()) {
26+
return isNodeNullable(schema);
27+
}
28+
return false;
29+
}
30+
31+
//Check to see if any child node for the OneOf SchemaNode is nullable
32+
public static boolean isChildNodeNullable(ArrayNode oneOfSchemaNode,SchemaValidatorsConfig config){
33+
Iterator iterator = oneOfSchemaNode.elements();
34+
while(iterator.hasNext()){
35+
//If one of the child Node for oneOf is nullable, it means the whole oneOf is nullable
36+
if (isNodeNullable((JsonNode)iterator.next(),config)) return true;
37+
}
38+
return false;
39+
}
40+
41+
public static boolean matchOneOfTypeNode(JsonNode oneOfSchemaNode, JsonType nodeType ){
42+
Iterator iterator = oneOfSchemaNode.elements();
43+
while (iterator.hasNext()){
44+
JsonNode oneOfTypeNode = (JsonNode) iterator.next();
45+
JsonNode typeTextNode = oneOfTypeNode.get("type");
46+
if(typeTextNode != null && typeTextNode.asText().equals(nodeType.toString())) //If the nodeType is oneOf the type defined in the oneOf , return true
47+
return true;
48+
}
49+
return false;
50+
}
51+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
6+
import io.undertow.Undertow;
7+
import io.undertow.server.handlers.resource.FileResourceManager;
8+
import org.junit.AfterClass;
9+
import org.junit.BeforeClass;
10+
import org.junit.Test;
11+
12+
import java.io.File;
13+
import java.io.InputStream;
14+
import java.net.URI;
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
18+
import static io.undertow.Handlers.resource;
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
22+
public class Issue428Test {
23+
protected ObjectMapper mapper = new ObjectMapper();
24+
protected JsonSchemaFactory validatorFactory = JsonSchemaFactory
25+
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)).objectMapper(mapper).build();
26+
protected static Undertow server = null;
27+
28+
public Issue428Test() {
29+
}
30+
31+
@BeforeClass
32+
public static void setUp() {
33+
if (server == null) {
34+
server = Undertow.builder()
35+
.addHttpListener(1234, "localhost")
36+
.setHandler(resource(new FileResourceManager(
37+
new File("./src/test/resources/remotes"), 100)))
38+
.build();
39+
server.start();
40+
}
41+
}
42+
43+
@AfterClass
44+
public static void tearDown() throws Exception {
45+
if (server != null) {
46+
try {
47+
Thread.sleep(100);
48+
} catch (InterruptedException ignored) {
49+
50+
}
51+
server.stop();
52+
}
53+
}
54+
55+
private void runTestFile(String testCaseFile) throws Exception {
56+
final URI testCaseFileUri = URI.create("classpath:" + testCaseFile);
57+
InputStream in = Thread.currentThread().getContextClassLoader()
58+
.getResourceAsStream(testCaseFile);
59+
ArrayNode testCases = mapper.readValue(in, ArrayNode.class);
60+
61+
for (int j = 0; j < testCases.size(); j++) {
62+
try {
63+
JsonNode testCase = testCases.get(j);
64+
SchemaValidatorsConfig config = new SchemaValidatorsConfig();
65+
66+
ArrayNode testNodes = (ArrayNode) testCase.get("tests");
67+
for (int i = 0; i < testNodes.size(); i++) {
68+
JsonNode test = testNodes.get(i);
69+
System.out.println("=== " + test.get("description"));
70+
JsonNode node = test.get("data");
71+
JsonNode typeLooseNode = test.get("isTypeLoose");
72+
// Configure the schemaValidator to set typeLoose's value based on the test file,
73+
// if test file do not contains typeLoose flag, use default value: true.
74+
config.setTypeLoose(typeLooseNode != null && typeLooseNode.asBoolean());
75+
config.setOpenAPI3StyleDiscriminators(false);
76+
JsonSchema schema = validatorFactory.getSchema(testCaseFileUri, testCase.get("schema"), config);
77+
78+
List<ValidationMessage> errors = new ArrayList<ValidationMessage>(schema.validate(node));
79+
80+
if (test.get("valid").asBoolean()) {
81+
if (!errors.isEmpty()) {
82+
System.out.println("---- test case failed ----");
83+
System.out.println("schema: " + schema.toString());
84+
System.out.println("data: " + test.get("data"));
85+
System.out.println("errors:");
86+
for (ValidationMessage error : errors) {
87+
System.out.println(error);
88+
}
89+
}
90+
//assertEquals(2, errors.size());
91+
} else {
92+
if (errors.isEmpty()) {
93+
System.out.println("---- test case failed ----");
94+
System.out.println("schema: " + schema);
95+
System.out.println("data: " + test.get("data"));
96+
} else {
97+
JsonNode errorCount = test.get("errorCount");
98+
if (errorCount != null && errorCount.isInt() && errors.size() != errorCount.asInt()) {
99+
System.out.println("---- test case failed ----");
100+
System.out.println("schema: " + schema);
101+
System.out.println("data: " + test.get("data"));
102+
System.out.println("errors: " + errors);
103+
for (ValidationMessage error : errors) {
104+
System.out.println(error);
105+
}
106+
assertEquals("expected error count", errorCount.asInt(), errors.size());
107+
}
108+
}
109+
assertFalse(errors.isEmpty());
110+
}
111+
112+
}
113+
114+
115+
} catch (JsonSchemaException e) {
116+
throw new IllegalStateException(String.format("Current schema should not be invalid: %s", testCaseFile), e);
117+
}
118+
}
119+
}
120+
121+
@Test
122+
public void testNullableOneOf() throws Exception {
123+
runTestFile("data/issue428.json");
124+
}
125+
}

0 commit comments

Comments
 (0)