diff --git a/core/src/main/java/org/everit/json/schema/ValidationException.java b/core/src/main/java/org/everit/json/schema/ValidationException.java index 52f065cc8..24f62b05b 100644 --- a/core/src/main/java/org/everit/json/schema/ValidationException.java +++ b/core/src/main/java/org/everit/json/schema/ValidationException.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; @@ -37,6 +36,18 @@ private static int getViolationCount(final List causes) { return Math.max(1, causeCount); } + private static List getAllMessages(final List causes) { + List messages = causes.stream() + .filter(cause -> cause.causingExceptions.isEmpty()) + .map(ValidationException::getMessage) + .collect(Collectors.toList()); + messages.addAll(causes.stream() + .filter(cause -> !cause.causingExceptions.isEmpty()) + .flatMap(cause -> getAllMessages(cause.getCausingExceptions()).stream()) + .collect(Collectors.toList())); + return messages; + } + /** * Sort of static factory method. It is used by {@link ObjectSchema} and {@link ArraySchema} to * create {@code ValidationException}s, handling the case of multiple violations occuring during @@ -241,6 +252,18 @@ public List getCausingExceptions() { return causingExceptions; } + /** + * Returns all messages collected from all violations, including nested causing exceptions. + * @return all messages + */ + public List getAllMessages() { + if (causingExceptions.isEmpty()) { + return Collections.singletonList(getMessage()); + } else { + return getAllMessages(causingExceptions).stream().collect(Collectors.toList()); + } + } + /** * Returns a programmer-readable error description prepended by {@link #getPointerToViolation() * the pointer to the violating fragment} of the JSON document. diff --git a/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java b/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java index ef1fc43a2..11b87bc54 100644 --- a/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java +++ b/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java @@ -19,11 +19,12 @@ import nl.jqno.equalsverifier.Warning; import org.everit.json.schema.loader.SchemaLoader; import org.json.JSONObject; -import org.json.JSONTokener; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; +import java.util.List; +import java.util.concurrent.Callable; + import static org.junit.Assert.assertTrue; public class ObjectSchemaTest { @@ -136,9 +137,74 @@ public void multipleViolations() { Assert.fail("did not throw exception for 3 schema violations"); } catch (ValidationException e) { Assert.assertEquals(3, e.getCausingExceptions().size()); - Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/numberProp")); Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/numberProp")); Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(e, "#/stringPatternMatch")); + + List messages = e.getAllMessages(); + Assert.assertEquals(3, messages.size()); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/numberProp:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/stringPatternMatch:")); + } + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Test + public void multipleViolationsNested() throws Exception { + Callable newBuilder = () -> ObjectSchema.builder() + .addPropertySchema("numberProp", new NumberSchema()) + .patternProperty("^string.*", new StringSchema()) + .addPropertySchema("boolProp", BooleanSchema.INSTANCE) + .addRequiredProperty("boolProp"); + + Schema nested2 = newBuilder.call().build(); + Schema nested1 = newBuilder.call().addPropertySchema("nested", nested2).build(); + Schema subject = newBuilder.call().addPropertySchema("nested", nested1).build(); + + try { + subject.validate(OBJECTS.get("multipleViolationsNested")); + Assert.fail("did not throw exception for 9 schema violations"); + } catch (ValidationException subjectException) { + Assert.assertEquals("#: 9 schema violations found", subjectException.getMessage()); + Assert.assertEquals(4, subjectException.getCausingExceptions().size()); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(subjectException, "#")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(subjectException, "#/numberProp")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(subjectException, "#/stringPatternMatch")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(subjectException, "#/nested")); + + ValidationException nested1Exception = subjectException.getCausingExceptions().stream() + .filter(ex -> ex.getPointerToViolation().equals("#/nested")) + .findFirst() + .get(); + Assert.assertEquals("#/nested: 6 schema violations found", nested1Exception.getMessage()); + Assert.assertEquals(4, nested1Exception.getCausingExceptions().size()); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested1Exception, "#/nested")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested1Exception, "#/nested/numberProp")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested1Exception, "#/nested/stringPatternMatch")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested1Exception, "#/nested/nested")); + + ValidationException nested2Exception = nested1Exception.getCausingExceptions().stream() + .filter(ex -> ex.getPointerToViolation().equals("#/nested/nested")) + .findFirst() + .get(); + Assert.assertEquals("#/nested/nested: 3 schema violations found", nested2Exception.getMessage()); + Assert.assertEquals(3, nested2Exception.getCausingExceptions().size()); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested2Exception, "#/nested/nested")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested2Exception, "#/nested/nested/numberProp")); + Assert.assertEquals(1, TestSupport.countCauseByJsonPointer(nested2Exception, "#/nested/nested/stringPatternMatch")); + + List messages = subjectException.getAllMessages(); + Assert.assertEquals(9, messages.size()); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/numberProp:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/stringPatternMatch:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested/numberProp:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested/stringPatternMatch:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested/nested:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested/nested/numberProp:")); + Assert.assertEquals(1, TestSupport.countMatchingMessage(messages, "#/nested/nested/stringPatternMatch:")); } } diff --git a/core/src/test/java/org/everit/json/schema/TestSupport.java b/core/src/test/java/org/everit/json/schema/TestSupport.java index f40c615e4..dab3cf954 100644 --- a/core/src/test/java/org/everit/json/schema/TestSupport.java +++ b/core/src/test/java/org/everit/json/schema/TestSupport.java @@ -17,6 +17,8 @@ import org.junit.Assert; +import java.util.List; + public class TestSupport { public static class Failure { @@ -95,6 +97,12 @@ public static long countCauseByJsonPointer(final ValidationException root, final .count(); } + public static long countMatchingMessage(final List messages, final String expectedSubstring) { + return messages.stream() + .filter(message -> message.contains(expectedSubstring)) + .count(); + } + public static void expectFailure(final Schema failingSchema, final Class expectedViolatedSchemaClass, final String expectedPointer, final Object input) { diff --git a/core/src/test/resources/org/everit/jsonvalidator/objecttestcases.json b/core/src/test/resources/org/everit/jsonvalidator/objecttestcases.json index 70b2ca1bb..ecf33d2b5 100644 --- a/core/src/test/resources/org/everit/jsonvalidator/objecttestcases.json +++ b/core/src/test/resources/org/everit/jsonvalidator/objecttestcases.json @@ -40,6 +40,18 @@ "numberProp": "not number", "stringPatternMatch": 2 }, + "multipleViolationsNested": { + "numberProp": "not number", + "stringPatternMatch": 2, + "nested": { + "numberProp": "not number 1", + "stringPatternMatch": 11, + "nested": { + "numberProp": "not number 2", + "stringPatternMatch": 22 + } + } + }, "rectangleSingleFailure": { "rectangle": { "a": -5,