فهرست منبع

Make uniqueItems and SizeTester pass.
Add new test for sub-components to tests for uniqueItems.

Sam Jaffe 6 سال پیش
والد
کامیت
3f54df9366

+ 12 - 0
pom.xml

@@ -127,5 +127,17 @@
       <artifactId>container</artifactId>
       <version>0.2.1</version>
     </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-all</artifactId>
+      <version>1.3</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>

+ 1 - 0
src/main/lombok/org/leumasjaffe/json/schema/ValidationException.java

@@ -22,6 +22,7 @@ public class ValidationException extends IllegalArgumentException {
 		super(message);
 		this.jsonPath.add(path);
 		this.causingExceptions = Collections.unmodifiableList(Arrays.asList(causes));
+		this.causingExceptions.forEach(ve -> ve.jsonPath.add(0, path));
 	}
 
 	public ValidationException(String key, String message) {

+ 43 - 5
src/main/lombok/org/leumasjaffe/json/schema/tester/SizeTester.java

@@ -1,7 +1,5 @@
 package org.leumasjaffe.json.schema.tester;
 
-import org.leumasjaffe.json.schema.Tester;
-
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.JsonNodeType;
 
@@ -11,7 +9,7 @@ import lombok.experimental.FieldDefaults;
 
 @RequiredArgsConstructor(access=AccessLevel.PRIVATE)
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class SizeTester implements Tester {
+public class SizeTester extends SimpleValidationTester {
 	private static enum Rule {
 		MIN_ELEMS, MAX_ELEMS;
 
@@ -19,9 +17,26 @@ public class SizeTester implements Tester {
 			switch(this) {
 			case MIN_ELEMS: return elems <= size;
 			case MAX_ELEMS: return elems >= size;
-			default: return false;
+			default: throw new IllegalStateException();
+			}
+		}
+
+		public String jsonName() {
+			switch(this) {
+			case MIN_ELEMS: return "min";
+			case MAX_ELEMS: return "max";
+			default: throw new IllegalStateException();
 			}
 		}
+
+		public String compare() {
+			switch(this) {
+			case MIN_ELEMS: return " is less than ";
+			case MAX_ELEMS: return " is greater than ";
+			default: throw new IllegalStateException();
+			}
+		}
+		
 	}
 	
 	public static SizeTester minLength(int elems) {
@@ -52,6 +67,20 @@ public class SizeTester implements Tester {
 	Rule rule;
 	int elems;
 	
+	@Override
+	String name() {
+		return rule.jsonName() + argumentName();
+	}
+	
+	@Override
+	String errorMessage(JsonNode node) {
+		final StringBuilder build = new StringBuilder();
+		build.append(type).append(" element count of ").append(getSize(node));
+		build.append(rule.compare()).append(rule.jsonName()).append("imum");
+		build.append(" of ").append(elems);
+		return build.toString();
+	}
+
 	@Override
 	public boolean accepts(JsonNode node) {
 		return node.getNodeType() == type && rule.test(elems, getSize(node));
@@ -60,5 +89,14 @@ public class SizeTester implements Tester {
 	private int getSize(JsonNode node) {
 		return node.isTextual() ? node.asText().length() : node.size();
 	}
-
+	
+	private String argumentName() {
+		switch (type) {
+		case OBJECT: return "Properties";
+		case ARRAY: return "Items";
+		case STRING: return "Length";
+		default: throw new IllegalStateException();
+		}
+	}
+	
 }

+ 39 - 0
src/main/lombok/org/leumasjaffe/json/schema/tester/UniqueItemTester.java

@@ -1,14 +1,31 @@
 package org.leumasjaffe.json.schema.tester;
 
 import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import org.leumasjaffe.json.schema.Tester;
+import org.leumasjaffe.json.schema.ValidationException;
 
 import com.fasterxml.jackson.databind.JsonNode;
 
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
 public class UniqueItemTester implements Tester {
 	public static final Tester INSTANCE = new UniqueItemTester();
+	
+	@Override
+	public void validate(final JsonNode node) throws ValidationException {
+		final List<ValidationException> exceptions = new Helper(node).validate();
+		if (!exceptions.isEmpty()) {
+			throw new ValidationException("uniqueItems", "duplicate items in array",
+					exceptions.toArray(new ValidationException[0]));
+		}
+	}
 
 	@Override
 	public boolean accepts(final JsonNode node) {
@@ -17,5 +34,27 @@ public class UniqueItemTester implements Tester {
 		node.iterator().forEachRemaining(nodes::add);
 		return nodes.size() == node.size();
 	}
+	
+	@RequiredArgsConstructor
+	@FieldDefaults(makeFinal=true)
+	private static class Helper {
+		Set<JsonNode> items = new HashSet<>();
+		JsonNode node;
+		
+		List<ValidationException> validate() {
+			return IntStream.range(0, node.size()).mapToObj(this::validate)
+					.filter(Optional::isPresent).map(Optional::get)
+					.collect(Collectors.toList());
+		}
+		
+		private Optional<ValidationException> validate(int i) {
+			if (items.add(node.get(i))) {
+				return Optional.empty();
+			} else {
+				return Optional.of(new ValidationException("", 
+						"non-unique array item at index " + i));
+			}
+		}
+	}
 
 }

+ 37 - 0
src/test/java/org/leumasjaffe/json/schema/matcher/CausingExceptions.java

@@ -0,0 +1,37 @@
+package org.leumasjaffe.json.schema.matcher;
+
+import java.util.List;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.leumasjaffe.json.schema.ValidationException;
+
+public class CausingExceptions extends TypeSafeMatcher<ValidationException> {
+	public static Matcher<ValidationException> causedBy(Matcher<? super List<ValidationException>> sub) {
+		return new CausingExceptions(sub);
+	}
+	
+	private final Matcher<? super List<ValidationException>> causedBy;
+	
+	private CausingExceptions(Matcher<? super List<ValidationException>> causedBy) {
+		this.causedBy = causedBy;
+	}
+
+	@Override
+	public void describeTo(Description arg0) {
+		arg0.appendText("caused by ");
+		causedBy.describeTo(arg0);
+	}
+	
+	@Override
+	public void describeMismatchSafely(ValidationException arg0, Description arg1) {
+		arg1.appendText("was caused by ").appendValue(arg0.getCausingExceptions());
+	}
+
+	@Override
+	protected boolean matchesSafely(ValidationException arg0) {
+		return causedBy.matches(arg0.getCausingExceptions());
+	}
+
+}

+ 16 - 0
src/test/java/org/leumasjaffe/json/schema/tester/UniqueItemTesterTest.java

@@ -1,7 +1,11 @@
 package org.leumasjaffe.json.schema.tester;
 
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.collection.IsIterableWithSize.iterableWithSize;
+import static org.hamcrest.core.Every.everyItem;
 import static org.junit.Assert.assertThat;
 import static org.leumasjaffe.json.schema.matcher.Accepts.accepts;
+import static org.leumasjaffe.json.schema.matcher.CausingExceptions.causedBy;
 import static org.leumasjaffe.json.schema.matcher.JsonPath.jsonPath;
 import static org.leumasjaffe.json.schema.matcher.Not.not;
 
@@ -73,4 +77,16 @@ public class UniqueItemTesterTest {
 		unique.validate(arrayRepeated);
 	}
 	
+	@Test
+	public void testProducesSubExceptionForEachDuplicate() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(1.5);
+		node.add(1.5);
+		node.add(1.5);
+		thrown.expect(ValidationException.class);
+		thrown.expect(causedBy(allOf(iterableWithSize(2),
+				everyItem(jsonPath("#/uniqueItems/")))));
+		unique.validate(node);
+	}
+	
 }