Cause a failure when field's documented and actual types do not match

Closes gh-276
This commit is contained in:
Andy Wilkinson
2016-07-22 13:20:44 +01:00
parent 86c2310b90
commit 78318775e0
9 changed files with 186 additions and 22 deletions

View File

@@ -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);
}
}

View File

@@ -50,12 +50,12 @@ interface ContentHandler {
String getUndocumentedContent(List<FieldDescriptor> 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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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)");
}
}

View File

@@ -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);

View File

@@ -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")

View File

@@ -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

View File

@@ -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")