Cause a failure when field's documented and actual types do not match
Closes gh-276
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user