Skip to content

Commit ae7c0ca

Browse files
The bug fix for the issue 520 (#543)
* Issue-520 OneOfValidator issue fix started * issus-520 Fix OneOfValidator issue * issue-520 Fix all of validator issue * issue-520 Fix anyOfValidator issue
1 parent c9ed1bf commit ae7c0ca

18 files changed

+248
-90
lines changed

pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<version.hamcrest>2.2</version.hamcrest>
7373
<version.undertow>2.2.14.Final</version.undertow>
7474
<version.itu>1.5.1</version.itu>
75+
<version.commons.io>2.11.0</version.commons.io>
7576
</properties>
7677
<dependencies>
7778
<dependency>
@@ -118,6 +119,12 @@
118119
<version>${version.junit}</version>
119120
<scope>test</scope>
120121
</dependency>
122+
<dependency>
123+
<groupId>junit</groupId>
124+
<artifactId>junit</artifactId>
125+
<version>4.13.2</version>
126+
<scope>test</scope>
127+
</dependency>
121128
<dependency>
122129
<groupId>org.mockito</groupId>
123130
<artifactId>mockito-core</artifactId>
@@ -136,6 +143,12 @@
136143
<version>${version.undertow}</version>
137144
<scope>test</scope>
138145
</dependency>
146+
<dependency>
147+
<groupId>commons-io</groupId>
148+
<artifactId>commons-io</artifactId>
149+
<version>${version.commons.io}</version>
150+
<scope>test</scope>
151+
</dependency>
139152
</dependencies>
140153
<build>
141154
<sourceDirectory>${basedir}/src/main/java</sourceDirectory>

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.LinkedHashSet;
2323
import java.util.List;
2424
import java.util.Set;
25+
import java.util.stream.Collectors;
2526

2627
import com.fasterxml.jackson.databind.JsonNode;
2728
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -49,7 +50,7 @@ public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
4950
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
5051
debug(logger, node, rootNode, at);
5152

52-
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
53+
Set<ValidationMessage> childSchemaErrors = new LinkedHashSet<>();
5354

5455
// As AllOf might contain multiple schemas take a backup of evaluatedProperties.
5556
Object backupEvaluatedProperties = CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES);
@@ -59,7 +60,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
5960

6061
for (JsonSchema schema : schemas) {
6162
try {
62-
errors.addAll(schema.validate(node, rootNode, at));
63+
childSchemaErrors.addAll(schema.validate(node, rootNode, at));
6364

6465
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
6566
final Iterator<JsonNode> arrayElements = schemaNode.elements();
@@ -93,7 +94,7 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
9394
}
9495
}
9596
} finally {
96-
if (errors.isEmpty()) {
97+
if (childSchemaErrors.isEmpty()) {
9798
List<String> backupEvaluatedPropertiesList = (backupEvaluatedProperties == null ? new ArrayList<>() : (List<String>) backupEvaluatedProperties);
9899
backupEvaluatedPropertiesList.addAll((List<String>) CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES));
99100
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, backupEvaluatedPropertiesList);
@@ -103,7 +104,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
103104
}
104105
}
105106

106-
return Collections.unmodifiableSet(errors);
107+
if(!childSchemaErrors.isEmpty()){
108+
List<String> childSchemaErrorMessages = childSchemaErrors.stream().map(ValidationMessage::getMessage).collect(Collectors.toList());
109+
String childMessages = String.join(", ", childSchemaErrorMessages);
110+
return Collections.singleton(ValidationMessage.of(getValidatorType().getValue(), ValidatorTypeCode.ALL_OF, at, childMessages));
111+
}
112+
113+
return Collections.unmodifiableSet(childSchemaErrors);
107114
}
108115

109116
@Override

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
113113
CollectorContext.getInstance().add(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES, backupEvaluatedProperties);
114114
}
115115
}
116+
if (!allErrors.isEmpty()) {
117+
List<String> childSchemaErrorMessages = allErrors.stream().map(ValidationMessage::getMessage).collect(Collectors.toList());
118+
String childMessages = String.join(", ", childSchemaErrorMessages);
119+
return Collections.singleton(ValidationMessage.of(getValidatorType().getValue(), ValidatorTypeCode.ANY_OF, at, childMessages));
120+
}
116121
return Collections.unmodifiableSet(allErrors);
117122
}
118123

@@ -129,12 +134,12 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
129134
for (JsonSchema schema : schemas) {
130135
results.add(schema.walk(node, rootNode, at, shouldValidateSchema));
131136
}
132-
if(! shouldValidateSchema) {
137+
if (!shouldValidateSchema) {
133138
return new LinkedHashSet<>();
134139
}
135140
boolean atLeastOneValid = results.stream()
136141
.anyMatch(Set::isEmpty);
137-
if(atLeastOneValid) {
142+
if (atLeastOneValid) {
138143
return new LinkedHashSet<>();
139144
}
140145
return results.stream()

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

Lines changed: 9 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
126126
}
127127

128128
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
129-
Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
129+
Set<ValidationMessage> errors = new LinkedHashSet<>();
130130

131131
// As oneOf might contain multiple schemas take a backup of evaluatedProperties.
132132
Object backupEvaluatedProperties = CollectorContext.getInstance().get(UnEvaluatedPropertiesValidator.EVALUATED_PROPERTIES);
@@ -143,14 +143,13 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
143143
state.setComplexValidator(true);
144144

145145
int numberOfValidSchema = 0;
146-
Set<ValidationMessage> childErrors = new LinkedHashSet<ValidationMessage>();
146+
Set<ValidationMessage> childErrors = new LinkedHashSet<>();
147147
// validate that only a single element has been received in the oneOf node
148148
// validation should not continue, as it contradicts the oneOf requirement of only one
149149
// if(node.isObject() && node.size()>1) {
150150
// errors = Collections.singleton(buildValidationMessage(at, ""));
151151
// return Collections.unmodifiableSet(errors);
152152
// }
153-
154153
for (ShortcutValidator validator : schemas) {
155154
Set<ValidationMessage> schemaErrors = null;
156155
// Reset state in case the previous validator did not match
@@ -191,36 +190,15 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
191190
Set<ValidationMessage> childNotRequiredErrors = childErrors.stream().filter(error -> !ValidatorTypeCode.REQUIRED.getValue().equals(error.getType())).collect(Collectors.toSet());
192191

193192
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
194-
if (numberOfValidSchema > 1) {
193+
// TODO We need to break it into two errors in the future.
194+
if (numberOfValidSchema != 1) {
195195
final ValidationMessage message = getMultiSchemasValidErrorMsg(at);
196196
if (failFast) {
197197
throw new JsonSchemaException(message);
198198
}
199199
errors.add(message);
200200
}
201201

202-
// ensure there is always an "OneOf" error reported if number of valid schemas is not equal to 1.
203-
else if (numberOfValidSchema < 1) {
204-
if (!childNotRequiredErrors.isEmpty()) {
205-
childErrors = childNotRequiredErrors;
206-
}
207-
if (!childErrors.isEmpty()) {
208-
if (childErrors.size() > 1) {
209-
Set<ValidationMessage> notAdditionalPropertiesOnly = new LinkedHashSet<>(childErrors.stream()
210-
.filter((ValidationMessage validationMessage) -> !ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(validationMessage.getType()))
211-
.sorted((vm1, vm2) -> compareValidationMessages(vm1, vm2))
212-
.collect(Collectors.toList()));
213-
if (notAdditionalPropertiesOnly.size() > 0) {
214-
childErrors = notAdditionalPropertiesOnly;
215-
}
216-
}
217-
errors.addAll(childErrors);
218-
}
219-
if (failFast) {
220-
throw new JsonSchemaException(errors.toString());
221-
}
222-
}
223-
224202
// Make sure to signal parent handlers we matched
225203
if (errors.isEmpty())
226204
state.setMatchedNode(true);
@@ -240,84 +218,34 @@ else if (numberOfValidSchema < 1) {
240218
}
241219
}
242220

243-
/**
244-
* Sort <code>ValidationMessage</code> by its type
245-
* @return
246-
*/
247-
private static int compareValidationMessages(ValidationMessage vm1, ValidationMessage vm2) {
248-
// ValidationMessage's type has smaller index in the list below has high priority
249-
final List<String> typeCodes = Arrays.asList(
250-
ValidatorTypeCode.TYPE.getValue(),
251-
ValidatorTypeCode.DATETIME.getValue(),
252-
ValidatorTypeCode.UUID.getValue(),
253-
ValidatorTypeCode.ID.getValue(),
254-
ValidatorTypeCode.EXCLUSIVE_MAXIMUM.getValue(),
255-
ValidatorTypeCode.EXCLUSIVE_MINIMUM.getValue(),
256-
ValidatorTypeCode.TRUE.getValue(),
257-
ValidatorTypeCode.FALSE.getValue(),
258-
ValidatorTypeCode.CONST.getValue(),
259-
ValidatorTypeCode.CONTAINS.getValue(),
260-
ValidatorTypeCode.PROPERTYNAMES.getValue()
261-
);
262-
263-
final int index1 = typeCodes.indexOf(vm1.getType());
264-
final int index2 = typeCodes.indexOf(vm2.getType());
265-
266-
if (index1 >= 0) {
267-
if (index2 >= 0) {
268-
return Integer.compare(index1, index2);
269-
} else {
270-
return -1;
271-
}
272-
} else {
273-
if (index2 >= 0) {
274-
return 1;
275-
} else {
276-
return vm1.getCode().compareTo(vm2.getCode());
277-
}
278-
}
279-
}
280-
281221
private void resetValidatorState() {
282222
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
283223
state.setComplexValidator(false);
284224
state.setMatchedNode(true);
285225
}
286226

287-
public List<JsonSchema> getChildSchemas() {
288-
List<JsonSchema> childJsonSchemas = new ArrayList<JsonSchema>();
289-
for (ShortcutValidator shortcutValidator: schemas ) {
290-
childJsonSchemas.add(shortcutValidator.getSchema());
291-
}
292-
return childJsonSchemas;
293-
}
294-
295227
@Override
296228
public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at, boolean shouldValidateSchema) {
297229
HashSet<ValidationMessage> validationMessages = new LinkedHashSet<ValidationMessage>();
298230
if (shouldValidateSchema) {
299231
validationMessages.addAll(validate(node, rootNode, at));
300232
} else {
301233
for (ShortcutValidator validator : schemas) {
302-
validator.schema.walk(node, rootNode, at , shouldValidateSchema);
234+
validator.schema.walk(node, rootNode, at, shouldValidateSchema);
303235
}
304236
}
305237
return validationMessages;
306238
}
307239

308-
private ValidationMessage getMultiSchemasValidErrorMsg(String at){
309-
String msg="";
310-
for(ShortcutValidator schema: schemas){
311-
String schemaValue = schema.getSchema().getSchemaNode().toString();
312-
msg = msg.concat(schemaValue);
313-
}
314-
240+
private ValidationMessage getMultiSchemasValidErrorMsg(String at) {
241+
List<String> msgStrList = schemas.stream().map(shortcutValidator -> shortcutValidator.getSchema().getSchemaNode().toString()).collect(Collectors.toList());
242+
String msg = String.join(", ", msgStrList);
315243
return ValidationMessage.of(getValidatorType().getValue(), ValidatorTypeCode.ONE_OF, at, msg);
316244
}
317245

318246
@Override
319247
public void preloadJsonSchema() {
320-
for (final ShortcutValidator scValidator: schemas) {
248+
for (final ShortcutValidator scValidator : schemas) {
321249
scValidator.getSchema().initializeValidators();
322250
}
323251
}

src/main/resources/jsv-messages.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
$ref = {0}: has an error with 'refs'
22
additionalProperties = {0}.{1}: is not defined in the schema and the schema does not allow additional properties
3-
allOf = {0}: should be valid to all the schemas {1}
4-
anyOf = {0}: should be valid to any of the schemas {1}
3+
allOf = {0}: should be valid to all the schemas: {1}
4+
anyOf = {0}: should be valid to one schema at least: {1}
55
const = {0}: must be a constant value {1}
66
contains = {0}: does not contain an element that passes these validations: {1}
77
crossEdits = {0}: has an error with 'cross edits'
@@ -28,7 +28,7 @@ minimum = {0}: must have a minimum value of {1}
2828
multipleOf = {0}: must be multiple of {1}
2929
not = {0}: should not be valid to the schema {1}
3030
notAllowed = {0}.{1}: is not allowed but it is in the data
31-
oneOf = {0}: should be valid to one and only one of schema, but more than one are valid: {1}
31+
oneOf = {0}: should be valid to one and only one of schema, but more than one are valid or no one valid: {1}
3232
pattern = {0}: does not match the regex pattern {1}
3333
patternProperties = {0}: has some error with 'pattern properties'
3434
properties = {0}: has an error with 'properties'
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.apache.commons.io.FileUtils;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.IOException;
8+
import java.util.Set;
9+
10+
public class AllOfValidatorTest {
11+
12+
private static final SpecVersion.VersionFlag SCHEMA_VERSION = SpecVersion.VersionFlag.V201909;
13+
private static final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SCHEMA_VERSION);
14+
private static final ObjectMapper mapper = new ObjectMapper();
15+
16+
public static final String BASE_PATH = "src/test/resources/data/issue520";
17+
18+
@Test
19+
public void validTest() throws IOException {
20+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-allOfValidator-valid.json"), "UTF-8");
21+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-allOfValidator-schema.json"), "UTF-8");
22+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
23+
System.out.println(validationMessages);
24+
}
25+
26+
@Test
27+
public void invalidTest() throws IOException {
28+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-allOfValidator-invalid.json"), "UTF-8");
29+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-allOfValidator-schema.json"), "UTF-8");
30+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
31+
System.out.println(validationMessages);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.apache.commons.io.FileUtils;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.IOException;
8+
import java.util.Set;
9+
10+
public class AnyOfValidatorTest {
11+
12+
private static final SpecVersion.VersionFlag SCHEMA_VERSION = SpecVersion.VersionFlag.V201909;
13+
private static final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SCHEMA_VERSION);
14+
private static final ObjectMapper mapper = new ObjectMapper();
15+
16+
public static final String BASE_PATH = "src/test/resources/data/issue520";
17+
18+
@Test
19+
public void validTest() throws IOException {
20+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-anyOfValidator-valid.json"), "UTF-8");
21+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-anyOfValidator-schema.json"), "UTF-8");
22+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
23+
System.out.println(validationMessages);
24+
}
25+
26+
@Test
27+
public void invalidTest() throws IOException {
28+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-anyOfValidator-invalid.json"), "UTF-8");
29+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-anyOfValidator-schema.json"), "UTF-8");
30+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
31+
System.out.println(validationMessages);
32+
}
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.apache.commons.io.FileUtils;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.io.IOException;
8+
import java.util.Set;
9+
10+
public class OneOfValidatorTest {
11+
12+
private static final SpecVersion.VersionFlag SCHEMA_VERSION = SpecVersion.VersionFlag.V201909;
13+
private static final JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SCHEMA_VERSION);
14+
private static final ObjectMapper mapper = new ObjectMapper();
15+
16+
public static final String BASE_PATH = "src/test/resources/data/issue520";
17+
18+
@Test
19+
public void validTest() throws IOException {
20+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-valid.json"), "UTF-8");
21+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-schema.json"), "UTF-8");
22+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
23+
System.out.println(validationMessages);
24+
}
25+
26+
@Test
27+
public void invalid1Test() throws IOException {
28+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-invalid-1.json"), "UTF-8");
29+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-schema.json"), "UTF-8");
30+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
31+
System.out.println(validationMessages);
32+
}
33+
34+
@Test
35+
public void invalid2Test() throws IOException {
36+
String json = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-invalid-2.json"), "UTF-8");
37+
String schema = FileUtils.readFileToString(FileUtils.getFile(BASE_PATH, "issue520-oneOfValidator-schema.json"), "UTF-8");
38+
Set<ValidationMessage> validationMessages = factory.getSchema(schema).validate(mapper.readTree(json));
39+
System.out.println(validationMessages);
40+
}
41+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"k3": "v3"
3+
}

0 commit comments

Comments
 (0)