Rework JsonFieldTypeResolver to only discover the field types

See gh-549
This commit is contained in:
Andy Wilkinson
2018-11-21 13:03:24 +00:00
parent 9deae49795
commit ada273ba9e
8 changed files with 402 additions and 314 deletions

View File

@@ -28,6 +28,8 @@ import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.springframework.restdocs.payload.JsonFieldProcessor.ExtractedField;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -109,7 +111,7 @@ public class JsonFieldProcessorTests {
new HashMap<String, Object>());
payload.put("a", alpha);
assertThat(this.fieldProcessor.extract("a[].b", payload).getValue())
.isEqualTo(Arrays.asList("bravo"));
.isEqualTo(Arrays.asList("bravo", ExtractedField.ABSENT));
}
@Test
@@ -165,54 +167,61 @@ public class JsonFieldProcessorTests {
Arrays.asList(4)));
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentTopLevelField() {
this.fieldProcessor.extract("a", Collections.emptyMap());
assertThat(this.fieldProcessor.extract("a", Collections.emptyMap()).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentNestedField() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", new HashMap<String, Object>());
this.fieldProcessor.extract("a.b", payload);
assertThat(this.fieldProcessor.extract("a.b", payload).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentNestedFieldWhenParentIsNotAMap() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5);
this.fieldProcessor.extract("a.b", payload);
assertThat(this.fieldProcessor.extract("a.b", payload).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentFieldWhenParentIsAnArray() {
HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
payload.put("a", alpha);
this.fieldProcessor.extract("a.b.c", payload);
assertThat(this.fieldProcessor.extract("a.b.c", payload).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentArrayField() {
HashMap<String, Object> payload = new HashMap<>();
this.fieldProcessor.extract("a[]", payload);
assertThat(this.fieldProcessor.extract("a[]", payload).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentArrayFieldAsTypeDoesNotMatch() {
HashMap<String, Object> payload = new HashMap<>();
payload.put("a", 5);
this.fieldProcessor.extract("a[]", payload);
assertThat(this.fieldProcessor.extract("a[]", payload).getValue())
.isEqualTo(ExtractedField.ABSENT);
}
@Test(expected = FieldDoesNotExistException.class)
@Test
public void nonExistentFieldBeneathAnArray() {
HashMap<String, Object> payload = new HashMap<>();
HashMap<String, Object> alpha = new HashMap<>();
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
payload.put("a", alpha);
this.fieldProcessor.extract("a.b[].id", payload);
assertThat(this.fieldProcessor.extract("a.b[].id", payload).getValue())
.isEqualTo(Arrays.asList(ExtractedField.ABSENT));
}
@Test

View File

@@ -1,263 +0,0 @@
/*
* Copyright 2014-2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonFieldTypeResolver}.
*
* @author Andy Wilkinson
*/
public class JsonFieldTypeResolverTests {
private final JsonFieldTypeResolver fieldTypeResolver = new JsonFieldTypeResolver();
@Rule
public ExpectedException thrownException = ExpectedException.none();
@Test
public void arrayField() throws IOException {
assertFieldType(JsonFieldType.ARRAY, "[]");
}
@Test
public void topLevelArray() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("[]"),
new ObjectMapper().readValue("[{\"a\":\"alpha\"}]", List.class)))
.isEqualTo(JsonFieldType.ARRAY);
}
@Test
public void nestedArray() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[]"),
createPayload("{\"a\": [{\"b\":\"bravo\"}]}")))
.isEqualTo(JsonFieldType.ARRAY);
}
@Test
public void arrayNestedBeneathAnArray() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].b[]"),
createPayload("{\"a\": [{\"b\": [ 1, 2 ]}]}")))
.isEqualTo(JsonFieldType.ARRAY);
}
@Test
public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException {
assertThat(
this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].b[].c"),
createPayload("{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void booleanField() throws IOException {
assertFieldType(JsonFieldType.BOOLEAN, "true");
}
@Test
public void objectField() throws IOException {
assertFieldType(JsonFieldType.OBJECT, "{}");
}
@Test
public void nullField() throws IOException {
assertFieldType(JsonFieldType.NULL, "null");
}
@Test
public void numberField() throws IOException {
assertFieldType(JsonFieldType.NUMBER, "1.2345");
}
@Test
public void stringField() throws IOException {
assertFieldType(JsonFieldType.STRING, "\"Foo\"");
}
@Test
public void nestedField() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.b.c"),
createPayload("{\"a\":{\"b\":{\"c\":{}}}}")))
.isEqualTo(JsonFieldType.OBJECT);
}
@Test
public void multipleFieldsWithSameType() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{\"id\":2}]}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWithDifferentTypes() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{\"id\":true}]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{\"id\":true}, { }]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesAbsentWhenOptionalResolvesToVaries()
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(
new FieldDescriptor("a[].id").optional(),
createPayload("{\"a\":[{\"id\":1},{\"id\":true}, { }]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWhenSometimesAbsent() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{ }]}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWhenNotNullThenNullWhenRequiredHasVariesType()
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":1},{\"id\":null}]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWhenNotNullThenNullWhenOptionalHasSpecificType()
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(
new FieldDescriptor("a[].id").optional(),
createPayload("{\"a\":[{\"id\":1},{\"id\":null}]}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWhenNullThenNotNullWhenRequiredHasVariesType()
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":null},{\"id\":1}]}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void multipleFieldsWhenNullThenNotNullWhenOptionalHasSpecificType()
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(
new FieldDescriptor("a[].id").optional(),
createPayload("{\"a\":[{\"id\":null},{\"id\":1}]}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWhenEitherNullOrAbsent() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{},{\"id\":null}]}")))
.isEqualTo(JsonFieldType.NULL);
}
@Test
public void multipleFieldsThatAreAllNull() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a[].id"),
createPayload("{\"a\":[{\"id\":null},{\"id\":null}]}")))
.isEqualTo(JsonFieldType.NULL);
}
@Test
public void nonExistentSingleFieldProducesFieldDoesNotExistException()
throws IOException {
this.thrownException.expect(FieldDoesNotExistException.class);
this.thrownException.expectMessage(
"The payload does not contain a field with the path 'a.b'");
this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.b"),
createPayload("{\"a\":{}}"));
}
@Test
public void nonExistentMultipleFieldsProducesFieldDoesNotExistException()
throws IOException {
this.thrownException.expect(FieldDoesNotExistException.class);
this.thrownException.expectMessage(
"The payload does not contain a field with the path '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}}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void leafWildcardWithVaryingType() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*"),
createPayload("{\"a\": {\"b\": 5, \"c\": \"six\"}}")))
.isEqualTo(JsonFieldType.VARIES);
}
@Test
public void intermediateWildcardWithCommonType() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*.d"),
createPayload("{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}")))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void intermediateWildcardWithVaryingType() throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("a.*.d"),
createPayload("{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}")))
.isEqualTo(JsonFieldType.VARIES);
}
private void assertFieldType(JsonFieldType expectedType, String jsonValue)
throws IOException {
assertThat(this.fieldTypeResolver.resolveFieldType(new FieldDescriptor("field"),
createSimplePayload(jsonValue))).isEqualTo(expectedType);
}
private Map<String, Object> createSimplePayload(String value) throws IOException {
return createPayload("{\"field\":" + value + "}");
}
@SuppressWarnings("unchecked")
private Map<String, Object> createPayload(String json) throws IOException {
return new ObjectMapper().readValue(json, Map.class);
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2014-2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonFieldTypesDiscoverer}.
*
* @author Andy Wilkinson
*/
public class JsonFieldTypesDiscovererTests {
private final JsonFieldTypesDiscoverer fieldTypeDiscoverer = new JsonFieldTypesDiscoverer();
@Rule
public ExpectedException thrownException = ExpectedException.none();
@Test
public void arrayField() throws IOException {
assertThat(discoverFieldTypes("[]")).containsExactly(JsonFieldType.ARRAY);
}
@Test
public void topLevelArray() throws IOException {
assertThat(discoverFieldTypes("[]", "[{\"a\":\"alpha\"}]"))
.containsExactly(JsonFieldType.ARRAY);
}
@Test
public void nestedArray() throws IOException {
assertThat(discoverFieldTypes("a[]", "{\"a\": [{\"b\":\"bravo\"}]}"))
.containsExactly(JsonFieldType.ARRAY);
}
@Test
public void arrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[]", "{\"a\": [{\"b\": [ 1, 2 ]}]}"))
.containsExactly(JsonFieldType.ARRAY);
}
@Test
public void specificFieldOfObjectInArrayNestedBeneathAnArray() throws IOException {
assertThat(discoverFieldTypes("a[].b[].c",
"{\"a\": [{\"b\": [ {\"c\": 5}, {\"c\": 5}]}]}"))
.containsExactly(JsonFieldType.NUMBER);
}
@Test
public void booleanField() throws IOException {
assertThat(discoverFieldTypes("true")).containsExactly(JsonFieldType.BOOLEAN);
}
@Test
public void objectField() throws IOException {
assertThat(discoverFieldTypes("{}")).containsExactly(JsonFieldType.OBJECT);
}
@Test
public void nullField() throws IOException {
assertThat(discoverFieldTypes("null")).containsExactly(JsonFieldType.NULL);
}
@Test
public void numberField() throws IOException {
assertThat(discoverFieldTypes("1.2345")).containsExactly(JsonFieldType.NUMBER);
}
@Test
public void stringField() throws IOException {
assertThat(discoverFieldTypes("\"Foo\"")).containsExactly(JsonFieldType.STRING);
}
@Test
public void nestedField() throws IOException {
assertThat(discoverFieldTypes("a.b.c", "{\"a\":{\"b\":{\"c\":{}}}}"))
.containsExactly(JsonFieldType.OBJECT);
}
@Test
public void multipleFieldsWithSameType() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}]}"))
.containsExactly(JsonFieldType.NUMBER);
}
@Test
public void multipleFieldsWithDifferentTypes() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":true}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.BOOLEAN,
JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenSometimesAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":1},{\"id\":2}, {}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id",
"{\"a\":[{\"id\":1},{\"id\":2}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER,
JsonFieldType.NULL);
}
@Test
public void multipleFieldsWithDifferentTypesAndSometimesNull() throws IOException {
assertThat(discoverFieldTypes("a[].id",
"{\"a\":[{\"id\":1},{\"id\":true}, {\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER,
JsonFieldType.BOOLEAN, JsonFieldType.NULL);
}
@Test
public void multipleFieldsWhenEitherNullOrAbsent() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL);
}
@Test
public void multipleFieldsThatAreAllNull() throws IOException {
assertThat(discoverFieldTypes("a[].id", "{\"a\":[{\"id\":null},{\"id\":null}]}"))
.containsExactlyInAnyOrder(JsonFieldType.NULL);
}
@Test
public void nonExistentSingleFieldProducesFieldDoesNotExistException()
throws IOException {
this.thrownException.expect(FieldDoesNotExistException.class);
this.thrownException.expectMessage(
"The payload does not contain a field with the path 'a.b'");
discoverFieldTypes("a.b", "{\"a\":{}}");
}
@Test
public void nonExistentMultipleFieldsProducesFieldDoesNotExistException()
throws IOException {
this.thrownException.expect(FieldDoesNotExistException.class);
this.thrownException.expectMessage(
"The payload does not contain a field with the path 'a[].b'");
discoverFieldTypes("a[].b", "{\"a\":[{\"c\":1},{\"c\":2}]}");
}
@Test
public void leafWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": 6}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER);
}
@Test
public void leafWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*", "{\"a\": {\"b\": 5, \"c\": \"six\"}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER, JsonFieldType.STRING);
}
@Test
public void intermediateWildcardWithCommonType() throws IOException {
assertThat(discoverFieldTypes("a.*.d",
"{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": 5}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER);
}
@Test
public void intermediateWildcardWithVaryingType() throws IOException {
assertThat(discoverFieldTypes("a.*.d",
"{\"a\": {\"b\": {\"d\": 4}, \"c\": {\"d\": \"four\"}}}}"))
.containsExactlyInAnyOrder(JsonFieldType.NUMBER,
JsonFieldType.STRING);
}
private JsonFieldTypes discoverFieldTypes(String value) throws IOException {
return discoverFieldTypes("field", "{\"field\":" + value + "}");
}
private JsonFieldTypes discoverFieldTypes(String path, String json)
throws IOException {
return this.fieldTypeDiscoverer.discoverFieldTypes(path,
new ObjectMapper().readValue(json, Object.class));
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2014-2018 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
import java.util.EnumSet;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link JsonFieldTypes}.
*
* @author Andy Wilkinson
*/
public class JsonFieldTypesTests {
@Test
public void singleTypeCoalescesToThatType() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(false))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void singleTypeCoalescesToThatTypeWhenOptional() {
assertThat(new JsonFieldTypes(JsonFieldType.NUMBER).coalesce(true))
.isEqualTo(JsonFieldType.NUMBER);
}
@Test
public void multipleTypesCoalescesToVaries() {
assertThat(
new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NUMBER))
.coalesce(false)).isEqualTo(JsonFieldType.VARIES);
}
@Test
public void nullAndNonNullTypesCoalescesToVaries() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL))
.coalesce(false)).isEqualTo(JsonFieldType.VARIES);
}
@Test
public void nullAndNonNullTypesCoalescesToNonNullTypeWhenOptional() {
assertThat(new JsonFieldTypes(EnumSet.of(JsonFieldType.ARRAY, JsonFieldType.NULL))
.coalesce(true)).isEqualTo(JsonFieldType.ARRAY);
}
}