Skip to content

Commit 3758d52

Browse files
Fix for issue-366 (#372)
* Fix for issue-366 * fix for code review comments Co-authored-by: Krishnakumar Ramamurthy <[email protected]>
1 parent 60917a8 commit 3758d52

File tree

7 files changed

+307
-231
lines changed

7 files changed

+307
-231
lines changed

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected void parseErrorCode(String errorCodeKey) {
126126

127127
protected ValidationMessage buildValidationMessage(String at, String... arguments) {
128128
final ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments);
129-
if (failFast) {
129+
if (failFast && !isPartOfOneOfMultipleType()) {
130130
throw new JsonSchemaException(message);
131131
}
132132
return message;
@@ -149,18 +149,22 @@ protected String getNodeFieldType() {
149149
}
150150
return null;
151151
}
152-
153-
/**
154-
* This is default implementation of walk method. Its job is to call the
155-
* validate method if shouldValidateSchema is enabled.
156-
*/
157-
@Override
158-
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
159-
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
160-
if (shouldValidateSchema) {
161-
validationMessages = validate(node, rootNode, at);
162-
}
163-
return validationMessages;
164-
}
152+
153+
/**
154+
* This is default implementation of walk method. Its job is to call the
155+
* validate method if shouldValidateSchema is enabled.
156+
*/
157+
@Override
158+
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
159+
Set<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
160+
if (shouldValidateSchema) {
161+
validationMessages = validate(node, rootNode, at);
162+
}
163+
return validationMessages;
164+
}
165+
166+
protected boolean isPartOfOneOfMultipleType(){
167+
return parentSchema.schemaPath.equals(ValidatorTypeCode.ONE_OF.getValue());
168+
}
165169

166170
}

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

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ public boolean allConstantsMatch(JsonNode node) {
107107
return true;
108108
}
109109

110-
private JsonSchema getSchema() {
111-
return schema;
112-
}
110+
private JsonSchema getSchema() {
111+
return schema;
112+
}
113113

114114
}
115115

@@ -119,7 +119,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
119119
for (int i = 0; i < size; i++) {
120120
JsonNode childNode = schemaNode.get(i);
121121
JsonSchema childSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), childNode, parentSchema)
122-
.initialize();
122+
.initialize();
123123
schemas.add(new ShortcutValidator(childNode, parentSchema, validationContext, childSchema));
124124
}
125125

@@ -136,11 +136,11 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
136136
}
137137
// this is a complex validator, we set the flag to true
138138
state.setComplexValidator(true);
139-
139+
140140

141141
int numberOfValidSchema = 0;
142142
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
143-
143+
Set<ValidationMessage> childErrors = new LinkedHashSet<ValidationMessage>();
144144
// validate that only a single element has been received in the oneOf node
145145
// validation should not continue, as it contradicts the oneOf requirement of only one
146146
// if(node.isObject() && node.size()>1) {
@@ -152,15 +152,17 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
152152
Set<ValidationMessage> schemaErrors = null;
153153
// Reset state in case the previous validator did not match
154154
state.setMatchedNode(true);
155-
if (!validator.allConstantsMatch(node)) {
155+
156+
//This prevents from collecting all the error messages in proper format.
157+
/* if (!validator.allConstantsMatch(node)) {
156158
// take a shortcut: if there is any constant that does not match,
157159
// we can bail out of the validation
158160
continue;
159-
}
161+
}*/
160162

161163
// get the current validator
162164
JsonSchema schema = validator.schema;
163-
if (!state.isWalkEnabled()) {
165+
if (!state.isWalkEnabled()) {
164166
schemaErrors = schema.validate(node, rootNode, at);
165167
} else {
166168
schemaErrors = schema.walk(node, rootNode, at, state.isValidationEnabledWhileWalking());
@@ -174,26 +176,41 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
174176

175177
numberOfValidSchema++;
176178
}
179+
childErrors.addAll(schemaErrors);
177180
}
178181

179182

180183
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
181-
if (numberOfValidSchema != 1) {
182-
errors = Collections.singleton(buildValidationMessage(at, ""));
184+
if(numberOfValidSchema > 1){
185+
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
186+
if( failFast ) {
187+
throw new JsonSchemaException(message);
188+
}
189+
errors.add(message);
190+
}
191+
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
192+
else if (numberOfValidSchema < 1) {
193+
if (!childErrors.isEmpty()) {
194+
errors.addAll(childErrors);
195+
}
196+
if( failFast ){
197+
throw new JsonSchemaException(errors.toString());
198+
}
183199
}
184200

201+
185202
// reset the ValidatorState object in the ThreadLocal
186203
validatorState.remove();
187204

188205
return Collections.unmodifiableSet(errors);
189206
}
190-
207+
191208
public List<JsonSchema> getChildSchemas() {
192-
List<JsonSchema> childJsonSchemas = new ArrayList<JsonSchema>();
193-
for (ShortcutValidator shortcutValidator: schemas ) {
194-
childJsonSchemas.add(shortcutValidator.getSchema());
195-
}
196-
return childJsonSchemas;
209+
List<JsonSchema> childJsonSchemas = new ArrayList<JsonSchema>();
210+
for (ShortcutValidator shortcutValidator: schemas ) {
211+
childJsonSchemas.add(shortcutValidator.getSchema());
212+
}
213+
return childJsonSchemas;
197214
}
198215

199216
@Override
@@ -209,4 +226,17 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
209226
return validationMessages;
210227
}
211228

229+
private ValidationMessage getMultiSchemasValidErrorMsg(String at){
230+
String msg="";
231+
for(ShortcutValidator schema: schemas){
232+
String schemaValue = schema.getSchema().getSchemaNode().toString();
233+
msg = msg.concat(schemaValue);
234+
}
235+
236+
ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(),ValidatorTypeCode.ONE_OF ,
237+
at, String.format("but more than one schemas {%s} are valid ",msg));
238+
239+
return message;
240+
}
241+
212242
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package com.networknt.schema;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.net.URI;
10+
import java.util.List;
11+
import java.util.Set;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
15+
public class Issue366FailFast {
16+
17+
@Before
18+
public void setup() throws IOException {
19+
setupSchema();
20+
}
21+
22+
JsonSchema jsonSchema;
23+
ObjectMapper objectMapper = new ObjectMapper();
24+
private void setupSchema() throws IOException {
25+
26+
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
27+
schemaValidatorsConfig.setFailFast(true);
28+
JsonSchemaFactory schemaFactory = JsonSchemaFactory
29+
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
30+
.objectMapper(objectMapper)
31+
.build();
32+
33+
schemaValidatorsConfig.setTypeLoose(false);
34+
35+
URI uri = getSchema();
36+
37+
InputStream in = getClass().getResourceAsStream("/draft7/issue366_schema.json");
38+
JsonNode testCases = objectMapper.readValue(in, JsonNode.class);
39+
this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig);
40+
}
41+
42+
protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
43+
ObjectMapper mapper = new ObjectMapper();
44+
JsonNode node = mapper.readTree(content);
45+
return node;
46+
}
47+
48+
@Test
49+
public void firstOneValid() throws Exception {
50+
String dataPath = "/data/issue366.json";
51+
52+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
53+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
54+
List<JsonNode> testNodes = node.findValues("tests");
55+
JsonNode testNode = testNodes.get(0).get(0);
56+
JsonNode dataNode = testNode.get("data");
57+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
58+
assertTrue(errors.isEmpty());
59+
}
60+
61+
@Test
62+
public void secondOneValid() throws Exception {
63+
String dataPath = "/data/issue366.json";
64+
65+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
66+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
67+
List<JsonNode> testNodes = node.findValues("tests");
68+
JsonNode testNode = testNodes.get(0).get(1);
69+
JsonNode dataNode = testNode.get("data");
70+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
71+
assertTrue(errors.isEmpty());
72+
}
73+
74+
@Test(expected = JsonSchemaException.class)
75+
public void bothValid() throws Exception {
76+
String dataPath = "/data/issue366.json";
77+
78+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
79+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
80+
List<JsonNode> testNodes = node.findValues("tests");
81+
JsonNode testNode = testNodes.get(0).get(2);
82+
JsonNode dataNode = testNode.get("data");
83+
jsonSchema.validate(dataNode);
84+
}
85+
86+
@Test(expected = JsonSchemaException.class)
87+
public void neitherValid() throws Exception {
88+
String dataPath = "/data/issue366.json";
89+
90+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
91+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
92+
List<JsonNode> testNodes = node.findValues("tests");
93+
JsonNode testNode = testNodes.get(0).get(3);
94+
JsonNode dataNode = testNode.get("data");
95+
jsonSchema.validate(dataNode);
96+
}
97+
98+
private URI getSchema() {
99+
return URI.create("classpath:" + "/draft7/issue366_schema.json");
100+
}
101+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.networknt.schema;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.net.URI;
11+
import java.util.List;
12+
import java.util.Set;
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
16+
public class Issue366FailSlow {
17+
18+
@Before
19+
public void setup() throws IOException {
20+
setupSchema();
21+
}
22+
23+
JsonSchema jsonSchema;
24+
ObjectMapper objectMapper = new ObjectMapper();
25+
private void setupSchema() throws IOException {
26+
27+
SchemaValidatorsConfig schemaValidatorsConfig = new SchemaValidatorsConfig();
28+
JsonSchemaFactory schemaFactory = JsonSchemaFactory
29+
.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7))
30+
.objectMapper(objectMapper)
31+
.build();
32+
33+
schemaValidatorsConfig.setTypeLoose(false);
34+
35+
URI uri = getSchema();
36+
37+
InputStream in = getClass().getResourceAsStream("/schema/issue366_schema.json");
38+
JsonNode testCases = objectMapper.readValue(in, JsonNode.class);
39+
this.jsonSchema = schemaFactory.getSchema(uri, testCases,schemaValidatorsConfig);
40+
}
41+
42+
protected JsonNode getJsonNodeFromStreamContent(InputStream content) throws Exception {
43+
ObjectMapper mapper = new ObjectMapper();
44+
JsonNode node = mapper.readTree(content);
45+
return node;
46+
}
47+
48+
@Test
49+
public void firstOneValid() throws Exception {
50+
String dataPath = "/data/issue366.json";
51+
52+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
53+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
54+
List<JsonNode> testNodes = node.findValues("tests");
55+
JsonNode testNode = testNodes.get(0).get(0);
56+
JsonNode dataNode = testNode.get("data");
57+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
58+
assertTrue(errors.isEmpty());
59+
}
60+
61+
@Test
62+
public void secondOneValid() throws Exception {
63+
String dataPath = "/data/issue366.json";
64+
65+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
66+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
67+
List<JsonNode> testNodes = node.findValues("tests");
68+
JsonNode testNode = testNodes.get(0).get(1);
69+
JsonNode dataNode = testNode.get("data");
70+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
71+
assertTrue(errors.isEmpty());
72+
}
73+
74+
@Test
75+
public void bothValid() throws Exception {
76+
String dataPath = "/data/issue366.json";
77+
78+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
79+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
80+
List<JsonNode> testNodes = node.findValues("tests");
81+
JsonNode testNode = testNodes.get(0).get(2);
82+
JsonNode dataNode = testNode.get("data");
83+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
84+
assertTrue(!errors.isEmpty());
85+
assertEquals(errors.size(),1);
86+
}
87+
88+
@Test
89+
public void neitherValid() throws Exception {
90+
String dataPath = "/data/issue366.json";
91+
92+
InputStream dataInputStream = getClass().getResourceAsStream(dataPath);
93+
JsonNode node = getJsonNodeFromStreamContent(dataInputStream);
94+
List<JsonNode> testNodes = node.findValues("tests");
95+
JsonNode testNode = testNodes.get(0).get(3);
96+
JsonNode dataNode = testNode.get("data");
97+
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
98+
assertTrue(!errors.isEmpty());
99+
assertEquals(errors.size(),2);
100+
}
101+
102+
private URI getSchema() {
103+
return URI.create("classpath:" + "/draft7/issue366_schema.json");
104+
}
105+
}

0 commit comments

Comments
 (0)