Skip to content

Commit 7831877

Browse files
committed
Cause a failure when field's documented and actual types do not match
Closes gh-276
1 parent 86c2310 commit 7831877

File tree

9 files changed

+186
-22
lines changed

9 files changed

+186
-22
lines changed

spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/AbstractFieldsSnippet.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,15 @@ protected Map<String, Object> createModel(Operation operation) {
100100
validateFieldDocumentation(contentHandler);
101101

102102
for (FieldDescriptor descriptor : this.fieldDescriptors) {
103-
if (descriptor.getType() == null) {
104-
descriptor.type(contentHandler.determineFieldType(descriptor.getPath()));
103+
try {
104+
descriptor.type(contentHandler.determineFieldType(descriptor));
105+
}
106+
catch (FieldDoesNotExistException ex) {
107+
String message = "Cannot determine the type of the field '"
108+
+ descriptor.getPath() + "' as it is not present in the "
109+
+ "payload. Please provide a type using "
110+
+ "FieldDescriptor.type(Object type).";
111+
throw new FieldTypeRequiredException(message);
105112
}
106113
}
107114

spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/ContentHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ interface ContentHandler {
5050
String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors);
5151

5252
/**
53-
* Returns the type of the field with the given {@code path} based on the content of
54-
* the payload.
53+
* Returns the type of the field that is described by the given
54+
* {@code fieldDescriptor} based on the content of the payload.
5555
*
56-
* @param path the field path
56+
* @param fieldDescriptor the field descriptor
5757
* @return the type of the field
5858
*/
59-
Object determineFieldType(String path);
59+
Object determineFieldType(FieldDescriptor fieldDescriptor);
6060

6161
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2014-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.restdocs.payload;
18+
19+
/**
20+
* A {@code FieldTypesDoNotMatchException} is thrown when the documented and actual types
21+
* of a field do not match.
22+
*
23+
* @author Andy Wilkinson
24+
*/
25+
class FieldTypesDoNotMatchException extends RuntimeException {
26+
27+
/**
28+
* Creates a new {@code FieldTypesDoNotMatchException} for the field described by the
29+
* given {@code fieldDescriptor} that has the given {@code actualType}.
30+
*
31+
* @param fieldDescriptor the field
32+
* @param actualType the actual type of the field
33+
*/
34+
FieldTypesDoNotMatchException(FieldDescriptor fieldDescriptor, Object actualType) {
35+
super("The documented type of the field '" + fieldDescriptor.getPath() + "' is "
36+
+ fieldDescriptor.getType() + " but the actual type is " + actualType);
37+
}
38+
39+
}

spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/JsonContentHandler.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class JsonContentHandler implements ContentHandler {
3434

3535
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
3636

37+
private final JsonFieldTypeResolver fieldTypeResolver = new JsonFieldTypeResolver();
38+
3739
private final ObjectMapper objectMapper = new ObjectMapper()
3840
.enable(SerializationFeature.INDENT_OUTPUT);
3941

@@ -93,15 +95,26 @@ private boolean isEmpty(Object object) {
9395
}
9496

9597
@Override
96-
public Object determineFieldType(String path) {
98+
public Object determineFieldType(FieldDescriptor fieldDescriptor) {
99+
if (fieldDescriptor.getType() == null) {
100+
return this.fieldTypeResolver.resolveFieldType(fieldDescriptor.getPath(),
101+
readContent());
102+
}
103+
if (!(fieldDescriptor.getType() instanceof JsonFieldType)) {
104+
return fieldDescriptor.getType();
105+
}
106+
JsonFieldType descriptorFieldType = (JsonFieldType) fieldDescriptor.getType();
97107
try {
98-
return new JsonFieldTypeResolver().resolveFieldType(path, readContent());
108+
JsonFieldType actualFieldType = this.fieldTypeResolver
109+
.resolveFieldType(fieldDescriptor.getPath(), readContent());
110+
if (descriptorFieldType == JsonFieldType.VARIES
111+
|| descriptorFieldType == actualFieldType) {
112+
return descriptorFieldType;
113+
}
114+
throw new FieldTypesDoNotMatchException(fieldDescriptor, actualFieldType);
99115
}
100116
catch (FieldDoesNotExistException ex) {
101-
String message = "Cannot determine the type of the field '" + path + "' as"
102-
+ " it is not present in the payload. Please provide a type using"
103-
+ " FieldDescriptor.type(Object type).";
104-
throw new FieldTypeRequiredException(message);
117+
return fieldDescriptor.getType();
105118
}
106119
}
107120

spring-restdocs-core/src/main/java/org/springframework/restdocs/payload/XmlContentHandler.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,14 @@ private String prettyPrint(Document document) {
153153
}
154154

155155
@Override
156-
public Object determineFieldType(String path) {
157-
try {
158-
return new JsonFieldTypeResolver().resolveFieldType(path, readPayload());
156+
public Object determineFieldType(FieldDescriptor fieldDescriptor) {
157+
if (fieldDescriptor.getType() != null) {
158+
return fieldDescriptor.getType();
159159
}
160-
catch (FieldDoesNotExistException ex) {
161-
String message = "Cannot determine the type of the field '" + path + "' as"
162-
+ " it is not present in the payload. Please provide a type using"
163-
+ " FieldDescriptor.type(Object type).";
164-
throw new FieldTypeRequiredException(message);
160+
else {
161+
throw new FieldTypeRequiredException("The type of a field in an XML payload "
162+
+ "cannot be determined automatically. Please provide a type using "
163+
+ "FieldDescriptor.type(Object type)");
165164
}
166165
}
167166

spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetFailureTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,33 @@ public void attemptToDocumentFieldsWithNoRequestBody() throws IOException {
110110
.build());
111111
}
112112

113+
@Test
114+
public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException {
115+
this.thrown.expect(FieldTypesDoNotMatchException.class);
116+
this.thrown.expectMessage(equalTo("The documented type of the field 'a' is"
117+
+ " Object but the actual type is Number"));
118+
new RequestFieldsSnippet(Arrays
119+
.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
120+
.document(new OperationBuilder("mismatched-field-types",
121+
this.snippet.getOutputDirectory())
122+
.request("http://localhost")
123+
.content("{ \"a\": 5 }").build());
124+
}
125+
126+
@Test
127+
public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException {
128+
this.thrown.expect(FieldTypesDoNotMatchException.class);
129+
this.thrown.expectMessage(equalTo("The documented type of the field '[].a' is"
130+
+ " Object but the actual type is Varies"));
131+
new RequestFieldsSnippet(Arrays.asList(
132+
fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
133+
.document(new OperationBuilder("mismatched-field-types",
134+
this.snippet.getOutputDirectory())
135+
.request("http://localhost")
136+
.content("[{ \"a\": 5 },{ \"a\": \"b\" }]")
137+
.build());
138+
}
139+
113140
@Test
114141
public void undocumentedXmlRequestField() throws IOException {
115142
this.thrown.expect(SnippetException.class);

spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/RequestFieldsSnippetTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,34 @@ public void requestFieldsWithCustomDescriptorAttributes() throws IOException {
177177
.build());
178178
}
179179

180+
@Test
181+
public void fieldWithExplictExactlyMatchingType() throws IOException {
182+
this.snippet
183+
.expectRequestFields("request-field-with-explicit-exactly-matching-type")
184+
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
185+
"`Number`", "one"));
186+
187+
new RequestFieldsSnippet(Arrays
188+
.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
189+
.document(operationBuilder(
190+
"request-field-with-explicit-exactly-matching-type")
191+
.request("http://localhost")
192+
.content("{\"a\": 5 }").build());
193+
}
194+
195+
@Test
196+
public void fieldWithExplictVariesType() throws IOException {
197+
this.snippet.expectRequestFields("request-field-with-explicit-varies-type")
198+
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
199+
"`Varies`", "one"));
200+
201+
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
202+
.type(JsonFieldType.VARIES))).document(
203+
operationBuilder("request-field-with-explicit-varies-type")
204+
.request("http://localhost").content("{\"a\": 5 }")
205+
.build());
206+
}
207+
180208
@Test
181209
public void xmlRequestFields() throws IOException {
182210
this.snippet.expectRequestFields("xml-request")

spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetFailureTests.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,32 @@ public void attemptToDocumentFieldsWithNoResponseBody() throws IOException {
5757
equalTo("Cannot document response fields as the response body is empty"));
5858
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
5959
.document(new OperationBuilder("no-response-body",
60-
this.snippet.getOutputDirectory()).request("http://localhost")
61-
.build());
60+
this.snippet.getOutputDirectory()).build());
61+
}
62+
63+
@Test
64+
public void fieldWithExplicitTypeThatDoesNotMatchThePayload() throws IOException {
65+
this.thrown.expect(FieldTypesDoNotMatchException.class);
66+
this.thrown.expectMessage(equalTo("The documented type of the field 'a' is"
67+
+ " Object but the actual type is Number"));
68+
new ResponseFieldsSnippet(Arrays
69+
.asList(fieldWithPath("a").description("one").type(JsonFieldType.OBJECT)))
70+
.document(new OperationBuilder("mismatched-field-types",
71+
this.snippet.getOutputDirectory()).response()
72+
.content("{ \"a\": 5 }}").build());
73+
}
74+
75+
@Test
76+
public void fieldWithExplicitSpecificTypeThatActuallyVaries() throws IOException {
77+
this.thrown.expect(FieldTypesDoNotMatchException.class);
78+
this.thrown.expectMessage(equalTo("The documented type of the field '[].a' is"
79+
+ " Object but the actual type is Varies"));
80+
new ResponseFieldsSnippet(Arrays.asList(
81+
fieldWithPath("[].a").description("one").type(JsonFieldType.OBJECT)))
82+
.document(new OperationBuilder("mismatched-field-types",
83+
this.snippet.getOutputDirectory()).response()
84+
.content("[{ \"a\": 5 },{ \"a\": \"b\" }]")
85+
.build());
6286
}
6387

6488
@Test

spring-restdocs-core/src/test/java/org/springframework/restdocs/payload/ResponseFieldsSnippetTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,33 @@ public void responseFieldsWithCustomDescriptorAttributes() throws IOException {
185185
.build());
186186
}
187187

188+
@Test
189+
public void fieldWithExplictExactlyMatchingType() throws IOException {
190+
this.snippet
191+
.expectResponseFields(
192+
"response-field-with-explicit-exactly-matching-type")
193+
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
194+
"`Number`", "one"));
195+
196+
new ResponseFieldsSnippet(Arrays
197+
.asList(fieldWithPath("a").description("one").type(JsonFieldType.NUMBER)))
198+
.document(operationBuilder(
199+
"response-field-with-explicit-exactly-matching-type")
200+
.response().content("{\"a\": 5 }").build());
201+
}
202+
203+
@Test
204+
public void fieldWithExplictVariesType() throws IOException {
205+
this.snippet.expectResponseFields("response-field-with-explicit-varies-type")
206+
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
207+
"`Varies`", "one"));
208+
209+
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")
210+
.type(JsonFieldType.VARIES))).document(
211+
operationBuilder("response-field-with-explicit-varies-type")
212+
.response().content("{\"a\": 5 }").build());
213+
}
214+
188215
@Test
189216
public void xmlResponseFields() throws IOException {
190217
this.snippet.expectResponseFields("xml-response")

0 commit comments

Comments
 (0)