From 7b3a5d6c1bcd58253014d0547141723c03e093fe Mon Sep 17 00:00:00 2001 From: Steven Farley Date: Sun, 6 Nov 2016 07:12:13 -0500 Subject: [PATCH 1/2] Add test for multiple nested violations --- .../everit/json/schema/ObjectSchemaTest.java | 51 ++++++++++++++++++- .../everit/jsonvalidator/objecttestcases.json | 12 +++++ 2 files changed, 61 insertions(+), 2 deletions(-) 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..b8e85bf46 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,11 @@ 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.concurrent.Callable; + import static org.junit.Assert.assertTrue; public class ObjectSchemaTest { @@ -142,6 +142,53 @@ public void multipleViolations() { } } + @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")); + } + } + @Test public void noAdditionalProperties() { ObjectSchema subject = ObjectSchema.builder().additionalProperties(false).build(); 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, From 0a9618ad693db3ae244d95f9b760e2ec26de714b Mon Sep 17 00:00:00 2001 From: Steven Farley Date: Sun, 6 Nov 2016 09:37:49 -0500 Subject: [PATCH 2/2] Add ValidationException#getAllMessages() Walk through all nested violations and return the list of error messages. This is particularly useful for use by a REST API when validating the schema of an incoming JSON payload. The flat list of messages provides a convenient format for clients to log, and even parse, the errors. --- .../json/schema/ValidationException.java | 25 ++++++++++++++++++- .../everit/json/schema/ObjectSchemaTest.java | 21 +++++++++++++++- .../org/everit/json/schema/TestSupport.java | 8 ++++++ 3 files changed, 52 insertions(+), 2 deletions(-) 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 b8e85bf46..11b87bc54 100644 --- a/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java +++ b/core/src/test/java/org/everit/json/schema/ObjectSchemaTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; +import java.util.List; import java.util.concurrent.Callable; import static org.junit.Assert.assertTrue; @@ -136,9 +137,15 @@ 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:")); } } @@ -186,6 +193,18 @@ public void multipleViolationsNested() throws Exception { 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) {