diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDoesNotExistException.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDoesNotExistException.java index 37d79493..3ba56eb0 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDoesNotExistException.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldDoesNotExistException.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ public class FieldDoesNotExistException extends RuntimeException { * * @param fieldPath the path of the field that does not exist */ - public FieldDoesNotExistException(JsonFieldPath fieldPath) { + public FieldDoesNotExistException(String fieldPath) { super("The payload does not contain a field with the path '" + fieldPath + "'"); } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java index ad948e82..103f4a22 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldPathPayloadSubsectionExtractor.java @@ -22,6 +22,8 @@ import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldPath.PathType; +import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; /** * A {@link PayloadSubsectionExtractor} that extracts the subsection of the JSON payload @@ -66,20 +68,20 @@ public class FieldPathPayloadSubsectionExtractor public byte[] extractSubsection(byte[] payload, MediaType contentType) { ObjectMapper objectMapper = new ObjectMapper(); try { - JsonFieldPath compiledPath = JsonFieldPath.compile(this.fieldPath); - Object extracted = new JsonFieldProcessor().extract(compiledPath, - objectMapper.readValue(payload, Object.class)); - if (extracted instanceof List && !compiledPath.isPrecise()) { - List extractedList = (List) extracted; + ExtractedField extractedField = new JsonFieldProcessor().extract( + this.fieldPath, objectMapper.readValue(payload, Object.class)); + Object value = extractedField.getValue(); + if (value instanceof List && extractedField.getType() == PathType.MULTI) { + List extractedList = (List) value; if (extractedList.size() == 1) { - extracted = extractedList.get(0); + value = extractedList.get(0); } else { throw new PayloadHandlingException(this.fieldPath + " does not uniquely identify a subsection of the payload"); } } - return objectMapper.writeValueAsBytes(extracted); + return objectMapper.writeValueAsBytes(value); } catch (IOException ex) { throw new PayloadHandlingException(ex); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java index 02eded55..afacb6e1 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java @@ -52,8 +52,8 @@ class JsonContentHandler implements ContentHandler { List missingFields = new ArrayList<>(); Object payload = readContent(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { - if (!fieldDescriptor.isOptional() && !this.fieldProcessor.hasField( - JsonFieldPath.compile(fieldDescriptor.getPath()), payload)) { + if (!fieldDescriptor.isOptional() && !this.fieldProcessor + .hasField(fieldDescriptor.getPath(), payload)) { missingFields.add(fieldDescriptor); } } @@ -65,12 +65,11 @@ class JsonContentHandler implements ContentHandler { public String getUndocumentedContent(List fieldDescriptors) { Object content = readContent(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { - JsonFieldPath path = JsonFieldPath.compile(fieldDescriptor.getPath()); if (describesSubsection(fieldDescriptor)) { - this.fieldProcessor.removeSubsection(path, content); + this.fieldProcessor.removeSubsection(fieldDescriptor.getPath(), content); } else { - this.fieldProcessor.remove(path, content); + this.fieldProcessor.remove(fieldDescriptor.getPath(), content); } } if (!isEmpty(content)) { @@ -107,7 +106,7 @@ class JsonContentHandler implements ContentHandler { @Override public Object determineFieldType(FieldDescriptor fieldDescriptor) { if (fieldDescriptor.getType() == null) { - return this.fieldTypeResolver.resolveFieldType(fieldDescriptor.getPath(), + return this.fieldTypeResolver.resolveFieldType(fieldDescriptor, readContent()); } if (!(fieldDescriptor.getType() instanceof JsonFieldType)) { @@ -116,7 +115,7 @@ class JsonContentHandler implements ContentHandler { JsonFieldType descriptorFieldType = (JsonFieldType) fieldDescriptor.getType(); try { JsonFieldType actualFieldType = this.fieldTypeResolver - .resolveFieldType(fieldDescriptor.getPath(), readContent()); + .resolveFieldType(fieldDescriptor, readContent()); if (descriptorFieldType == JsonFieldType.VARIES || descriptorFieldType == actualFieldType || (fieldDescriptor.isOptional() diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java index 35da9d65..2e12b050 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import java.util.regex.Pattern; * * @author Andy Wilkinson * @author Jeremy Rickard - * */ final class JsonFieldPath { @@ -41,24 +40,16 @@ final class JsonFieldPath { private final List segments; - private final boolean precise; + private final PathType type; - private final boolean array; - - private JsonFieldPath(String rawPath, List segments, boolean precise, - boolean array) { + private JsonFieldPath(String rawPath, List segments, PathType type) { this.rawPath = rawPath; this.segments = segments; - this.precise = precise; - this.array = array; + this.type = type; } - boolean isPrecise() { - return this.precise; - } - - boolean isArray() { - return this.array; + PathType getType() { + return this.type; } List getSegments() { @@ -72,9 +63,8 @@ final class JsonFieldPath { static JsonFieldPath compile(String path) { List segments = extractSegments(path); - String leafSegment = segments.get(segments.size() - 1); - return new JsonFieldPath(path, segments, matchesSingleValue(segments), - isArraySegment(leafSegment) || isWildcardSegment(leafSegment)); + return new JsonFieldPath(path, segments, + matchesSingleValue(segments) ? PathType.SINGLE : PathType.MULTI); } static boolean isArraySegment(String segment) { @@ -84,8 +74,9 @@ final class JsonFieldPath { static boolean matchesSingleValue(List segments) { Iterator iterator = segments.iterator(); while (iterator.hasNext()) { - String next = iterator.next(); - if ((isArraySegment(next) || isWildcardSegment(next)) && iterator.hasNext()) { + String segment = iterator.next(); + if ((isArraySegment(segment) && iterator.hasNext()) + || isWildcardSegment(segment)) { return false; } } @@ -132,4 +123,19 @@ final class JsonFieldPath { } return segments; } + + static enum PathType { + + /** + * The path identifies a single item in the payload + */ + SINGLE, + + /** + * The path identifies multiple items in the payload + */ + MULTI; + + } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldProcessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldProcessor.java index 2d22ee1d..187cf6b5 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldProcessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldProcessor.java @@ -22,6 +22,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.springframework.restdocs.payload.JsonFieldPath.PathType; + /** * A {@code JsonFieldProcessor} processes a payload's fields, allowing them to be * extracted and removed. @@ -31,15 +33,16 @@ import java.util.Map; */ final class JsonFieldProcessor { - boolean hasField(final JsonFieldPath fieldPath, Object payload) { + boolean hasField(String path, Object payload) { HasFieldMatchCallback callback = new HasFieldMatchCallback(); - traverse(new ProcessingContext(payload, fieldPath), callback); + traverse(new ProcessingContext(payload, JsonFieldPath.compile(path)), callback); return callback.fieldFound(); } - Object extract(JsonFieldPath path, Object payload) { + ExtractedField extract(String path, Object payload) { + JsonFieldPath compiledPath = JsonFieldPath.compile(path); final List matches = new ArrayList<>(); - traverse(new ProcessingContext(payload, path), new MatchCallback() { + traverse(new ProcessingContext(payload, compiledPath), new MatchCallback() { @Override public void foundMatch(Match match) { @@ -55,44 +58,43 @@ final class JsonFieldProcessor { if (matches.isEmpty()) { throw new FieldDoesNotExistException(path); } - if ((!path.isArray()) && path.isPrecise()) { - return matches.get(0); - } - else { - return matches; - } + return new ExtractedField( + compiledPath.getType() == PathType.SINGLE ? matches.get(0) : matches, + compiledPath.getType()); } - void remove(final JsonFieldPath path, Object payload) { - traverse(new ProcessingContext(payload, path), new MatchCallback() { + void remove(String path, Object payload) { + traverse(new ProcessingContext(payload, JsonFieldPath.compile(path)), + new MatchCallback() { - @Override - public void foundMatch(Match match) { - match.remove(); - } + @Override + public void foundMatch(Match match) { + match.remove(); + } - @Override - public void absent() { + @Override + public void absent() { - } + } - }); + }); } - void removeSubsection(final JsonFieldPath path, Object payload) { - traverse(new ProcessingContext(payload, path), new MatchCallback() { + void removeSubsection(String path, Object payload) { + traverse(new ProcessingContext(payload, JsonFieldPath.compile(path)), + new MatchCallback() { - @Override - public void foundMatch(Match match) { - match.removeSubsection(); - } + @Override + public void foundMatch(Match match) { + match.removeSubsection(); + } - @Override - public void absent() { + @Override + public void absent() { - } + } - }); + }); } private void traverse(ProcessingContext context, MatchCallback matchCallback) { @@ -115,6 +117,22 @@ final class JsonFieldProcessor { private void handleCollectionPayload(Collection collection, MatchCallback matchCallback, ProcessingContext context) { + if (context.isLeaf()) { + matchCallback.foundMatch( + new LeafCollectionMatch(collection, context.getParentMatch())); + } + else { + Iterator items = collection.iterator(); + while (items.hasNext()) { + Object item = items.next(); + traverse(context.descend(item, new CollectionMatch(items, collection, + item, context.getParentMatch())), matchCallback); + } + } + } + + private void handleWildcardPayload(Collection collection, + MatchCallback matchCallback, ProcessingContext context) { Iterator items = collection.iterator(); if (context.isLeaf()) { while (items.hasNext()) { @@ -147,7 +165,7 @@ final class JsonFieldProcessor { } } else if ("*".equals(context.getSegment())) { - handleCollectionPayload(map.values(), matchCallback, context); + handleWildcardPayload(map.values(), matchCallback, context); } else { matchCallback.absent(); @@ -309,6 +327,51 @@ final class JsonFieldProcessor { } + private static class LeafCollectionMatch implements Match { + + private final Collection collection; + + private final Match parent; + + public LeafCollectionMatch(Collection collection, Match parent) { + this.collection = collection; + this.parent = parent; + } + + @Override + public Collection getValue() { + return this.collection; + } + + @Override + public void remove() { + if (containsOnlyScalars(this.collection)) { + this.collection.clear(); + if (this.parent != null) { + this.parent.remove(); + } + } + } + + @Override + public void removeSubsection() { + this.collection.clear(); + if (this.parent != null) { + this.parent.removeSubsection(); + } + } + + private boolean containsOnlyScalars(Collection collection) { + for (Object item : collection) { + if (item instanceof Collection || item instanceof Map) { + return false; + } + } + return true; + } + + } + private interface MatchCallback { void foundMatch(Match match); @@ -371,4 +434,25 @@ final class JsonFieldProcessor { } } + static class ExtractedField { + + private final Object value; + + private final PathType type; + + ExtractedField(Object value, PathType type) { + this.value = value; + this.type = type; + } + + Object getValue() { + return this.value; + } + + PathType getType() { + return this.type; + } + + } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldTypeResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldTypeResolver.java index 30b577e9..9e6a7174 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldTypeResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonFieldTypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ package org.springframework.restdocs.payload; import java.util.Collection; import java.util.Map; +import org.springframework.restdocs.payload.JsonFieldPath.PathType; +import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField; + /** * Resolves the type of a field in a JSON request or response payload. * @@ -28,23 +31,32 @@ class JsonFieldTypeResolver { private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor(); - JsonFieldType resolveFieldType(String path, Object payload) { - JsonFieldPath fieldPath = JsonFieldPath.compile(path); - Object field = this.fieldProcessor.extract(fieldPath, payload); - if (field instanceof Collection && !fieldPath.isPrecise()) { + JsonFieldType resolveFieldType(FieldDescriptor fieldDescriptor, Object payload) { + ExtractedField extractedField = this.fieldProcessor + .extract(fieldDescriptor.getPath(), payload); + Object value = extractedField.getValue(); + if (value instanceof Collection && extractedField.getType() == PathType.MULTI) { JsonFieldType commonType = null; - for (Object item : (Collection) field) { + for (Object item : (Collection) value) { JsonFieldType fieldType = determineFieldType(item); if (commonType == null) { commonType = fieldType; } - else if (fieldType != commonType && fieldType != JsonFieldType.NULL) { - return JsonFieldType.VARIES; + else if (fieldType != commonType) { + if (!fieldDescriptor.isOptional()) { + return JsonFieldType.VARIES; + } + if (commonType == JsonFieldType.NULL) { + commonType = fieldType; + } + else if (fieldType != JsonFieldType.NULL) { + return JsonFieldType.VARIES; + } } } return commonType; } - return determineFieldType(field); + return determineFieldType(value); } private JsonFieldType determineFieldType(Object fieldValue) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java index 349ea04a..b2e5a582 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonContentHandlerTests.java @@ -44,7 +44,57 @@ public class JsonContentHandlerTests { } @Test - public void typeForOptionalFieldWithNullValueDoesNotHaveToMatch() throws IOException { + public void typeForFieldWithNotNullAndThenNullValueMustMatch() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + new JsonContentHandler("{\"a\":[{\"id\":1},{\"id\":null}]}".getBytes()) + .determineFieldType( + new FieldDescriptor("a[].id").type(JsonFieldType.STRING)); + } + + @Test + public void typeForFieldWithNullAndThenNotNullValueMustMatch() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + new JsonContentHandler("{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes()) + .determineFieldType( + new FieldDescriptor("a.[].id").type(JsonFieldType.STRING)); + } + + @Test + public void typeForOptionalFieldWithNumberAndThenNullValueIsNumber() + throws IOException { + Object fieldType = new JsonContentHandler( + "{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes()) + .determineFieldType(new FieldDescriptor("a[].id").optional()); + assertThat((JsonFieldType) fieldType, is(equalTo(JsonFieldType.NUMBER))); + } + + @Test + public void typeForOptionalFieldWithNullAndThenNumberIsNumber() throws IOException { + Object fieldType = new JsonContentHandler( + "{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes()) + .determineFieldType(new FieldDescriptor("a[].id").optional()); + assertThat((JsonFieldType) fieldType, is(equalTo(JsonFieldType.NUMBER))); + } + + @Test + public void typeForFieldWithNumberAndThenNullValueIsVaries() throws IOException { + Object fieldType = new JsonContentHandler( + "{\"a\":[{\"id\":1},{\"id\":null}]}\"".getBytes()) + .determineFieldType(new FieldDescriptor("a[].id")); + assertThat((JsonFieldType) fieldType, is(equalTo(JsonFieldType.VARIES))); + } + + @Test + public void typeForFieldWithNullAndThenNumberIsVaries() throws IOException { + Object fieldType = new JsonContentHandler( + "{\"a\":[{\"id\":null},{\"id\":1}]}".getBytes()) + .determineFieldType(new FieldDescriptor("a[].id")); + assertThat((JsonFieldType) fieldType, is(equalTo(JsonFieldType.VARIES))); + } + + @Test + public void typeForOptionalFieldWithNullValueCanBeProvidedExplicitly() + throws IOException { Object fieldType = new JsonContentHandler("{\"a\": null}".getBytes()) .determineFieldType( new FieldDescriptor("a").type(JsonFieldType.STRING).optional()); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java index 2d97fd5f..b0762d7f 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldPathTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,12 @@ package org.springframework.restdocs.payload; import org.junit.Test; +import org.springframework.restdocs.payload.JsonFieldPath.PathType; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.contains; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; /** * Tests for {@link JsonFieldPath}. @@ -32,59 +34,69 @@ import static org.junit.Assert.assertTrue; public class JsonFieldPathTests { @Test - public void singleFieldIsPreciseAndNotAnArray() { + public void pathTypeOfSingleFieldIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a"); - assertTrue(path.isPrecise()); - assertFalse(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.SINGLE))); } @Test - public void singleNestedFieldIsPreciseAndNotAnArray() { + public void pathTypeOfSingleNestedFieldIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a.b"); - assertTrue(path.isPrecise()); - assertFalse(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.SINGLE))); } @Test - public void topLevelArrayIsPreciseAndAnArray() { + public void pathTypeOfTopLevelArrayIsSingle() { JsonFieldPath path = JsonFieldPath.compile("[]"); - assertTrue(path.isPrecise()); - assertTrue(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.SINGLE))); } @Test - public void fieldBeneathTopLevelArrayIsNotPreciseAndNotAnArray() { + public void pathTypeOfFieldBeneathTopLevelArrayIsMulti() { JsonFieldPath path = JsonFieldPath.compile("[]a"); - assertFalse(path.isPrecise()); - assertFalse(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); } @Test - public void arrayIsPreciseAndAnArray() { + public void pathTypeOfSingleNestedArrayIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a[]"); - assertTrue(path.isPrecise()); - assertTrue(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.SINGLE))); } @Test - public void nestedArrayIsPreciseAndAnArray() { + public void pathTypeOfArrayBeneathNestedFieldsIsSingle() { JsonFieldPath path = JsonFieldPath.compile("a.b[]"); - assertTrue(path.isPrecise()); - assertTrue(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.SINGLE))); } @Test - public void arrayOfArraysIsNotPreciseAndIsAnArray() { + public void pathTypeOfArrayOfArraysIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a[][]"); - assertFalse(path.isPrecise()); - assertTrue(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); } @Test - public void fieldBeneathAnArrayIsNotPreciseAndIsNotAnArray() { + public void pathTypeOfFieldBeneathAnArrayIsMulti() { JsonFieldPath path = JsonFieldPath.compile("a[].b"); - assertFalse(path.isPrecise()); - assertFalse(path.isArray()); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); + } + + @Test + public void pathTypeOfFieldBeneathTopLevelWildcardIsMulti() { + JsonFieldPath path = JsonFieldPath.compile("*.a"); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); + } + + @Test + public void pathTypeOfFieldBeneathNestedWildcardIsMulti() { + JsonFieldPath path = JsonFieldPath.compile("a.*.b"); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); + } + + @Test + public void pathTypeOfLeafWidlcardIsMulti() { + JsonFieldPath path = JsonFieldPath.compile("a.*"); + assertThat(path.getType(), is(equalTo(PathType.MULTI))); } @Test @@ -157,18 +169,4 @@ public class JsonFieldPathTests { contains("a", "b", "*", "c")); } - @Test - public void fieldBeneathTopLevelWildcardIsNotPreciseAndNotAnArray() { - JsonFieldPath path = JsonFieldPath.compile("*.a"); - assertFalse(path.isPrecise()); - assertFalse(path.isArray()); - } - - @Test - public void fieldBeneathNestedWildcardIsNotPreciseAndNotAnArray() { - JsonFieldPath path = JsonFieldPath.compile("a.*.b"); - assertFalse(path.isPrecise()); - assertFalse(path.isArray()); - } - } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java index c694644d..26d83fec 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldProcessorTests.java @@ -19,6 +19,7 @@ package org.springframework.restdocs.payload; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -48,7 +49,7 @@ public class JsonFieldProcessorTests { public void extractTopLevelMapEntry() { Map payload = new HashMap<>(); payload.put("a", "alpha"); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload), + assertThat(this.fieldProcessor.extract("a", payload).getValue(), equalTo((Object) "alpha")); } @@ -58,7 +59,7 @@ public class JsonFieldProcessorTests { Map alpha = new HashMap<>(); payload.put("a", alpha); alpha.put("b", "bravo"); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload), + assertThat(this.fieldProcessor.extract("a.b", payload).getValue(), equalTo((Object) "bravo")); } @@ -69,7 +70,7 @@ public class JsonFieldProcessorTests { bravo.put("b", "bravo"); payload.add(bravo); payload.add(bravo); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("[]"), payload), + assertThat(this.fieldProcessor.extract("[]", payload).getValue(), equalTo((Object) payload)); } @@ -80,7 +81,7 @@ public class JsonFieldProcessorTests { bravo.put("b", "bravo"); List> alpha = Arrays.asList(bravo, bravo); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload), + assertThat(this.fieldProcessor.extract("a", payload).getValue(), equalTo((Object) alpha)); } @@ -91,7 +92,7 @@ public class JsonFieldProcessorTests { bravo.put("b", "bravo"); List> alpha = Arrays.asList(bravo, bravo); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload), + assertThat(this.fieldProcessor.extract("a[]", payload).getValue(), equalTo((Object) alpha)); } @@ -102,7 +103,7 @@ public class JsonFieldProcessorTests { entry.put("b", "bravo"); List> alpha = Arrays.asList(entry, entry); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[].b"), payload), + assertThat(this.fieldProcessor.extract("a[].b", payload).getValue(), equalTo((Object) Arrays.asList("bravo", "bravo"))); } @@ -114,7 +115,7 @@ public class JsonFieldProcessorTests { List> alpha = Arrays.asList(entry, new HashMap()); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[].b"), payload), + assertThat(this.fieldProcessor.extract("a[].b", payload).getValue(), equalTo((Object) Arrays.asList("bravo"))); } @@ -127,7 +128,7 @@ public class JsonFieldProcessorTests { nullField.put("b", null); List> alpha = Arrays.asList(nonNullField, nullField); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[].b"), payload), + assertThat(this.fieldProcessor.extract("a[].b", payload).getValue(), equalTo((Object) Arrays.asList("bravo", null))); } @@ -140,8 +141,9 @@ public class JsonFieldProcessorTests { List>> alpha = Arrays .asList(Arrays.asList(entry1, entry2), Arrays.asList(entry3)); payload.put("a", alpha); - assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][]"), payload), - equalTo((Object) Arrays.asList(entry1, entry2, entry3))); + assertThat(this.fieldProcessor.extract("a[][]", payload).getValue(), + equalTo((Object) Arrays.asList(Arrays.asList(entry1, entry2), + Arrays.asList(entry3)))); } @Test @@ -153,8 +155,7 @@ public class JsonFieldProcessorTests { List>> alpha = Arrays .asList(Arrays.asList(entry1, entry2), Arrays.asList(entry3)); payload.put("a", alpha); - assertThat( - this.fieldProcessor.extract(JsonFieldPath.compile("a[][].id"), payload), + assertThat(this.fieldProcessor.extract("a[][].id", payload).getValue(), equalTo((Object) Arrays.asList("1", "2", "3"))); } @@ -167,30 +168,28 @@ public class JsonFieldProcessorTests { List>> alpha = Arrays .asList(Arrays.asList(entry1, entry2), Arrays.asList(entry3)); payload.put("a", alpha); - assertThat( - this.fieldProcessor.extract(JsonFieldPath.compile("a[][].ids"), payload), + assertThat(this.fieldProcessor.extract("a[][].ids", payload).getValue(), equalTo((Object) Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3), Arrays.asList(4)))); } @Test(expected = FieldDoesNotExistException.class) public void nonExistentTopLevelField() { - this.fieldProcessor.extract(JsonFieldPath.compile("a"), - new HashMap()); + this.fieldProcessor.extract("a", Collections.emptyMap()).getValue(); } @Test(expected = FieldDoesNotExistException.class) public void nonExistentNestedField() { HashMap payload = new HashMap<>(); payload.put("a", new HashMap()); - this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload); + this.fieldProcessor.extract("a.b", payload).getValue(); } @Test(expected = FieldDoesNotExistException.class) public void nonExistentNestedFieldWhenParentIsNotAMap() { HashMap payload = new HashMap<>(); payload.put("a", 5); - this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload); + this.fieldProcessor.extract("a.b", payload).getValue(); } @Test(expected = FieldDoesNotExistException.class) @@ -199,20 +198,20 @@ public class JsonFieldProcessorTests { HashMap alpha = new HashMap<>(); alpha.put("b", Arrays.asList(new HashMap())); payload.put("a", alpha); - this.fieldProcessor.extract(JsonFieldPath.compile("a.b.c"), payload); + this.fieldProcessor.extract("a.b.c", payload).getValue(); } @Test(expected = FieldDoesNotExistException.class) public void nonExistentArrayField() { HashMap payload = new HashMap<>(); - this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.extract("a[]", payload).getValue(); } @Test(expected = FieldDoesNotExistException.class) public void nonExistentArrayFieldAsTypeDoesNotMatch() { HashMap payload = new HashMap<>(); payload.put("a", 5); - this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.extract("a[]", payload).getValue(); } @Test(expected = FieldDoesNotExistException.class) @@ -221,14 +220,14 @@ public class JsonFieldProcessorTests { HashMap alpha = new HashMap<>(); alpha.put("b", Arrays.asList(new HashMap())); payload.put("a", alpha); - this.fieldProcessor.extract(JsonFieldPath.compile("a.b[].id"), payload); + this.fieldProcessor.extract("a.b[].id", payload).getValue(); } @Test public void removeTopLevelMapEntry() { Map payload = new HashMap<>(); payload.put("a", "alpha"); - this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload); + this.fieldProcessor.remove("a", payload); assertThat(payload.size(), equalTo(0)); } @@ -238,7 +237,7 @@ public class JsonFieldProcessorTests { Map alpha = new HashMap<>(); payload.put("a", alpha); alpha.put("b", "bravo"); - this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload); + this.fieldProcessor.remove("a", payload); assertThat(payload.size(), equalTo(1)); } @@ -248,7 +247,7 @@ public class JsonFieldProcessorTests { Map alpha = new HashMap<>(); payload.put("a", alpha); alpha.put("b", "bravo"); - this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a"), payload); + this.fieldProcessor.removeSubsection("a", payload); assertThat(payload.size(), equalTo(0)); } @@ -258,7 +257,7 @@ public class JsonFieldProcessorTests { Map alpha = new HashMap<>(); payload.put("a", alpha); alpha.put("b", "bravo"); - this.fieldProcessor.remove(JsonFieldPath.compile("a.b"), payload); + this.fieldProcessor.remove("a.b", payload); assertThat(payload.size(), equalTo(0)); } @@ -267,7 +266,7 @@ public class JsonFieldProcessorTests { public void removeItemsInArray() throws IOException { Map payload = new ObjectMapper() .readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); - this.fieldProcessor.remove(JsonFieldPath.compile("a[].b"), payload); + this.fieldProcessor.remove("a[].b", payload); assertThat(payload.size(), equalTo(0)); } @@ -276,7 +275,7 @@ public class JsonFieldProcessorTests { public void removeItemsInNestedArray() throws IOException { Map payload = new ObjectMapper() .readValue("{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}", Map.class); - this.fieldProcessor.remove(JsonFieldPath.compile("a[][].id"), payload); + this.fieldProcessor.remove("a[][].id", payload); assertThat(payload.size(), equalTo(0)); } @@ -285,7 +284,7 @@ public class JsonFieldProcessorTests { public void removeDoesNotRemoveArrayWithMapEntries() throws IOException { Map payload = new ObjectMapper() .readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); - this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.remove("a[]", payload); assertThat(payload.size(), equalTo(1)); } @@ -294,7 +293,7 @@ public class JsonFieldProcessorTests { public void removeDoesNotRemoveArrayWithListEntries() throws IOException { Map payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); - this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.remove("a[]", payload); assertThat(payload.size(), equalTo(1)); } @@ -303,7 +302,7 @@ public class JsonFieldProcessorTests { public void removeRemovesArrayWithOnlyScalarEntries() throws IOException { Map payload = new ObjectMapper() .readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class); - this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload); + this.fieldProcessor.remove("a", payload); assertThat(payload.size(), equalTo(0)); } @@ -312,7 +311,7 @@ public class JsonFieldProcessorTests { public void removeSubsectionRemovesArrayWithMapEntries() throws IOException { Map payload = new ObjectMapper() .readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class); - this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.removeSubsection("a[]", payload); assertThat(payload.size(), equalTo(0)); } @@ -321,7 +320,7 @@ public class JsonFieldProcessorTests { public void removeSubsectionRemovesArrayWithListEntries() throws IOException { Map payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}", Map.class); - this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload); + this.fieldProcessor.removeSubsection("a[]", payload); assertThat(payload.size(), equalTo(0)); } @@ -331,8 +330,7 @@ public class JsonFieldProcessorTests { Map alpha = new HashMap<>(); payload.put("a.key", alpha); alpha.put("b.key", "bravo"); - assertThat(this.fieldProcessor - .extract(JsonFieldPath.compile("['a.key']['b.key']"), payload), + assertThat(this.fieldProcessor.extract("['a.key']['b.key']", payload).getValue(), equalTo((Object) "bravo")); } @@ -346,8 +344,7 @@ public class JsonFieldProcessorTests { Map charlie = new LinkedHashMap<>(); charlie.put("b", "bravo2"); payload.put("c", charlie); - assertThat((List) this.fieldProcessor - .extract(JsonFieldPath.compile("*.b"), payload), + assertThat((List) this.fieldProcessor.extract("*.b", payload).getValue(), contains("bravo1", "bravo2")); } @@ -361,8 +358,8 @@ public class JsonFieldProcessorTests { bravo.put("b", "bravo"); alpha.put("one", bravo); alpha.put("two", bravo); - assertThat((List) this.fieldProcessor - .extract(JsonFieldPath.compile("a.*.b"), payload), + assertThat( + (List) this.fieldProcessor.extract("a.*.b", payload).getValue(), contains("bravo", "bravo")); } @@ -376,8 +373,8 @@ public class JsonFieldProcessorTests { Map charlie = new HashMap<>(); charlie.put("b", "bravo2"); payload.put("c", charlie); - assertThat((List) this.fieldProcessor - .extract(JsonFieldPath.compile("a.*"), payload), contains("bravo1")); + assertThat((List) this.fieldProcessor.extract("a.*", payload).getValue(), + contains("bravo1")); } @SuppressWarnings("unchecked") @@ -388,8 +385,7 @@ public class JsonFieldProcessorTests { payload.put("a", alpha); alpha.put("b", "bravo1"); alpha.put("c", "charlie"); - assertThat((List) this.fieldProcessor - .extract(JsonFieldPath.compile("a.*"), payload), + assertThat((List) this.fieldProcessor.extract("a.*", payload).getValue(), contains("bravo1", "charlie")); } @@ -400,7 +396,7 @@ public class JsonFieldProcessorTests { payload.put("a", alpha); alpha.put("b", "bravo1"); alpha.put("c", "charlie"); - this.fieldProcessor.remove(JsonFieldPath.compile("a.*"), payload); + this.fieldProcessor.remove("a.*", payload); assertThat(payload.size(), equalTo(0)); } @@ -411,7 +407,7 @@ public class JsonFieldProcessorTests { payload.put("a", alpha); alpha.put("b", "bravo1"); alpha.put("c", "charlie"); - this.fieldProcessor.remove(JsonFieldPath.compile("*.b"), payload); + this.fieldProcessor.remove("*.b", payload); assertThat(alpha, not(hasKey("b"))); } @@ -427,7 +423,7 @@ public class JsonFieldProcessorTests { Map bravo2 = new LinkedHashMap<>(); bravo2.put("b", "bravo"); alpha.put("two", bravo2); - this.fieldProcessor.remove(JsonFieldPath.compile("a.*.b"), payload); + this.fieldProcessor.remove("a.*.b", payload); assertThat(payload.size(), equalTo(1)); assertThat(payload, hasEntry("c", (Object) "charlie")); } @@ -436,24 +432,21 @@ public class JsonFieldProcessorTests { public void hasFieldIsTrueForNonNullFieldInMap() throws Exception { Map payload = new HashMap<>(); payload.put("a", "alpha"); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a"), payload), - is(true)); + assertThat(this.fieldProcessor.hasField("a", payload), is(true)); } @Test public void hasFieldIsTrueForNullFieldInMap() throws Exception { Map payload = new HashMap<>(); payload.put("a", null); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a"), payload), - is(true)); + assertThat(this.fieldProcessor.hasField("a", payload), is(true)); } @Test public void hasFieldIsFalseForAbsentFieldInMap() throws Exception { Map payload = new HashMap<>(); payload.put("a", null); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("b"), payload), - is(false)); + assertThat(this.fieldProcessor.hasField("b", payload), is(false)); } @Test @@ -462,8 +455,7 @@ public class JsonFieldProcessorTests { Map nested = new HashMap<>(); nested.put("b", "bravo"); payload.put("a", Arrays.asList(nested, nested, nested)); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a.[].b"), payload), - is(true)); + assertThat(this.fieldProcessor.hasField("a.[].b", payload), is(true)); } @Test @@ -472,8 +464,7 @@ public class JsonFieldProcessorTests { Map nested = new HashMap<>(); nested.put("b", null); payload.put("a", Arrays.asList(nested, nested, nested)); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a.[].b"), payload), - is(true)); + assertThat(this.fieldProcessor.hasField("a.[].b", payload), is(true)); } @Test @@ -482,8 +473,7 @@ public class JsonFieldProcessorTests { Map nested = new HashMap<>(); nested.put("b", "bravo"); payload.put("a", Arrays.asList(nested, nested, nested)); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a.[].c"), payload), - is(false)); + assertThat(this.fieldProcessor.hasField("a.[].c", payload), is(false)); } @Test @@ -492,8 +482,7 @@ public class JsonFieldProcessorTests { Map nested = new HashMap<>(); nested.put("b", "bravo"); payload.put("a", Arrays.asList(nested, new HashMap<>(), nested)); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a.[].b"), payload), - is(false)); + assertThat(this.fieldProcessor.hasField("a.[].b", payload), is(false)); } @Test @@ -504,8 +493,7 @@ public class JsonFieldProcessorTests { Map fieldNull = new HashMap<>(); fieldNull.put("b", null); payload.put("a", Arrays.asList(fieldPresent, fieldPresent, fieldNull)); - assertThat(this.fieldProcessor.hasField(JsonFieldPath.compile("a.[].b"), payload), - is(false)); + assertThat(this.fieldProcessor.hasField("a.[].b", payload), is(false)); } private Map createEntry(String... pairs) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypeResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypeResolverTests.java index 10d38d12..14ac262f 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypeResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/JsonFieldTypeResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ import static org.junit.Assert.assertThat; * Tests for {@link JsonFieldTypeResolver}. * * @author Andy Wilkinson - * */ public class JsonFieldTypeResolverTests { @@ -49,7 +48,7 @@ public class JsonFieldTypeResolverTests { @Test public void topLevelArray() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("[]", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("[]"), new ObjectMapper().readValue("[{\"a\":\"alpha\"}]", List.class)), equalTo(JsonFieldType.ARRAY)); } @@ -57,11 +56,27 @@ public class JsonFieldTypeResolverTests { @Test public void nestedArray() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[]", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[]"), createPayload("{\"a\": [{\"b\":\"bravo\"}]}")), equalTo(JsonFieldType.ARRAY)); } + @Test + public void arrayNestedBeneathAnArray() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].b[]"), + createPayload("{\"a\": [{\"b\": [ 1, 2 ]}]}")), + equalTo(JsonFieldType.ARRAY)); + } + + @Test + public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].b[].c"), + createPayload("{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}")), + equalTo(JsonFieldType.NUMBER)); + } + @Test public void booleanField() throws IOException { assertFieldType(JsonFieldType.BOOLEAN, "true"); @@ -90,7 +105,7 @@ public class JsonFieldTypeResolverTests { @Test public void nestedField() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a.b.c", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.b.c"), createPayload("{\"a\":{\"b\":{\"c\":{}}}}")), equalTo(JsonFieldType.OBJECT)); } @@ -98,7 +113,7 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsWithSameType() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{\"id\":1},{\"id\":2}]}")), equalTo(JsonFieldType.NUMBER)); } @@ -106,7 +121,7 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsWithDifferentTypes() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{\"id\":1},{\"id\":true}]}")), equalTo(JsonFieldType.VARIES)); } @@ -114,7 +129,17 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), + createPayload("{\"a\":[{\"id\":1},{\"id\":true}, { }]}")), + equalTo(JsonFieldType.VARIES)); + } + + @Test + public void multipleFieldsWithDifferentTypesAndSometimesAbsentWhenOptionalResolvesToVaries() + throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType( + new FieldDescriptor("a[].id").optional(), createPayload("{\"a\":[{\"id\":1},{\"id\":true}, { }]}")), equalTo(JsonFieldType.VARIES)); } @@ -122,7 +147,7 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsWhenSometimesAbsent() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{\"id\":1},{ }]}")), equalTo(JsonFieldType.NUMBER)); } @@ -130,24 +155,54 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload( "{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}")), equalTo(JsonFieldType.VARIES)); } @Test - public void multipleFieldsWhenSometimesNull() throws IOException { + public void multipleFieldsWhenNotNullThenNullWhenRequiredHasVariesType() + throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{\"id\":1},{\"id\":null}]}")), + equalTo(JsonFieldType.VARIES)); + } + + @Test + public void multipleFieldsWhenNotNullThenNullWhenOptionalHasSpecificType() + throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType( + new FieldDescriptor("a[].id").optional(), + createPayload("{\"a\":[{\"id\":1},{\"id\":null}]}")), + equalTo(JsonFieldType.NUMBER)); + } + + @Test + public void multipleFieldsWhenNullThenNotNullWhenRequiredHasVariesType() + throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), + createPayload("{\"a\":[{\"id\":null},{\"id\":1}]}")), + equalTo(JsonFieldType.VARIES)); + } + + @Test + public void multipleFieldsWhenNullThenNotNullWhenOptionalHasSpecificType() + throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType( + new FieldDescriptor("a[].id").optional(), + createPayload("{\"a\":[{\"id\":null},{\"id\":1}]}")), equalTo(JsonFieldType.NUMBER)); } @Test public void multipleFieldsWhenEitherNullOrAbsent() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{},{\"id\":null}]}")), equalTo(JsonFieldType.NULL)); } @@ -155,7 +210,7 @@ public class JsonFieldTypeResolverTests { @Test public void multipleFieldsThatAreAllNull() throws IOException { assertThat( - this.fieldTypeResolver.resolveFieldType("a[].id", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"), createPayload("{\"a\":[{\"id\":null},{\"id\":null}]}")), equalTo(JsonFieldType.NULL)); } @@ -166,7 +221,8 @@ public class JsonFieldTypeResolverTests { this.thrownException.expect(FieldDoesNotExistException.class); this.thrownException.expectMessage( "The payload does not contain a field with the path 'a.b'"); - this.fieldTypeResolver.resolveFieldType("a.b", createPayload("{\"a\":{}}")); + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.b"), + createPayload("{\"a\":{}}")); } @Test @@ -175,13 +231,47 @@ public class JsonFieldTypeResolverTests { this.thrownException.expect(FieldDoesNotExistException.class); this.thrownException.expectMessage( "The payload does not contain a field with the path 'a[].b'"); - this.fieldTypeResolver.resolveFieldType("a[].b", + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].b"), createPayload("{\"a\":[{\"c\":1},{\"c\":2}]}")); } + @Test + public void leafWildcardWithCommonType() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*"), + createPayload("{\"a\": {\"b\": 5, \"c\": 6}}")), + equalTo(JsonFieldType.NUMBER)); + } + + @Test + public void leafWildcardWithVaryingType() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*"), + createPayload("{\"a\": {\"b\": 5, \"c\": \"six\"}}")), + equalTo(JsonFieldType.VARIES)); + } + + @Test + public void intermediateWildcardWithCommonType() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*.d"), + createPayload( + "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}")), + equalTo(JsonFieldType.NUMBER)); + } + + @Test + public void intermediateWildcardWithVaryingType() throws IOException { + assertThat( + this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*.d"), + createPayload( + "{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}")), + equalTo(JsonFieldType.VARIES)); + } + private void assertFieldType(JsonFieldType expectedType, String jsonValue) throws IOException { - assertThat(this.fieldTypeResolver.resolveFieldType("field", + assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("field"), createSimplePayload(jsonValue)), equalTo(expectedType)); }