Skip to content

Commit 8ada62c

Browse files
authored
Sort ValidationMessage by its type (#492)
1 parent 30d4b4b commit 8ada62c

15 files changed

+391
-2
lines changed

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,10 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
186186
else if (numberOfValidSchema < 1) {
187187
if (!childErrors.isEmpty()) {
188188
if (childErrors.size() > 1) {
189-
Set<ValidationMessage> notAdditionalPropertiesOnly = childErrors.stream()
189+
Set<ValidationMessage> notAdditionalPropertiesOnly = new LinkedHashSet<>(childErrors.stream()
190190
.filter((ValidationMessage validationMessage) -> !ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(validationMessage.getType()))
191-
.collect(Collectors.toSet());
191+
.sorted((vm1, vm2) -> compareValidationMessages(vm1, vm2))
192+
.collect(Collectors.toList()));
192193
if (notAdditionalPropertiesOnly.size() > 0) {
193194
childErrors = notAdditionalPropertiesOnly;
194195
}
@@ -210,6 +211,44 @@ else if (numberOfValidSchema < 1) {
210211
return Collections.unmodifiableSet(errors);
211212
}
212213

214+
/**
215+
* Sort <code>ValidationMessage</code> by its type
216+
* @return
217+
*/
218+
private static int compareValidationMessages(ValidationMessage vm1, ValidationMessage vm2) {
219+
// ValidationMessage's type has smaller index in the list below has high priority
220+
final List<String> typeCodes = Arrays.asList(
221+
ValidatorTypeCode.TYPE.getValue(),
222+
ValidatorTypeCode.DATETIME.getValue(),
223+
ValidatorTypeCode.UUID.getValue(),
224+
ValidatorTypeCode.ID.getValue(),
225+
ValidatorTypeCode.EXCLUSIVE_MAXIMUM.getValue(),
226+
ValidatorTypeCode.EXCLUSIVE_MINIMUM.getValue(),
227+
ValidatorTypeCode.TRUE.getValue(),
228+
ValidatorTypeCode.FALSE.getValue(),
229+
ValidatorTypeCode.CONST.getValue(),
230+
ValidatorTypeCode.CONTAINS.getValue(),
231+
ValidatorTypeCode.PROPERTYNAMES.getValue()
232+
);
233+
234+
final int index1 = typeCodes.indexOf(vm1.getType());
235+
final int index2 = typeCodes.indexOf(vm2.getType());
236+
237+
if (index1 >= 0) {
238+
if (index2 >= 0) {
239+
return Integer.compare(index1, index2);
240+
} else {
241+
return -1;
242+
}
243+
} else {
244+
if (index2 >= 0) {
245+
return 1;
246+
} else {
247+
return vm1.getCode().compareTo(vm2.getCode());
248+
}
249+
}
250+
}
251+
213252
private void resetValidatorState() {
214253
ValidatorState state = (ValidatorState) CollectorContext.getInstance().get(ValidatorState.VALIDATOR_STATE_KEY);
215254
state.setComplexValidator(false);
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.networknt.schema;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import org.junit.jupiter.api.Assertions;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.Arguments;
10+
import org.junit.jupiter.params.provider.MethodSource;
11+
12+
import java.io.InputStream;
13+
import java.util.Set;
14+
import java.util.stream.Stream;
15+
16+
class Issue491Test {
17+
18+
private static JsonSchemaFactory factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
19+
private static String schemaPath1 = "/schema/issue491-v7.json";
20+
private static String schemaPath2 = "/schema/issue491_2-v7.json";
21+
private static String schemaPath3 = "/schema/issue491_3-v7.json";
22+
23+
private JsonNode getJsonNodeFromJsonData(String jsonFilePath) throws Exception {
24+
InputStream content = getClass().getResourceAsStream(jsonFilePath);
25+
ObjectMapper mapper = new ObjectMapper();
26+
return mapper.readTree(content);
27+
}
28+
29+
@Test
30+
@DisplayName("Test valid oneOf option 1")
31+
void testValidJson1() throws Exception {
32+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1);
33+
JsonSchema schema = factory.getSchema(schemaInputStream);
34+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-1.json");
35+
Set<ValidationMessage> errors = schema.validate(node);
36+
Assertions.assertTrue(errors.isEmpty());
37+
}
38+
39+
@Test
40+
@DisplayName("Test valid oneOf option 2")
41+
void testValidJson2() throws Exception {
42+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1);
43+
JsonSchema schema = factory.getSchema(schemaInputStream);
44+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json");
45+
Set<ValidationMessage> errors = schema.validate(node);
46+
Assertions.assertTrue(errors.isEmpty());
47+
}
48+
49+
@Test
50+
@DisplayName("Test valid oneOf option 1")
51+
void testValidJson3() throws Exception {
52+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2);
53+
JsonSchema schema = factory.getSchema(schemaInputStream);
54+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-3.json");
55+
Set<ValidationMessage> errors = schema.validate(node);
56+
Assertions.assertTrue(errors.isEmpty());
57+
}
58+
59+
@Test
60+
@DisplayName("Test valid oneOf option 2")
61+
void testValidJson4() throws Exception {
62+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2);
63+
JsonSchema schema = factory.getSchema(schemaInputStream);
64+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json");
65+
Set<ValidationMessage> errors = schema.validate(node);
66+
Assertions.assertTrue(errors.isEmpty());
67+
}
68+
69+
@Test
70+
@DisplayName("Test valid oneOf option 1")
71+
void testValidJson5() throws Exception {
72+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3);
73+
JsonSchema schema = factory.getSchema(schemaInputStream);
74+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-4.json");
75+
Set<ValidationMessage> errors = schema.validate(node);
76+
Assertions.assertTrue(errors.isEmpty());
77+
}
78+
79+
@Test
80+
@DisplayName("Test valid oneOf option 2")
81+
void testValidJson6() throws Exception {
82+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3);
83+
JsonSchema schema = factory.getSchema(schemaInputStream);
84+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-valid-2.json");
85+
Set<ValidationMessage> errors = schema.validate(node);
86+
Assertions.assertTrue(errors.isEmpty());
87+
}
88+
89+
@Test
90+
@DisplayName("Test invalid oneOf option 1 - wrong type")
91+
void testInvalidJson1() throws Exception {
92+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1);
93+
JsonSchema schema = factory.getSchema(schemaInputStream);
94+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-1.json");
95+
Set<ValidationMessage> errors = schema.validate(node);
96+
Assertions.assertEquals(2, errors.size());
97+
Assertions.assertEquals("$.search.searchAge.age: string found, integer expected", errors.iterator().next().getMessage());
98+
}
99+
100+
@Test
101+
@DisplayName("Test invalid oneOf option 2 - wrong type")
102+
void testInvalidJson2() throws Exception {
103+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath1);
104+
JsonSchema schema = factory.getSchema(schemaInputStream);
105+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-2.json");
106+
Set<ValidationMessage> errors = schema.validate(node);
107+
Assertions.assertEquals(2, errors.size());
108+
Assertions.assertEquals("$.search.name: integer found, string expected", errors.iterator().next().getMessage());
109+
}
110+
111+
@Test
112+
@DisplayName("Test invalid oneOf option 1 - wrong type")
113+
void testInvalidJson3() throws Exception {
114+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2);
115+
JsonSchema schema = factory.getSchema(schemaInputStream);
116+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-3.json");
117+
Set<ValidationMessage> errors = schema.validate(node);
118+
Assertions.assertEquals(2, errors.size());
119+
Assertions.assertEquals("$.search.byAge.age: string found, integer expected", errors.iterator().next().getMessage());
120+
}
121+
122+
@Test
123+
@DisplayName("Test invalid oneOf option 2 - wrong type")
124+
void testInvalidJson4() throws Exception {
125+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath2);
126+
JsonSchema schema = factory.getSchema(schemaInputStream);
127+
JsonNode node = getJsonNodeFromJsonData("/data/issue491-invalid-2.json");
128+
Set<ValidationMessage> errors = schema.validate(node);
129+
Assertions.assertEquals(2, errors.size());
130+
Assertions.assertEquals("$.search.name: integer found, string expected", errors.iterator().next().getMessage());
131+
}
132+
133+
@ParameterizedTest
134+
@MethodSource("parametersProvider")
135+
@DisplayName("Test invalid oneOf option - wrong types or values")
136+
void testInvalidJson5(String jsonPath, String expectedError) throws Exception {
137+
InputStream schemaInputStream = Issue491Test.class.getResourceAsStream(schemaPath3);
138+
JsonSchema schema = factory.getSchema(schemaInputStream);
139+
JsonNode node = getJsonNodeFromJsonData(jsonPath);
140+
Set<ValidationMessage> errors = schema.validate(node);
141+
Assertions.assertEquals(2, errors.size());
142+
Assertions.assertEquals(expectedError, errors.iterator().next().getMessage());
143+
}
144+
145+
private static Stream<Arguments> parametersProvider() {
146+
return Stream.of(
147+
Arguments.of("/data/issue491-invalid-4.json", "$.search.age: string found, integer expected"),
148+
Arguments.of("/data/issue491-invalid-2.json", "$.search.name: integer found, string expected"),
149+
Arguments.of("/data/issue491-invalid-5.json", "$.search.age: must have a maximum value of 150"),
150+
Arguments.of("/data/issue491-invalid-6.json", "$.search.name: may only be 20 characters long")
151+
);
152+
}
153+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"search": {
3+
"searchAge": {
4+
"age": "Steve"
5+
}
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"name": 123
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"search": {
3+
"byAge": {
4+
"age": "Steve"
5+
}
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"age": "Steve"
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"age": 200
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"name": "TooLoooooooooooooooooooooooooooooooooongName"
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"search": {
3+
"searchAge": {
4+
"age": 50
5+
}
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"name": "Steve"
4+
}
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"search": {
3+
"byAge": {
4+
"age": 50
5+
}
6+
}
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"search": {
3+
"age": 50
4+
}
5+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://example.com/issue-470.json",
4+
"title": "OneOf validation message",
5+
"description": "Test description",
6+
"type": "object",
7+
"properties": {
8+
"search": {
9+
"type": "object",
10+
"oneOf": [
11+
{
12+
"type": "object",
13+
"properties": {
14+
"searchAge": {
15+
"type": "object",
16+
"properties": {
17+
"age": {
18+
"type": "integer",
19+
"maximum": 150,
20+
"minimum": 1
21+
}
22+
},
23+
"required": [
24+
"age"
25+
]
26+
}
27+
},
28+
"required": [
29+
"searchAge"
30+
]
31+
},
32+
{
33+
"type": "object",
34+
"properties": {
35+
"name": {
36+
"type": "string",
37+
"maxLength": 20,
38+
"minLength": 1
39+
}
40+
},
41+
"required": [
42+
"name"
43+
]
44+
}
45+
]
46+
}
47+
},
48+
"additionalProperties": false
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://example.com/issue-470.json",
4+
"title": "OneOf validation message",
5+
"description": "Test description",
6+
"type": "object",
7+
"properties": {
8+
"search": {
9+
"type": "object",
10+
"oneOf": [
11+
{
12+
"type": "object",
13+
"properties": {
14+
"byAge": {
15+
"type": "object",
16+
"properties": {
17+
"age": {
18+
"type": "integer",
19+
"maximum": 150,
20+
"minimum": 1
21+
}
22+
},
23+
"required": [
24+
"age"
25+
]
26+
}
27+
},
28+
"required": [
29+
"byAge"
30+
]
31+
},
32+
{
33+
"type": "object",
34+
"properties": {
35+
"name": {
36+
"type": "string",
37+
"maxLength": 20,
38+
"minLength": 1
39+
}
40+
},
41+
"required": [
42+
"name"
43+
]
44+
}
45+
]
46+
}
47+
},
48+
"additionalProperties": false
49+
}

0 commit comments

Comments
 (0)