Skip to content

Commit ca9c5e1

Browse files
committed
added test for proper json pointer construction, changed ValidationException#prepend() to properly handle causingExceptions, and some docs & formatting improvements
1 parent f7ff3d2 commit ca9c5e1

File tree

8 files changed

+164
-36
lines changed

8 files changed

+164
-36
lines changed

core/src/main/java/org/everit/json/schema/ArraySchema.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,15 @@ private List<ValidationException> testItems(final JSONArray subject) {
226226
for (int i = 0; i < itemValidationUntil; ++i) {
227227
int copyOfI = i; // i is not effectively final so we copy it
228228
ifFails(itemSchemas.get(i), subject.get(i))
229-
.map(exc -> exc.prepend(String.valueOf(copyOfI)))
230-
.ifPresent(rval::add);
229+
.map(exc -> exc.prepend(String.valueOf(copyOfI)))
230+
.ifPresent(rval::add);
231231
}
232232
if (schemaOfAdditionalItems != null) {
233233
for (int i = itemValidationUntil; i < subject.length(); ++i) {
234234
int copyOfI = i; // i is not effectively final so we copy it
235235
ifFails(schemaOfAdditionalItems, subject.get(i))
236-
.map(exc -> exc.prepend(String.valueOf(copyOfI)))
237-
.ifPresent(rval::add);
236+
.map(exc -> exc.prepend(String.valueOf(copyOfI)))
237+
.ifPresent(rval::add);
238238
}
239239
}
240240
}

core/src/main/java/org/everit/json/schema/ObjectSchema.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ public static Builder builder() {
155155
return new Builder();
156156
}
157157

158+
private static <K, V> Map<K, V> copyMap(final Map<K, V> original) {
159+
return Collections.unmodifiableMap(new HashMap<>(original));
160+
}
161+
158162
private final Map<String, Schema> propertySchemas;
159163

160164
private final boolean additionalProperties;
@@ -201,10 +205,6 @@ public ObjectSchema(final Builder builder) {
201205
this.patternProperties = copyMap(builder.patternProperties);
202206
}
203207

204-
private final <K, V> Map<K, V> copyMap(final Map<K, V> original) {
205-
return Collections.unmodifiableMap(new HashMap<>(original));
206-
}
207-
208208
private Stream<String> getAdditionalProperties(final JSONObject subject) {
209209
String[] names = JSONObject.getNames(subject);
210210
if (names == null) {

core/src/main/java/org/everit/json/schema/ValidationException.java

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,30 @@
1919
import java.util.Collections;
2020
import java.util.List;
2121
import java.util.Objects;
22+
import java.util.stream.Collectors;
2223

2324
/**
2425
* Thrown by {@link Schema} subclasses on validation failure.
2526
*/
2627
public class ValidationException extends RuntimeException {
2728
private static final long serialVersionUID = 6192047123024651924L;
2829

30+
/**
31+
* Sort of static factory method. It is used by {@link ObjectSchema} and {@link ArraySchema} to
32+
* create {@code ValidationException}s, handling the case of multiple violations occuring during
33+
* validation.
34+
*
35+
* <p>
36+
* <ul>
37+
* <li>If {@code failures} is empty, then it doesn't do anything</li>
38+
* <li>If {@code failures} contains 1 exception instance, then that will be thrown</li>
39+
* <li>Otherwise a new exception instance will be created, its {@link #getViolatedSchema()
40+
* violated schema} will be {@code rootFailingSchema}, and its {@link #getCausingExceptions()
41+
* causing exceptions} will be the {@code failures} list</li>
42+
* </ul>
43+
* </p>
44+
*
45+
*/
2946
public static void throwFor(final Schema rootFailingSchema,
3047
final List<ValidationException> failures) {
3148
int failureCount = failures.size();
@@ -40,27 +57,27 @@ public static void throwFor(final Schema rootFailingSchema,
4057

4158
private final StringBuilder pointerToViolation;
4259

43-
private final Schema violatedSchema;
60+
private final transient Schema violatedSchema;
4461

4562
private final List<ValidationException> causingExceptions;
4663

4764
/**
4865
* Deprecated, use {@code ValidationException(Schema, Class<?>, Object)} instead.
49-
*
50-
* @param expectedType
51-
* @param actualValue
5266
*/
5367
@Deprecated
5468
public ValidationException(final Class<?> expectedType, final Object actualValue) {
5569
this(null, expectedType, actualValue);
5670
}
5771

72+
/**
73+
* Constructor.
74+
*/
5875
public ValidationException(final Schema violatedSchema, final Class<?> expectedType,
5976
final Object actualValue) {
6077
this(violatedSchema, new StringBuilder("#"),
6178
"expected type: " + expectedType.getSimpleName() + ", found: "
6279
+ (actualValue == null ? "null" : actualValue.getClass().getSimpleName()),
63-
Collections.emptyList());
80+
Collections.emptyList());
6481
}
6582

6683
private ValidationException(final Schema rootFailingSchema,
@@ -75,11 +92,7 @@ public ValidationException(final Schema violatedSchema, final String message) {
7592
}
7693

7794
/***
78-
*
79-
* @param violatedSchema
80-
* @param pointerToViolation
81-
* @param message
82-
* @param causingExceptions
95+
* Constructor.
8396
*/
8497
public ValidationException(final Schema violatedSchema, final StringBuilder pointerToViolation,
8598
final String message,
@@ -103,8 +116,9 @@ public ValidationException(final String message) {
103116

104117
private ValidationException(final StringBuilder pointerToViolation,
105118
final Schema violatedSchema,
106-
final ValidationException original) {
107-
this(violatedSchema, pointerToViolation, original.getMessage(), original.causingExceptions);
119+
final String message,
120+
final List<ValidationException> causingExceptions) {
121+
this(violatedSchema, pointerToViolation, message, causingExceptions);
108122
}
109123

110124
public List<ValidationException> getCausingExceptions() {
@@ -145,7 +159,11 @@ public ValidationException prepend(final String fragment) {
145159
public ValidationException prepend(final String fragment, final Schema violatedSchema) {
146160
Objects.requireNonNull(fragment, "fragment cannot be null");
147161
StringBuilder newPointer = this.pointerToViolation.insert(1, '/').insert(2, fragment);
148-
return new ValidationException(newPointer, violatedSchema, this);
162+
List<ValidationException> prependedCausingExceptions = causingExceptions.stream()
163+
.map(exc -> exc.prepend(fragment))
164+
.collect(Collectors.toList());
165+
return new ValidationException(newPointer, violatedSchema, getMessage(),
166+
prependedCausingExceptions);
149167
}
150168

151169
}

core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ public void additionalPropertySchema() {
4141
TestSupport.expectFailure(subject, "#/foo", OBJECTS.get("additionalPropertySchema"));
4242
}
4343

44-
private long countCauseByJsonPointer(final ValidationException root, final String pointer) {
45-
return root.getCausingExceptions().stream()
46-
.map(ValidationException::getPointerToViolation)
47-
.filter(ptr -> ptr.equals(pointer))
48-
.count();
49-
}
50-
5144
@Test
5245
public void maxPropertiesFailure() {
5346
ObjectSchema subject = ObjectSchema.builder().maxProperties(2).build();
@@ -114,9 +107,9 @@ public void multipleViolations() {
114107
Assert.fail("did not throw exception for 3 schema violations");
115108
} catch (ValidationException e) {
116109
Assert.assertEquals(3, e.getCausingExceptions().size());
117-
Assert.assertEquals(1, countCauseByJsonPointer(e, "#/numberProp"));
118-
Assert.assertEquals(1, countCauseByJsonPointer(e, "#"));
119-
Assert.assertEquals(1, countCauseByJsonPointer(e, "#/stringPatternMatch"));
110+
Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/numberProp"));
111+
Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#"));
112+
Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/stringPatternMatch"));
120113
}
121114
}
122115

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (C) 2011 Everit Kft. (http://www.everit.org)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.everit.json.schema;
17+
18+
import org.everit.json.schema.loader.SchemaLoader;
19+
import org.json.JSONObject;
20+
import org.json.JSONTokener;
21+
import org.junit.Assert;
22+
import org.junit.Test;
23+
24+
public class PointerBubblingTest {
25+
26+
private final JSONObject allSchemas = new JSONObject(new JSONTokener(
27+
getClass().getResourceAsStream("/org/everit/jsonvalidator/testschemas.json")));
28+
29+
private final Schema rectangleSchema = SchemaLoader
30+
.load(allSchemas.getJSONObject("pointerResolution"));
31+
32+
private final JSONObject testInputs = new JSONObject(new JSONTokener(
33+
getClass().getResourceAsStream("/org/everit/jsonvalidator/objecttestcases.json")));
34+
35+
@Test
36+
public void rectangleMultipleFailures() {
37+
JSONObject input = testInputs.getJSONObject("rectangleMultipleFailures");
38+
try {
39+
rectangleSchema.validate(input);
40+
Assert.fail();
41+
} catch (ValidationException e) {
42+
Assert.assertEquals("#/rectangle", e.getPointerToViolation());
43+
Assert.assertEquals(2, e.getCausingExceptions().size());
44+
Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/rectangle/a"));
45+
Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/rectangle/b"));
46+
}
47+
}
48+
49+
@Test
50+
public void rectangleSingleFailure() {
51+
JSONObject input = testInputs.getJSONObject("rectangleSingleFailure");
52+
TestSupport.expectFailure(rectangleSchema, NumberSchema.class, "#/rectangle/a", input);
53+
}
54+
55+
}

core/src/test/java/org/everit/json/schema/TestSupport.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,23 @@
1919

2020
public class TestSupport {
2121

22+
public static long countCauseByJsonPointer(final ValidationException root, final String pointer) {
23+
return root.getCausingExceptions().stream()
24+
.map(ValidationException::getPointerToViolation)
25+
.filter(ptr -> ptr.equals(pointer))
26+
.count();
27+
}
28+
29+
public static void expectFailure(final Schema failingSchema,
30+
final Class<? extends Schema> expectedViolatedSchemaClass,
31+
final String expectedPointer, final Object input) {
32+
try {
33+
test(failingSchema, expectedPointer, input);
34+
} catch (ValidationException e) {
35+
Assert.assertSame(expectedViolatedSchemaClass, e.getViolatedSchema().getClass());
36+
}
37+
}
38+
2239
public static void expectFailure(final Schema failingSchema, final Object input) {
2340
expectFailure(failingSchema, null, input);
2441
}
@@ -27,13 +44,9 @@ public static void expectFailure(final Schema failingSchema,
2744
final Schema expectedViolatedSchema,
2845
final String expectedPointer, final Object input) {
2946
try {
30-
failingSchema.validate(input);
31-
Assert.fail(failingSchema + " did not fail for " + input);
47+
test(failingSchema, expectedPointer, input);
3248
} catch (ValidationException e) {
3349
Assert.assertSame(expectedViolatedSchema, e.getViolatedSchema());
34-
if (expectedPointer != null) {
35-
Assert.assertEquals(expectedPointer, e.getPointerToViolation());
36-
}
3750
}
3851
}
3952

@@ -42,4 +55,17 @@ public static void expectFailure(final Schema failingSchema, final String expect
4255
expectFailure(failingSchema, failingSchema, expectedPointer, input);
4356
}
4457

58+
private static void test(final Schema failingSchema, final String expectedPointer,
59+
final Object input) {
60+
try {
61+
failingSchema.validate(input);
62+
Assert.fail(failingSchema + " did not fail for " + input);
63+
} catch (ValidationException e) {
64+
if (expectedPointer != null) {
65+
Assert.assertEquals(expectedPointer, e.getPointerToViolation());
66+
}
67+
throw e;
68+
}
69+
}
70+
4571
}

core/src/test/java/org/everit/json/schema/ValidationExceptionTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public void constructorNullSchema() {
3131
new ValidationException(null, Boolean.class, 2);
3232
}
3333

34+
private ValidationException createDummyException(final String pointer) {
35+
return new ValidationException(BooleanSchema.INSTANCE,
36+
new StringBuilder(pointer),
37+
"stuff went wrong", Collections.emptyList());
38+
}
39+
3440
@Test(expected = NullPointerException.class)
3541
public void nullPointerFragmentFailure() {
3642
new ValidationException(BooleanSchema.INSTANCE, Boolean.class, 2).prepend(null,
@@ -53,6 +59,24 @@ public void prependPointer() {
5359
Assert.assertEquals(NullSchema.INSTANCE, changedExc.getViolatedSchema());
5460
}
5561

62+
@Test
63+
public void prependWithCausingExceptions() {
64+
ValidationException cause1 = createDummyException("#/a");
65+
ValidationException cause2 = createDummyException("#/b");
66+
try {
67+
ValidationException.throwFor(rootSchema, Arrays.asList(cause1, cause2));
68+
Assert.fail();
69+
} catch (ValidationException e) {
70+
ValidationException actual = e.prepend("rectangle");
71+
Assert.assertEquals("#/rectangle", actual.getPointerToViolation());
72+
ValidationException changedCause1 = actual.getCausingExceptions().get(0);
73+
Assert.assertEquals("#/rectangle/a", changedCause1.getPointerToViolation());
74+
ValidationException changedCause2 = actual.getCausingExceptions().get(1);
75+
Assert.assertEquals("#/rectangle/b", changedCause2.getPointerToViolation());
76+
}
77+
78+
}
79+
5680
@Test
5781
public void testConstructor() {
5882
ValidationException exc = new ValidationException(BooleanSchema.INSTANCE, Boolean.class, 2);

core/src/test/resources/org/everit/jsonvalidator/objecttestcases.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,17 @@
3535
"multipleViolations" : {
3636
"numberProp" : "not number",
3737
"stringPatternMatch" : 2
38+
},
39+
"rectangleSingleFailure" : {
40+
"rectangle" : {
41+
"a" : -5,
42+
"b" : 5
43+
}
44+
},
45+
"rectangleMultipleFailures" : {
46+
"rectangle" : {
47+
"a" : -5,
48+
"b" : "asd"
49+
}
3850
}
3951
}

0 commit comments

Comments
 (0)