diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java index 393dd8fd..a21bf524 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java @@ -100,8 +100,15 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet { validateFieldDocumentation(contentHandler); for (FieldDescriptor descriptor : this.fieldDescriptors) { - if (descriptor.getType() == null) { - descriptor.type(contentHandler.determineFieldType(descriptor.getPath())); + try { + descriptor.type(contentHandler.determineFieldType(descriptor)); + } + catch (FieldDoesNotExistException ex) { + String message = "Cannot determine the type of the field '" + + descriptor.getPath() + "' as it is not present in the " + + "payload. Please provide a type using " + + "FieldDescriptor.type(Object type)."; + throw new FieldTypeRequiredException(message); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java index ce99ff00..45865f4b 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java @@ -50,12 +50,12 @@ interface ContentHandler { String getUndocumentedContent(List fieldDescriptors); /** - * Returns the type of the field with the given {@code path} based on the content of - * the payload. + * Returns the type of the field that is described by the given + * {@code fieldDescriptor} based on the content of the payload. * - * @param path the field path + * @param fieldDescriptor the field descriptor * @return the type of the field */ - Object determineFieldType(String path); + Object determineFieldType(FieldDescriptor fieldDescriptor); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldTypesDoNotMatchException.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldTypesDoNotMatchException.java new file mode 100644 index 00000000..cfb8667a --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/FieldTypesDoNotMatchException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2016 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; + +/** + * A {@code FieldTypesDoNotMatchException} is thrown when the documented and actual types + * of a field do not match. + * + * @author Andy Wilkinson + */ +class FieldTypesDoNotMatchException extends RuntimeException { + + /** + * Creates a new {@code FieldTypesDoNotMatchException} for the field described by the + * given {@code fieldDescriptor} that has the given {@code actualType}. + * + * @param fieldDescriptor the field + * @param actualType the actual type of the field + */ + FieldTypesDoNotMatchException(FieldDescriptor fieldDescriptor, Object actualType) { + super("The documented type of the field '" + fieldDescriptor.getPath() + "' is " + + fieldDescriptor.getType() + " but the actual type is " + actualType); + } + +} 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 779b75fb..f1fff9ce 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 @@ -34,6 +34,8 @@ class JsonContentHandler implements ContentHandler { private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor(); + private final JsonFieldTypeResolver fieldTypeResolver = new JsonFieldTypeResolver(); + private final ObjectMapper objectMapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT); @@ -93,15 +95,26 @@ class JsonContentHandler implements ContentHandler { } @Override - public Object determineFieldType(String path) { + public Object determineFieldType(FieldDescriptor fieldDescriptor) { + if (fieldDescriptor.getType() == null) { + return this.fieldTypeResolver.resolveFieldType(fieldDescriptor.getPath(), + readContent()); + } + if (!(fieldDescriptor.getType() instanceof JsonFieldType)) { + return fieldDescriptor.getType(); + } + JsonFieldType descriptorFieldType = (JsonFieldType) fieldDescriptor.getType(); try { - return new JsonFieldTypeResolver().resolveFieldType(path, readContent()); + JsonFieldType actualFieldType = this.fieldTypeResolver + .resolveFieldType(fieldDescriptor.getPath(), readContent()); + if (descriptorFieldType == JsonFieldType.VARIES + || descriptorFieldType == actualFieldType) { + return descriptorFieldType; + } + throw new FieldTypesDoNotMatchException(fieldDescriptor, actualFieldType); } catch (FieldDoesNotExistException ex) { - String message = "Cannot determine the type of the field '" + path + "' as" - + " it is not present in the payload. Please provide a type using" - + " FieldDescriptor.type(Object type)."; - throw new FieldTypeRequiredException(message); + return fieldDescriptor.getType(); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java index 67e2763d..2e8f99e7 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java @@ -153,15 +153,14 @@ class XmlContentHandler implements ContentHandler { } @Override - public Object determineFieldType(String path) { - try { - return new JsonFieldTypeResolver().resolveFieldType(path, readPayload()); + public Object determineFieldType(FieldDescriptor fieldDescriptor) { + if (fieldDescriptor.getType() != null) { + return fieldDescriptor.getType(); } - catch (FieldDoesNotExistException ex) { - String message = "Cannot determine the type of the field '" + path + "' as" - + " it is not present in the payload. Please provide a type using" - + " FieldDescriptor.type(Object type)."; - throw new FieldTypeRequiredException(message); + else { + throw new FieldTypeRequiredException("The type of a field in an XML payload " + + "cannot be determined automatically. Please provide a type using " + + "FieldDescriptor.type(Object type)"); } } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java index a6dceaf0..f27633c2 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java @@ -110,6 +110,33 @@ public class RequestFieldsSnippetFailureTests { .build()); } + @Test + public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + this.thrown.expectMessage(equalTo("The documented type of the field 'a' is" + + " Object but the actual type is Number")); + new RequestFieldsSnippet(Arrays + .asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) + .document(new OperationBuilder("mismatched-field-types", + this.snippet.getOutputDirectory()) + .request("http://localhost") + .content("{ \"a\": 5 }").build()); + } + + @Test + public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + this.thrown.expectMessage(equalTo("The documented type of the field '[].a' is" + + " Object but the actual type is Varies")); + new RequestFieldsSnippet(Arrays.asList( + fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) + .document(new OperationBuilder("mismatched-field-types", + this.snippet.getOutputDirectory()) + .request("http://localhost") + .content("[{ \"a\": 5 },{ \"a\": \"b\" }]") + .build()); + } + @Test public void undocumentedXmlRequestField() throws IOException { this.thrown.expect(SnippetException.class); diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java index fa23f346..32e34be9 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java @@ -177,6 +177,34 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests { .build()); } + @Test + public void fieldWithExplictExactlyMatchingType() throws IOException { + this.snippet + .expectRequestFields("request-field-with-explicit-exactly-matching-type") + .withContents(tableWithHeader("Path", "Type", "Description").row("`a`", + "`Number`", "one")); + + new RequestFieldsSnippet(Arrays + .asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) + .document(operationBuilder( + "request-field-with-explicit-exactly-matching-type") + .request("http://localhost") + .content("{\"a\": 5 }").build()); + } + + @Test + public void fieldWithExplictVariesType() throws IOException { + this.snippet.expectRequestFields("request-field-with-explicit-varies-type") + .withContents(tableWithHeader("Path", "Type", "Description").row("`a`", + "`Varies`", "one")); + + new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type(JsonFieldType.VARIES))).document( + operationBuilder("request-field-with-explicit-varies-type") + .request("http://localhost").content("{\"a\": 5 }") + .build()); + } + @Test public void xmlRequestFields() throws IOException { this.snippet.expectRequestFields("xml-request") diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java index e3385a03..bfe50ad0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java @@ -57,8 +57,32 @@ public class ResponseFieldsSnippetFailureTests { equalTo("Cannot document response fields as the response body is empty")); new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one"))) .document(new OperationBuilder("no-response-body", - this.snippet.getOutputDirectory()).request("http://localhost") - .build()); + this.snippet.getOutputDirectory()).build()); + } + + @Test + public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + this.thrown.expectMessage(equalTo("The documented type of the field 'a' is" + + " Object but the actual type is Number")); + new ResponseFieldsSnippet(Arrays + .asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT))) + .document(new OperationBuilder("mismatched-field-types", + this.snippet.getOutputDirectory()).response() + .content("{ \"a\": 5 }}").build()); + } + + @Test + public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException { + this.thrown.expect(FieldTypesDoNotMatchException.class); + this.thrown.expectMessage(equalTo("The documented type of the field '[].a' is" + + " Object but the actual type is Varies")); + new ResponseFieldsSnippet(Arrays.asList( + fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT))) + .document(new OperationBuilder("mismatched-field-types", + this.snippet.getOutputDirectory()).response() + .content("[{ \"a\": 5 },{ \"a\": \"b\" }]") + .build()); } @Test diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java index db13d942..e0525888 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java @@ -185,6 +185,33 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests { .build()); } + @Test + public void fieldWithExplictExactlyMatchingType() throws IOException { + this.snippet + .expectResponseFields( + "response-field-with-explicit-exactly-matching-type") + .withContents(tableWithHeader("Path", "Type", "Description").row("`a`", + "`Number`", "one")); + + new ResponseFieldsSnippet(Arrays + .asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER))) + .document(operationBuilder( + "response-field-with-explicit-exactly-matching-type") + .response().content("{\"a\": 5 }").build()); + } + + @Test + public void fieldWithExplictVariesType() throws IOException { + this.snippet.expectResponseFields("response-field-with-explicit-varies-type") + .withContents(tableWithHeader("Path", "Type", "Description").row("`a`", + "`Varies`", "one")); + + new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one") + .type(JsonFieldType.VARIES))).document( + operationBuilder("response-field-with-explicit-varies-type") + .response().content("{\"a\": 5 }").build()); + } + @Test public void xmlResponseFields() throws IOException { this.snippet.expectResponseFields("xml-response")