Add support for documenting fields in XML payloads
Closes gh-46
This commit is contained in:
@@ -81,12 +81,18 @@ payload and the field has not been marked as optional. For payloads with a hiera
|
||||
structure, documenting a field is sufficient for all of its descendants to also be
|
||||
treated as having been documented.
|
||||
|
||||
[[documenting-your-api-request-response-payloads-field-paths]]
|
||||
==== Field paths
|
||||
TIP: By default, Spring REST Docs will assume that the payload you are documenting is
|
||||
JSON. If you want to document an XML payload the content type of the request or response
|
||||
must be compatible with `application/xml`.
|
||||
|
||||
When documenting request and response payloads, fields are identified using a path. Paths
|
||||
use `.` to descend into a child object and `[]` to identify an array. For example, with
|
||||
this JSON payload:
|
||||
[[documenting-your-api-request-response-payloads-json]]
|
||||
==== JSON payloads
|
||||
|
||||
[[documenting-your-api-request-response-payloads-json-field-paths]]
|
||||
===== JSON field paths
|
||||
|
||||
JSON field paths use `.` to descend into a child object and `[]` to identify an array. For
|
||||
example, with this JSON payload:
|
||||
|
||||
[source,json,indent=0]
|
||||
----
|
||||
@@ -131,8 +137,8 @@ The following paths are all present:
|
||||
|
||||
|
||||
|
||||
[[documenting-your-api-request-response-payloads-field-types]]
|
||||
==== Field types
|
||||
[[documenting-your-api-request-response-payloads-json-field-types]]
|
||||
===== JSON field types
|
||||
|
||||
When a field is documented, Spring REST Docs will attempt to determine its type by
|
||||
examining the payload. Seven different types are supported:
|
||||
@@ -163,8 +169,9 @@ examining the payload. Seven different types are supported:
|
||||
| The field occurs multiple times in the payload with a variety of different types
|
||||
|===
|
||||
|
||||
The type can also be set explicitly using the `type(FieldType)` method on
|
||||
`FieldDescriptor`:
|
||||
The type can also be set explicitly using the `type(Object)` method on
|
||||
`FieldDescriptor`. Typically, one of the values enumerated by `JsonFieldType` will be
|
||||
used:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@@ -173,6 +180,24 @@ include::{examples-dir}/com/example/Payload.java[tags=explicit-type]
|
||||
<1> Set the field's type to `string`.
|
||||
|
||||
|
||||
[[documenting-your-api-request-response-payloads-xml]]
|
||||
==== XML payloads
|
||||
|
||||
[[documenting-your-api-request-response-payloads-xml-field-paths]]
|
||||
===== XML field paths
|
||||
|
||||
XML field paths are described using XPath. `/` is used to descend into a child node.
|
||||
|
||||
|
||||
|
||||
[[documenting-your-api-request-response-payloads-xml-field-types]]
|
||||
===== XML field types
|
||||
|
||||
When documenting an XML payload, you must provide a type for the field using the
|
||||
`type(Object)` method on `FieldDescriptor`. The result of the supplied type's `toString`
|
||||
method will be included in the documentation.
|
||||
|
||||
|
||||
|
||||
[[documenting-your-api-request-parameters]]
|
||||
=== Request parameters
|
||||
|
||||
@@ -27,7 +27,7 @@ import static org.springframework.restdocs.RestDocumentationRequestBuilders.post
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.payload.FieldType;
|
||||
import org.springframework.restdocs.payload.JsonFieldType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
public class Payload {
|
||||
@@ -50,7 +50,7 @@ private MockMvc mockMvc;
|
||||
// tag::explicit-type[]
|
||||
.andDo(document("index", responseFields(
|
||||
fieldWithPath("contact.email")
|
||||
.type(FieldType.STRING) // <1>
|
||||
.type(JsonFieldType.STRING) // <1>
|
||||
.optional()
|
||||
.description("The user's email address"))));
|
||||
// end::explicit-type[]
|
||||
|
||||
@@ -18,36 +18,28 @@ package org.springframework.restdocs.payload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link TemplatedSnippet} that produces a snippet documenting a
|
||||
* RESTful resource's request or response fields.
|
||||
* A {@link TemplatedSnippet} that produces a snippet documenting a RESTful resource's
|
||||
* request or response fields.
|
||||
*
|
||||
* @author Andreas Evers
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class AbstractFieldsSnippet extends
|
||||
TemplatedSnippet {
|
||||
|
||||
private final Map<String, FieldDescriptor> descriptorsByPath = new LinkedHashMap<String, FieldDescriptor>();
|
||||
|
||||
private final FieldTypeResolver fieldTypeResolver = new FieldTypeResolver();
|
||||
|
||||
private final FieldValidator fieldValidator = new FieldValidator();
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
|
||||
private List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
@@ -57,45 +49,73 @@ public abstract class AbstractFieldsSnippet extends
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getPath());
|
||||
Assert.hasText(descriptor.getDescription());
|
||||
this.descriptorsByPath.put(descriptor.getPath(), descriptor);
|
||||
}
|
||||
this.fieldDescriptors = descriptors;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> document(MvcResult result) throws IOException {
|
||||
this.fieldValidator.validate(getPayloadReader(result), this.fieldDescriptors);
|
||||
Object payload = extractPayload(result);
|
||||
MediaType contentType = getContentType(result);
|
||||
PayloadHandler payloadHandler;
|
||||
if (contentType != null
|
||||
&& MediaType.APPLICATION_XML.isCompatibleWith(contentType)) {
|
||||
payloadHandler = new XmlPayloadHandler(readPayload(result));
|
||||
}
|
||||
else {
|
||||
payloadHandler = new JsonPayloadHandler(readPayload(result));
|
||||
}
|
||||
|
||||
validateFieldDocumentation(payloadHandler);
|
||||
|
||||
for (FieldDescriptor descriptor : this.fieldDescriptors) {
|
||||
if (descriptor.getType() == null) {
|
||||
descriptor.type(payloadHandler.determineFieldType(descriptor.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
List<Map<String, Object>> fields = new ArrayList<>();
|
||||
model.put("fields", fields);
|
||||
for (Entry<String, FieldDescriptor> entry : this.descriptorsByPath.entrySet()) {
|
||||
FieldDescriptor descriptor = entry.getValue();
|
||||
if (descriptor.getType() == null) {
|
||||
descriptor.type(getFieldType(descriptor, payload));
|
||||
}
|
||||
for (FieldDescriptor descriptor : this.fieldDescriptors) {
|
||||
fields.add(descriptor.toModel());
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private FieldType getFieldType(FieldDescriptor descriptor, Object payload) {
|
||||
try {
|
||||
return AbstractFieldsSnippet.this.fieldTypeResolver
|
||||
.resolveFieldType(descriptor.getPath(), payload);
|
||||
}
|
||||
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(FieldType).";
|
||||
throw new FieldTypeRequiredException(message);
|
||||
private String readPayload(MvcResult result) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
FileCopyUtils.copy(getPayloadReader(result), writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void validateFieldDocumentation(PayloadHandler payloadHandler) {
|
||||
List<FieldDescriptor> missingFields = payloadHandler
|
||||
.findMissingFields(this.fieldDescriptors);
|
||||
String undocumentedPayload = payloadHandler
|
||||
.getUndocumentedPayload(this.fieldDescriptors);
|
||||
|
||||
if (!missingFields.isEmpty() || StringUtils.hasText(undocumentedPayload)) {
|
||||
String message = "";
|
||||
if (StringUtils.hasText(undocumentedPayload)) {
|
||||
message += String.format("The following parts of the payload were"
|
||||
+ " not documented:%n%s", undocumentedPayload);
|
||||
}
|
||||
if (!missingFields.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message += String.format("%n");
|
||||
}
|
||||
List<String> paths = new ArrayList<String>();
|
||||
for (FieldDescriptor fieldDescriptor : missingFields) {
|
||||
paths.add(fieldDescriptor.getPath());
|
||||
}
|
||||
message += "Fields with the following paths were not found in the"
|
||||
+ " payload: " + paths;
|
||||
}
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private Object extractPayload(MvcResult result) throws IOException {
|
||||
return this.objectMapper.readValue(getPayloadReader(result), Object.class);
|
||||
}
|
||||
protected abstract MediaType getContentType(MvcResult result);
|
||||
|
||||
protected abstract Reader getPayloadReader(MvcResult result) throws IOException;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public class FieldDescriptor extends AbstractDescriptor<FieldDescriptor> {
|
||||
|
||||
private final String path;
|
||||
|
||||
private FieldType type;
|
||||
private Object type;
|
||||
|
||||
private boolean optional;
|
||||
|
||||
@@ -44,13 +44,14 @@ public class FieldDescriptor extends AbstractDescriptor<FieldDescriptor> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the type of the field
|
||||
* Specifies the type of the field. When documenting a JSON payload, the
|
||||
* {@link JsonFieldType} enumeration will typically be used.
|
||||
*
|
||||
* @param type The type of the field
|
||||
*
|
||||
* @return {@code this}
|
||||
* @see JsonFieldType
|
||||
*/
|
||||
public FieldDescriptor type(FieldType type) {
|
||||
public FieldDescriptor type(Object type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
@@ -80,7 +81,7 @@ public class FieldDescriptor extends AbstractDescriptor<FieldDescriptor> {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
FieldType getType() {
|
||||
Object getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class FieldDoesNotExistException extends RuntimeException {
|
||||
*
|
||||
* @param fieldPath the path of the field that does not exist
|
||||
*/
|
||||
public FieldDoesNotExistException(FieldPath fieldPath) {
|
||||
public FieldDoesNotExistException(JsonFieldPath fieldPath) {
|
||||
super("The payload does not contain a field with the path '" + fieldPath + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* {@code FieldValidator} is used to validate a payload's fields against the user-provided
|
||||
* {@link FieldDescriptor}s.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class FieldValidator {
|
||||
|
||||
private final FieldProcessor fieldProcessor = new FieldProcessor();
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
void validate(Reader payloadReader, List<FieldDescriptor> fieldDescriptors)
|
||||
throws IOException {
|
||||
Object payload = this.objectMapper.readValue(payloadReader, Object.class);
|
||||
List<String> missingFields = findMissingFields(payload, fieldDescriptors);
|
||||
Object undocumentedPayload = findUndocumentedFields(payload, fieldDescriptors);
|
||||
|
||||
if (!missingFields.isEmpty() || !isEmpty(undocumentedPayload)) {
|
||||
String message = "";
|
||||
if (!isEmpty(undocumentedPayload)) {
|
||||
message += String.format(
|
||||
"The following parts of the payload were not documented:%n%s",
|
||||
this.objectMapper.writeValueAsString(undocumentedPayload));
|
||||
}
|
||||
if (!missingFields.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message += String.format("%n");
|
||||
}
|
||||
message += "Fields with the following paths were not found in the payload: "
|
||||
+ missingFields;
|
||||
}
|
||||
throw new SnippetException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmpty(Object object) {
|
||||
if (object instanceof Map) {
|
||||
return ((Map<?, ?>) object).isEmpty();
|
||||
}
|
||||
return ((List<?>) object).isEmpty();
|
||||
}
|
||||
|
||||
private List<String> findMissingFields(Object payload,
|
||||
List<FieldDescriptor> fieldDescriptors) {
|
||||
List<String> missingFields = new ArrayList<String>();
|
||||
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
if (!fieldDescriptor.isOptional()
|
||||
&& !this.fieldProcessor.hasField(
|
||||
FieldPath.compile(fieldDescriptor.getPath()), payload)) {
|
||||
missingFields.add(fieldDescriptor.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
return missingFields;
|
||||
}
|
||||
|
||||
private Object findUndocumentedFields(Object payload,
|
||||
List<FieldDescriptor> fieldDescriptors) {
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
FieldPath path = FieldPath.compile(fieldDescriptor.getPath());
|
||||
this.fieldProcessor.remove(path, payload);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,12 +22,12 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A path that identifies a field in a payload
|
||||
* A path that identifies a field in a JSON payload
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
final class FieldPath {
|
||||
final class JsonFieldPath {
|
||||
|
||||
private static final Pattern ARRAY_INDEX_PATTERN = Pattern
|
||||
.compile("\\[([0-9]+|\\*){0,1}\\]");
|
||||
@@ -38,7 +38,7 @@ final class FieldPath {
|
||||
|
||||
private final boolean precise;
|
||||
|
||||
private FieldPath(String rawPath, List<String> segments, boolean precise) {
|
||||
private JsonFieldPath(String rawPath, List<String> segments, boolean precise) {
|
||||
this.rawPath = rawPath;
|
||||
this.segments = segments;
|
||||
this.precise = precise;
|
||||
@@ -57,9 +57,9 @@ final class FieldPath {
|
||||
return this.rawPath;
|
||||
}
|
||||
|
||||
static FieldPath compile(String path) {
|
||||
static JsonFieldPath compile(String path) {
|
||||
List<String> segments = extractSegments(path);
|
||||
return new FieldPath(path, segments, matchesSingleValue(segments));
|
||||
return new JsonFieldPath(path, segments, matchesSingleValue(segments));
|
||||
}
|
||||
|
||||
static boolean isArraySegment(String segment) {
|
||||
@@ -23,15 +23,15 @@ import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A {@code FieldProcessor} processes a payload's fields, allowing them to be extracted
|
||||
* and removed
|
||||
* A {@code JsonFieldProcessor} processes a payload's fields, allowing them to be
|
||||
* extracted and removed
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
final class FieldProcessor {
|
||||
final class JsonFieldProcessor {
|
||||
|
||||
boolean hasField(FieldPath fieldPath, Object payload) {
|
||||
boolean hasField(JsonFieldPath fieldPath, Object payload) {
|
||||
final AtomicReference<Boolean> hasField = new AtomicReference<Boolean>(false);
|
||||
traverse(new ProcessingContext(payload, fieldPath), new MatchCallback() {
|
||||
|
||||
@@ -44,7 +44,7 @@ final class FieldProcessor {
|
||||
return hasField.get();
|
||||
}
|
||||
|
||||
Object extract(FieldPath path, Object payload) {
|
||||
Object extract(JsonFieldPath path, Object payload) {
|
||||
final List<Object> matches = new ArrayList<Object>();
|
||||
traverse(new ProcessingContext(payload, path), new MatchCallback() {
|
||||
|
||||
@@ -65,7 +65,7 @@ final class FieldProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
void remove(final FieldPath path, Object payload) {
|
||||
void remove(final JsonFieldPath path, Object payload) {
|
||||
traverse(new ProcessingContext(payload, path), new MatchCallback() {
|
||||
|
||||
@Override
|
||||
@@ -78,7 +78,7 @@ final class FieldProcessor {
|
||||
|
||||
private void traverse(ProcessingContext context, MatchCallback matchCallback) {
|
||||
final String segment = context.getSegment();
|
||||
if (FieldPath.isArraySegment(segment)) {
|
||||
if (JsonFieldPath.isArraySegment(segment)) {
|
||||
if (context.getPayload() instanceof List) {
|
||||
handleListPayload(context, matchCallback);
|
||||
}
|
||||
@@ -206,13 +206,13 @@ final class FieldProcessor {
|
||||
|
||||
private final Match parent;
|
||||
|
||||
private final FieldPath path;
|
||||
private final JsonFieldPath path;
|
||||
|
||||
private ProcessingContext(Object payload, FieldPath path) {
|
||||
private ProcessingContext(Object payload, JsonFieldPath path) {
|
||||
this(payload, path, null, null);
|
||||
}
|
||||
|
||||
private ProcessingContext(Object payload, FieldPath path, List<String> segments,
|
||||
private ProcessingContext(Object payload, JsonFieldPath path, List<String> segments,
|
||||
Match parent) {
|
||||
this.payload = payload;
|
||||
this.path = path;
|
||||
@@ -25,7 +25,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public enum FieldType {
|
||||
public enum JsonFieldType {
|
||||
|
||||
ARRAY, BOOLEAN, OBJECT, NUMBER, NULL, STRING, VARIES;
|
||||
|
||||
@@ -20,26 +20,26 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resolves the type of a field in a request or response payload
|
||||
* Resolves the type of a field in a JSON request or response payload
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class FieldTypeResolver {
|
||||
class JsonFieldTypeResolver {
|
||||
|
||||
private final FieldProcessor fieldProcessor = new FieldProcessor();
|
||||
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
|
||||
|
||||
FieldType resolveFieldType(String path, Object payload) {
|
||||
FieldPath fieldPath = FieldPath.compile(path);
|
||||
JsonFieldType resolveFieldType(String path, Object payload) {
|
||||
JsonFieldPath fieldPath = JsonFieldPath.compile(path);
|
||||
Object field = this.fieldProcessor.extract(fieldPath, payload);
|
||||
if (field instanceof Collection && !fieldPath.isPrecise()) {
|
||||
FieldType commonType = null;
|
||||
JsonFieldType commonType = null;
|
||||
for (Object item : (Collection<?>) field) {
|
||||
FieldType fieldType = determineFieldType(item);
|
||||
JsonFieldType fieldType = determineFieldType(item);
|
||||
if (commonType == null) {
|
||||
commonType = fieldType;
|
||||
}
|
||||
else if (fieldType != commonType) {
|
||||
return FieldType.VARIES;
|
||||
return JsonFieldType.VARIES;
|
||||
}
|
||||
}
|
||||
return commonType;
|
||||
@@ -47,22 +47,22 @@ class FieldTypeResolver {
|
||||
return determineFieldType(this.fieldProcessor.extract(fieldPath, payload));
|
||||
}
|
||||
|
||||
private FieldType determineFieldType(Object fieldValue) {
|
||||
private JsonFieldType determineFieldType(Object fieldValue) {
|
||||
if (fieldValue == null) {
|
||||
return FieldType.NULL;
|
||||
return JsonFieldType.NULL;
|
||||
}
|
||||
if (fieldValue instanceof String) {
|
||||
return FieldType.STRING;
|
||||
return JsonFieldType.STRING;
|
||||
}
|
||||
if (fieldValue instanceof Map) {
|
||||
return FieldType.OBJECT;
|
||||
return JsonFieldType.OBJECT;
|
||||
}
|
||||
if (fieldValue instanceof Collection) {
|
||||
return FieldType.ARRAY;
|
||||
return JsonFieldType.ARRAY;
|
||||
}
|
||||
if (fieldValue instanceof Boolean) {
|
||||
return FieldType.BOOLEAN;
|
||||
return JsonFieldType.BOOLEAN;
|
||||
}
|
||||
return FieldType.NUMBER;
|
||||
return JsonFieldType.NUMBER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* A {@link PayloadHandler} for JSON payloads
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JsonPayloadHandler implements PayloadHandler {
|
||||
|
||||
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper()
|
||||
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
private final String rawPayload;
|
||||
|
||||
JsonPayloadHandler(String payload) throws IOException {
|
||||
this.rawPayload = payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
|
||||
List<FieldDescriptor> missingFields = new ArrayList<FieldDescriptor>();
|
||||
Object payload = readPayload();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
if (!fieldDescriptor.isOptional()
|
||||
&& !this.fieldProcessor.hasField(
|
||||
JsonFieldPath.compile(fieldDescriptor.getPath()), payload)) {
|
||||
missingFields.add(fieldDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return missingFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUndocumentedPayload(List<FieldDescriptor> fieldDescriptors) {
|
||||
Object payload = readPayload();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
JsonFieldPath path = JsonFieldPath.compile(fieldDescriptor.getPath());
|
||||
this.fieldProcessor.remove(path, payload);
|
||||
}
|
||||
if (!isEmpty(payload)) {
|
||||
try {
|
||||
return this.objectMapper.writeValueAsString(payload);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object readPayload() {
|
||||
try {
|
||||
return new ObjectMapper().readValue(this.rawPayload, Object.class);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmpty(Object object) {
|
||||
if (object instanceof Map) {
|
||||
return ((Map<?, ?>) object).isEmpty();
|
||||
}
|
||||
return ((List<?>) object).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object determineFieldType(String path) {
|
||||
try {
|
||||
return new JsonFieldTypeResolver().resolveFieldType(path, readPayload());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,8 +37,12 @@ public abstract class PayloadDocumentation {
|
||||
* Creates a {@code FieldDescriptor} that describes a field with the given
|
||||
* {@code path}.
|
||||
* <p>
|
||||
* The {@code path} uses '.' to descend into a child object and ' {@code []}' to
|
||||
* descend into an array. For example, with this JSON payload:
|
||||
* When documenting an XML payload, the {@code path} uses XPath, i.e. '/' is used to
|
||||
* descend to a child node.
|
||||
* <p>
|
||||
* When documenting a JSON payload, the {@code path} uses '.' to descend into a child
|
||||
* object and ' {@code []}' to descend into an array. For example, with this JSON
|
||||
* payload:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
@@ -132,8 +136,7 @@ public abstract class PayloadDocumentation {
|
||||
*/
|
||||
public static Snippet requestFields(Map<String, Object> attributes,
|
||||
FieldDescriptor... descriptors) {
|
||||
return new RequestFieldsSnippet(attributes,
|
||||
Arrays.asList(descriptors));
|
||||
return new RequestFieldsSnippet(attributes, Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,8 +177,7 @@ public abstract class PayloadDocumentation {
|
||||
*/
|
||||
public static Snippet responseFields(Map<String, Object> attributes,
|
||||
FieldDescriptor... descriptors) {
|
||||
return new ResponseFieldsSnippet(attributes,
|
||||
Arrays.asList(descriptors));
|
||||
return new ResponseFieldsSnippet(attributes, Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A handler for a request or response payload
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
interface PayloadHandler {
|
||||
|
||||
/**
|
||||
* Finds the fields that are missing from the handler's payload. A field is missing if
|
||||
* it is described by one of the {@code fieldDescriptors} but is not present in the
|
||||
* payload.
|
||||
*
|
||||
* @param fieldDescriptors the descriptors
|
||||
* @return descriptors for the fields that are missing from the payload
|
||||
* @throws PayloadHandlingException if a failure occurs
|
||||
*/
|
||||
List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors);
|
||||
|
||||
/**
|
||||
* Returns a modified payload, formatted as a String, that only contains the fields
|
||||
* that are undocumented. A field is undocumented if it is present in the handler's
|
||||
* payload but is not described by the given {@code fieldDescriptors}. If the payload
|
||||
* is completely documented, {@code null} is returned
|
||||
*
|
||||
* @param fieldDescriptors the descriptors
|
||||
* @return the undocumented payload, or {@code null} if all of the payload is
|
||||
* documented
|
||||
* @throws PayloadHandlingException if a failure occurs
|
||||
*/
|
||||
String getUndocumentedPayload(List<FieldDescriptor> fieldDescriptors);
|
||||
|
||||
/**
|
||||
* Returns the type of the field with the given {@code path} based on the content of
|
||||
* the payload.
|
||||
*
|
||||
* @param path the field path
|
||||
* @return the type of the field
|
||||
*/
|
||||
Object determineFieldType(String path);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that a failure has occurred during payload handling
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class PayloadHandlingException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@code PayloadHandlingException} with the given cause
|
||||
* @param cause the cause of the failure
|
||||
*/
|
||||
PayloadHandlingException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.io.Reader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
@@ -43,4 +44,13 @@ class RequestFieldsSnippet extends AbstractFieldsSnippet {
|
||||
return result.getRequest().getReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType getContentType(MvcResult result) {
|
||||
String contentType = result.getRequest().getContentType();
|
||||
if (contentType != null) {
|
||||
return MediaType.valueOf(contentType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} the documents the fields in a response.
|
||||
* A {@link Snippet} that documents the fields in a response.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@@ -45,4 +46,13 @@ class ResponseFieldsSnippet extends AbstractFieldsSnippet {
|
||||
return new StringReader(result.getResponse().getContentAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MediaType getContentType(MvcResult result) {
|
||||
String contentType = result.getResponse().getContentType();
|
||||
if (contentType != null) {
|
||||
return MediaType.valueOf(contentType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
/**
|
||||
* A {@link PayloadHandler} for XML payloads
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class XmlPayloadHandler implements PayloadHandler {
|
||||
|
||||
private final DocumentBuilder documentBuilder;
|
||||
|
||||
private final String rawPayload;
|
||||
|
||||
XmlPayloadHandler(String rawPayload) {
|
||||
try {
|
||||
this.documentBuilder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException ex) {
|
||||
throw new IllegalStateException("Failed to create document builder", ex);
|
||||
}
|
||||
this.rawPayload = rawPayload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
|
||||
List<FieldDescriptor> missingFields = new ArrayList<>();
|
||||
Document payload = readPayload();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
if (!fieldDescriptor.isOptional()) {
|
||||
NodeList matchingNodes = findMatchingNodes(fieldDescriptor, payload);
|
||||
if (matchingNodes.getLength() == 0) {
|
||||
missingFields.add(fieldDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return missingFields;
|
||||
}
|
||||
|
||||
private NodeList findMatchingNodes(FieldDescriptor fieldDescriptor, Document payload) {
|
||||
try {
|
||||
return (NodeList) createXPath(fieldDescriptor.getPath()).evaluate(payload,
|
||||
XPathConstants.NODESET);
|
||||
}
|
||||
catch (XPathExpressionException ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Document readPayload() {
|
||||
try {
|
||||
return this.documentBuilder.parse(new InputSource(new StringReader(
|
||||
this.rawPayload)));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private XPathExpression createXPath(String fieldPath) throws XPathExpressionException {
|
||||
return XPathFactory.newInstance().newXPath().compile(fieldPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUndocumentedPayload(List<FieldDescriptor> fieldDescriptors) {
|
||||
Document payload = readPayload();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
NodeList matchingNodes;
|
||||
try {
|
||||
matchingNodes = (NodeList) createXPath(fieldDescriptor.getPath())
|
||||
.evaluate(payload, XPathConstants.NODESET);
|
||||
}
|
||||
catch (XPathExpressionException ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
for (int i = 0; i < matchingNodes.getLength(); i++) {
|
||||
Node node = matchingNodes.item(i);
|
||||
node.getParentNode().removeChild(node);
|
||||
}
|
||||
}
|
||||
if (payload.getChildNodes().getLength() > 0) {
|
||||
return prettyPrint(payload);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String prettyPrint(Document document) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
StreamResult xmlOutput = new StreamResult(stringWriter);
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
transformerFactory.setAttribute("indent-number", 4);
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||
transformer.transform(new DOMSource(document), xmlOutput);
|
||||
return xmlOutput.getWriter().toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new PayloadHandlingException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object determineFieldType(String path) {
|
||||
try {
|
||||
return new JsonFieldTypeResolver().resolveFieldType(path, readPayload());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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 static org.hamcrest.CoreMatchers.equalTo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldValidator}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FieldValidatorTests {
|
||||
|
||||
private final FieldValidator fieldValidator = new FieldValidator();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrownException = ExpectedException.none();
|
||||
|
||||
private StringReader listPayload = new StringReader(
|
||||
"[{\"a\":1},{\"a\":2},{\"b\":{\"c\":3}}]");
|
||||
|
||||
private StringReader payload = new StringReader(
|
||||
"{\"a\":{\"b\":{},\"c\":true,\"d\":[{\"e\":1},{\"e\":2}]}}");
|
||||
|
||||
@Test
|
||||
public void noMissingFieldsAllFieldsDocumented() throws IOException {
|
||||
this.fieldValidator.validate(this.payload, Arrays.asList(new FieldDescriptor(
|
||||
"a.b"), new FieldDescriptor("a.c"), new FieldDescriptor("a.d[].e"),
|
||||
new FieldDescriptor("a.d"), new FieldDescriptor("a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optionalFieldsAreNotReportedMissing() throws IOException {
|
||||
this.fieldValidator.validate(this.payload, Arrays.asList(
|
||||
new FieldDescriptor("a"), new FieldDescriptor("a.b"),
|
||||
new FieldDescriptor("a.c"), new FieldDescriptor("a.d"),
|
||||
new FieldDescriptor("y").optional()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parentIsDocumentedWhenAllChildrenAreDocumented() throws IOException {
|
||||
this.fieldValidator.validate(this.payload, Arrays.asList(new FieldDescriptor(
|
||||
"a.b"), new FieldDescriptor("a.c"), new FieldDescriptor("a.d[].e")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void childIsDocumentedWhenParentIsDocumented() throws IOException {
|
||||
this.fieldValidator.validate(this.payload,
|
||||
Arrays.asList(new FieldDescriptor("a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingField() throws IOException {
|
||||
this.thrownException.expect(SnippetException.class);
|
||||
this.thrownException
|
||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||
+ " in the payload: [y, z]"));
|
||||
this.fieldValidator.validate(this.payload, Arrays.asList(
|
||||
new FieldDescriptor("a"), new FieldDescriptor("a.b"),
|
||||
new FieldDescriptor("y"), new FieldDescriptor("z")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedField() throws IOException {
|
||||
this.thrownException.expect(SnippetException.class);
|
||||
this.thrownException.expectMessage(equalTo(String
|
||||
.format("The following parts of the payload were not"
|
||||
+ " documented:%n{%n \"a\" : {%n \"c\" : true%n }%n}")));
|
||||
this.fieldValidator.validate(this.payload,
|
||||
Arrays.asList(new FieldDescriptor("a.b"), new FieldDescriptor("a.d")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPayloadNoMissingFieldsAllFieldsDocumented() throws IOException {
|
||||
this.fieldValidator.validate(this.listPayload, Arrays.asList(new FieldDescriptor(
|
||||
"[]b.c"), new FieldDescriptor("[]b"), new FieldDescriptor("[]a"),
|
||||
new FieldDescriptor("[]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPayloadParentIsDocumentedWhenAllChildrenAreDocumented()
|
||||
throws IOException {
|
||||
this.fieldValidator.validate(this.listPayload,
|
||||
Arrays.asList(new FieldDescriptor("[]b.c"), new FieldDescriptor("[]a")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listPayloadChildIsDocumentedWhenParentIsDocumented() throws IOException {
|
||||
this.fieldValidator.validate(this.listPayload,
|
||||
Arrays.asList(new FieldDescriptor("[]")));
|
||||
}
|
||||
}
|
||||
@@ -24,89 +24,89 @@ import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldPath}
|
||||
* Tests for {@link JsonFieldPath}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FieldPathTests {
|
||||
public class JsonFieldPathTests {
|
||||
|
||||
@Test
|
||||
public void singleFieldIsPrecise() {
|
||||
assertTrue(FieldPath.compile("a").isPrecise());
|
||||
assertTrue(JsonFieldPath.compile("a").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleNestedFieldIsPrecise() {
|
||||
assertTrue(FieldPath.compile("a.b").isPrecise());
|
||||
assertTrue(JsonFieldPath.compile("a.b").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topLevelArrayIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("[]").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("[]").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldBeneathTopLevelArrayIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("[]a").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("[]a").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrayIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("a[]").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("a[]").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedArrayIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("a.b[]").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("a.b[]").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arrayOfArraysIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("a[][]").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("a[][]").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fieldBeneathAnArrayIsNotPrecise() {
|
||||
assertFalse(FieldPath.compile("a[].b").isPrecise());
|
||||
assertFalse(JsonFieldPath.compile("a[].b").isPrecise());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfSingleElementPath() {
|
||||
assertThat(FieldPath.compile("a").getSegments(), contains("a"));
|
||||
assertThat(JsonFieldPath.compile("a").getSegments(), contains("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfMultipleElementPath() {
|
||||
assertThat(FieldPath.compile("a.b.c").getSegments(), contains("a", "b", "c"));
|
||||
assertThat(JsonFieldPath.compile("a.b.c").getSegments(), contains("a", "b", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfPathWithArraysWithNoDotSeparators() {
|
||||
assertThat(FieldPath.compile("a[]b[]c").getSegments(),
|
||||
assertThat(JsonFieldPath.compile("a[]b[]c").getSegments(),
|
||||
contains("a", "[]", "b", "[]", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfPathWithArraysWithPreAndPostDotSeparators() {
|
||||
assertThat(FieldPath.compile("a.[].b.[].c").getSegments(),
|
||||
assertThat(JsonFieldPath.compile("a.[].b.[].c").getSegments(),
|
||||
contains("a", "[]", "b", "[]", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfPathWithArraysWithPreDotSeparators() {
|
||||
assertThat(FieldPath.compile("a.[]b.[]c").getSegments(),
|
||||
assertThat(JsonFieldPath.compile("a.[]b.[]c").getSegments(),
|
||||
contains("a", "[]", "b", "[]", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfPathWithArraysWithPostDotSeparators() {
|
||||
assertThat(FieldPath.compile("a[].b[].c").getSegments(),
|
||||
assertThat(JsonFieldPath.compile("a[].b[].c").getSegments(),
|
||||
contains("a", "[]", "b", "[]", "c"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compilationOfPathStartingWithAnArray() {
|
||||
assertThat(FieldPath.compile("[]a.b.c").getSegments(),
|
||||
assertThat(JsonFieldPath.compile("[]a.b.c").getSegments(),
|
||||
contains("[]", "a", "b", "c"));
|
||||
}
|
||||
|
||||
@@ -30,19 +30,19 @@ import org.junit.Test;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldProcessor}
|
||||
* Tests for {@link JsonFieldProcessor}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FieldProcessorTests {
|
||||
public class JsonFieldProcessorTests {
|
||||
|
||||
private final FieldProcessor fieldProcessor = new FieldProcessor();
|
||||
private final JsonFieldProcessor fieldProcessor = new JsonFieldProcessor();
|
||||
|
||||
@Test
|
||||
public void extractTopLevelMapEntry() {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("a", "alpha");
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload),
|
||||
equalTo((Object) "alpha"));
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class FieldProcessorTests {
|
||||
Map<String, Object> alpha = new HashMap<>();
|
||||
payload.put("a", alpha);
|
||||
alpha.put("b", "bravo");
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a.b"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload),
|
||||
equalTo((Object) "bravo"));
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class FieldProcessorTests {
|
||||
bravo.put("b", "bravo");
|
||||
List<Map<String, Object>> alpha = Arrays.asList(bravo, bravo);
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a"), payload),
|
||||
equalTo((Object) alpha));
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class FieldProcessorTests {
|
||||
bravo.put("b", "bravo");
|
||||
List<Map<String, Object>> alpha = Arrays.asList(bravo, bravo);
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a[]"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload),
|
||||
equalTo((Object) alpha));
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class FieldProcessorTests {
|
||||
entry.put("b", "bravo");
|
||||
List<Map<String, Object>> alpha = Arrays.asList(entry, entry);
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a[].b"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[].b"), payload),
|
||||
equalTo((Object) Arrays.asList("bravo", "bravo")));
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public class FieldProcessorTests {
|
||||
List<List<Map<String, String>>> alpha = Arrays.asList(
|
||||
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a[][]"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][]"), payload),
|
||||
equalTo((Object) Arrays.asList(entry1, entry2, entry3)));
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public class FieldProcessorTests {
|
||||
List<List<Map<String, String>>> alpha = Arrays.asList(
|
||||
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a[][].id"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][].id"), payload),
|
||||
equalTo((Object) Arrays.asList("1", "2", "3")));
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public class FieldProcessorTests {
|
||||
List<List<Map<String, Object>>> alpha = Arrays.asList(
|
||||
Arrays.asList(entry1, entry2), Arrays.asList(entry3));
|
||||
payload.put("a", alpha);
|
||||
assertThat(this.fieldProcessor.extract(FieldPath.compile("a[][].ids"), payload),
|
||||
assertThat(this.fieldProcessor.extract(JsonFieldPath.compile("a[][].ids"), payload),
|
||||
equalTo((Object) Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3),
|
||||
Arrays.asList(4))));
|
||||
}
|
||||
@@ -132,21 +132,21 @@ public class FieldProcessorTests {
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentTopLevelField() {
|
||||
this.fieldProcessor
|
||||
.extract(FieldPath.compile("a"), new HashMap<String, Object>());
|
||||
.extract(JsonFieldPath.compile("a"), new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentNestedField() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", new HashMap<String, Object>());
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentNestedFieldWhenParentIsNotAMap() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", 5);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a.b"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
@@ -155,20 +155,20 @@ public class FieldProcessorTests {
|
||||
HashMap<String, Object> alpha = new HashMap<String, Object>();
|
||||
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
|
||||
payload.put("a", alpha);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b.c"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a.b.c"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentArrayField() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
this.fieldProcessor.extract(FieldPath.compile("a[]"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
public void nonExistentArrayFieldAsTypeDoesNotMatch() {
|
||||
HashMap<String, Object> payload = new HashMap<String, Object>();
|
||||
payload.put("a", 5);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a[]"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a[]"), payload);
|
||||
}
|
||||
|
||||
@Test(expected = FieldDoesNotExistException.class)
|
||||
@@ -177,14 +177,14 @@ public class FieldProcessorTests {
|
||||
HashMap<String, Object> alpha = new HashMap<String, Object>();
|
||||
alpha.put("b", Arrays.asList(new HashMap<String, Object>()));
|
||||
payload.put("a", alpha);
|
||||
this.fieldProcessor.extract(FieldPath.compile("a.b[].id"), payload);
|
||||
this.fieldProcessor.extract(JsonFieldPath.compile("a.b[].id"), payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeTopLevelMapEntry() {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("a", "alpha");
|
||||
this.fieldProcessor.remove(FieldPath.compile("a"), payload);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public class FieldProcessorTests {
|
||||
Map<String, Object> alpha = new HashMap<>();
|
||||
payload.put("a", alpha);
|
||||
alpha.put("b", "bravo");
|
||||
this.fieldProcessor.remove(FieldPath.compile("a.b"), payload);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a.b"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ public class FieldProcessorTests {
|
||||
public void removeItemsInArray() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper().readValue(
|
||||
"{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
|
||||
this.fieldProcessor.remove(FieldPath.compile("a[].b"), payload);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a[].b"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ public class FieldProcessorTests {
|
||||
public void removeItemsInNestedArray() throws IOException {
|
||||
Map<String, Object> payload = new ObjectMapper().readValue(
|
||||
"{\"a\": [[{\"id\":1},{\"id\":2}], [{\"id\":3}]]}", Map.class);
|
||||
this.fieldProcessor.remove(FieldPath.compile("a[][].id"), payload);
|
||||
this.fieldProcessor.remove(JsonFieldPath.compile("a[][].id"), payload);
|
||||
assertThat(payload.size(), equalTo(0));
|
||||
}
|
||||
|
||||
@@ -29,66 +29,66 @@ import org.junit.rules.ExpectedException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldTypeResolver}
|
||||
* Tests for {@link JsonFieldTypeResolver}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
*/
|
||||
public class FieldTypeResolverTests {
|
||||
public class JsonFieldTypeResolverTests {
|
||||
|
||||
private final FieldTypeResolver fieldTypeResolver = new FieldTypeResolver();
|
||||
private final JsonFieldTypeResolver fieldTypeResolver = new JsonFieldTypeResolver();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrownException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void arrayField() throws IOException {
|
||||
assertFieldType(FieldType.ARRAY, "[]");
|
||||
assertFieldType(JsonFieldType.ARRAY, "[]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void booleanField() throws IOException {
|
||||
assertFieldType(FieldType.BOOLEAN, "true");
|
||||
assertFieldType(JsonFieldType.BOOLEAN, "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void objectField() throws IOException {
|
||||
assertFieldType(FieldType.OBJECT, "{}");
|
||||
assertFieldType(JsonFieldType.OBJECT, "{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullField() throws IOException {
|
||||
assertFieldType(FieldType.NULL, "null");
|
||||
assertFieldType(JsonFieldType.NULL, "null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void numberField() throws IOException {
|
||||
assertFieldType(FieldType.NUMBER, "1.2345");
|
||||
assertFieldType(JsonFieldType.NUMBER, "1.2345");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stringField() throws IOException {
|
||||
assertFieldType(FieldType.STRING, "\"Foo\"");
|
||||
assertFieldType(JsonFieldType.STRING, "\"Foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedField() throws IOException {
|
||||
assertThat(this.fieldTypeResolver.resolveFieldType("a.b.c",
|
||||
createPayload("{\"a\":{\"b\":{\"c\":{}}}}")), equalTo(FieldType.OBJECT));
|
||||
createPayload("{\"a\":{\"b\":{\"c\":{}}}}")), equalTo(JsonFieldType.OBJECT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleFieldsWithSameType() throws IOException {
|
||||
assertThat(this.fieldTypeResolver.resolveFieldType("a[].id",
|
||||
createPayload("{\"a\":[{\"id\":1},{\"id\":2}]}")),
|
||||
equalTo(FieldType.NUMBER));
|
||||
equalTo(JsonFieldType.NUMBER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleFieldsWithDifferentTypes() throws IOException {
|
||||
assertThat(this.fieldTypeResolver.resolveFieldType("a[].id",
|
||||
createPayload("{\"a\":[{\"id\":1},{\"id\":true}]}")),
|
||||
equalTo(FieldType.VARIES));
|
||||
equalTo(JsonFieldType.VARIES));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,7 +99,7 @@ public class FieldTypeResolverTests {
|
||||
this.fieldTypeResolver.resolveFieldType("a.b", createPayload("{\"a\":{}}"));
|
||||
}
|
||||
|
||||
private void assertFieldType(FieldType expectedType, String jsonValue)
|
||||
private void assertFieldType(JsonFieldType expectedType, String jsonValue)
|
||||
throws IOException {
|
||||
assertThat(this.fieldTypeResolver.resolveFieldType("field",
|
||||
createSimplePayload(jsonValue)), equalTo(expectedType));
|
||||
@@ -36,6 +36,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
@@ -65,8 +66,8 @@ public class RequestFieldsSnippetTests {
|
||||
.row("a.c", "String", "two") //
|
||||
.row("a", "Object", "three"));
|
||||
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||
.description("one"), fieldWithPath("a.c").description("two"),
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one"),
|
||||
fieldWithPath("a.c").description("two"),
|
||||
fieldWithPath("a").description("three"))).document(
|
||||
"map-request-with-fields",
|
||||
result(get("/foo").content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")));
|
||||
@@ -80,9 +81,9 @@ public class RequestFieldsSnippetTests {
|
||||
.row("[]a.c", "String", "two") //
|
||||
.row("[]a", "Object", "three"));
|
||||
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b")
|
||||
.description("one"), fieldWithPath("[]a.c").description("two"),
|
||||
fieldWithPath("[]a").description("three"))).document(
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b").description("one"),
|
||||
fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a")
|
||||
.description("three"))).document(
|
||||
"array-request-with-fields",
|
||||
result(get("/foo").content(
|
||||
"[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]")));
|
||||
@@ -94,9 +95,8 @@ public class RequestFieldsSnippetTests {
|
||||
this.thrown
|
||||
.expectMessage(startsWith("The following parts of the payload were not"
|
||||
+ " documented:"));
|
||||
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList())
|
||||
.document("undocumented-request-field",
|
||||
result(get("/foo").content("{\"a\": 5}")));
|
||||
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList()).document(
|
||||
"undocumented-request-field", result(get("/foo").content("{\"a\": 5}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -105,18 +105,16 @@ public class RequestFieldsSnippetTests {
|
||||
this.thrown
|
||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||
+ " in the payload: [a.b]"));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||
.description("one"))).document("missing-request-fields",
|
||||
result(get("/foo").content("{}")));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
|
||||
.document("missing-request-fields", result(get("/foo").content("{}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||
.description("one").optional())).document(
|
||||
"missing-optional-request-field-with-no-type", result(get("/foo")
|
||||
.content("{ }")));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")
|
||||
.optional())).document("missing-optional-request-field-with-no-type",
|
||||
result(get("/foo").content("{ }")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,10 +126,9 @@ public class RequestFieldsSnippetTests {
|
||||
this.thrown
|
||||
.expectMessage(endsWith("Fields with the following paths were not found"
|
||||
+ " in the payload: [a.b]"));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b")
|
||||
.description("one"))).document(
|
||||
"undocumented-request-field-and-missing-request-field",
|
||||
result(get("/foo").content("{ \"a\": { \"c\": 5 }}")));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a.b").description("one")))
|
||||
.document("undocumented-request-field-and-missing-request-field",
|
||||
result(get("/foo").content("{ \"a\": { \"c\": 5 }}")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -171,10 +168,74 @@ public class RequestFieldsSnippetTests {
|
||||
resolver));
|
||||
request.setContent("{\"a\": \"foo\"}".getBytes());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
new RequestFieldsSnippet(attributes(key("title").value(
|
||||
"Custom title")), Arrays.asList(fieldWithPath("a").description("one")))
|
||||
.document("request-fields-with-custom-attributes",
|
||||
result(request, response));
|
||||
new RequestFieldsSnippet(attributes(key("title").value("Custom title")),
|
||||
Arrays.asList(fieldWithPath("a").description("one"))).document(
|
||||
"request-fields-with-custom-attributes", result(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlRequestFields() throws IOException {
|
||||
this.snippet.expectRequestFields("xml-request").withContents( //
|
||||
tableWithHeader("Path", "Type", "Description") //
|
||||
.row("a/b", "b", "one") //
|
||||
.row("a/c", "c", "two") //
|
||||
.row("a", "a", "three"));
|
||||
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")
|
||||
.type("b"), fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").type("a"))).document(
|
||||
"xml-request",
|
||||
result(get("/foo").content("<a><b>5</b><c>charlie</c></a>").contentType(
|
||||
MediaType.APPLICATION_XML)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedXmlRequestField() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(startsWith("The following parts of the payload were not"
|
||||
+ " documented:"));
|
||||
new RequestFieldsSnippet(Collections.<FieldDescriptor> emptyList()).document(
|
||||
"undocumented-xml-request-field",
|
||||
result(get("/foo").content("<a><b>5</b></a>").contentType(
|
||||
MediaType.APPLICATION_XML)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlRequestFieldWithNoType() throws IOException {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
|
||||
.document("missing-xml-request",
|
||||
result(get("/foo").contentType(MediaType.APPLICATION_XML)
|
||||
.content("<a>5</a>")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingXmlRequestField() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||
+ " in the payload: [a/b]"));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"),
|
||||
fieldWithPath("a").description("one"))).document(
|
||||
"missing-xml-request-fields",
|
||||
result(get("/foo").contentType(MediaType.APPLICATION_XML).content(
|
||||
"<a></a>")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedXmlRequestFieldAndMissingXmlRequestField() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(startsWith("The following parts of the payload were not"
|
||||
+ " documented:"));
|
||||
this.thrown
|
||||
.expectMessage(endsWith("Fields with the following paths were not found"
|
||||
+ " in the payload: [a/b]"));
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
|
||||
.document("undocumented-xml-request-field-and-missing-xml-request-field",
|
||||
result(get("/foo").contentType(MediaType.APPLICATION_XML)
|
||||
.content("<a><c>5</c></a>")));
|
||||
}
|
||||
|
||||
private FileSystemResource snippetResource(String name) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.endsWith;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -26,13 +28,16 @@ import static org.springframework.restdocs.test.StubMvcResult.result;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.restdocs.snippet.SnippetException;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
@@ -66,10 +71,10 @@ public class ResponseFieldsSnippetTests {
|
||||
response.getWriter().append(
|
||||
"{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":"
|
||||
+ " [{\"id\":356,\"name\": \"sample\"}]}");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id")
|
||||
.description("one"), fieldWithPath("date").description("two"),
|
||||
fieldWithPath("assets").description("three"), fieldWithPath("assets[]")
|
||||
.description("four"),
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("id").description("one"),
|
||||
fieldWithPath("date").description("two"), fieldWithPath("assets")
|
||||
.description("three"),
|
||||
fieldWithPath("assets[]").description("four"),
|
||||
fieldWithPath("assets[].id").description("five"),
|
||||
fieldWithPath("assets[].name").description("six"))).document(
|
||||
"map-response-with-fields", result(response));
|
||||
@@ -86,10 +91,10 @@ public class ResponseFieldsSnippetTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.getWriter()
|
||||
.append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]a.b")
|
||||
.description("one"), fieldWithPath("[]a.c").description("two"),
|
||||
fieldWithPath("[]a").description("three"))).document(
|
||||
"array-response-with-fields", result(response));
|
||||
new ResponseFieldsSnippet(Arrays.asList(
|
||||
fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c")
|
||||
.description("two"), fieldWithPath("[]a").description("three")))
|
||||
.document("array-response-with-fields", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,8 +105,8 @@ public class ResponseFieldsSnippetTests {
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.getWriter().append("[\"a\", \"b\", \"c\"]");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]")
|
||||
.description("one"))).document("array-response", result(response));
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("[]").description("one")))
|
||||
.document("array-response", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,10 +147,80 @@ public class ResponseFieldsSnippetTests {
|
||||
resolver));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.getOutputStream().print("{\"a\": \"foo\"}");
|
||||
new ResponseFieldsSnippet(attributes(key("title").value(
|
||||
"Custom title")), Arrays.asList(fieldWithPath("a").description("one")))
|
||||
.document("response-fields-with-custom-attributes",
|
||||
result(request, response));
|
||||
new ResponseFieldsSnippet(attributes(key("title").value("Custom title")),
|
||||
Arrays.asList(fieldWithPath("a").description("one"))).document(
|
||||
"response-fields-with-custom-attributes", result(request, response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlResponseFields() throws IOException {
|
||||
this.snippet.expectResponseFields("xml-response").withContents( //
|
||||
tableWithHeader("Path", "Type", "Description") //
|
||||
.row("a/b", "b", "one") //
|
||||
.row("a/c", "c", "two") //
|
||||
.row("a", "a", "three"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
response.getOutputStream().print("<a><b>5</b><c>charlie</c></a>");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")
|
||||
.type("b"), fieldWithPath("a/c").description("two").type("c"),
|
||||
fieldWithPath("a").description("three").type("a"))).document(
|
||||
"xml-response", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedXmlResponseField() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(startsWith("The following parts of the payload were not"
|
||||
+ " documented:"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
response.getOutputStream().print("<a><b>5</b></a>");
|
||||
new ResponseFieldsSnippet(Collections.<FieldDescriptor> emptyList()).document(
|
||||
"undocumented-xml-response-field", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xmlResponseFieldWithNoType() throws IOException {
|
||||
this.thrown.expect(FieldTypeRequiredException.class);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
response.getOutputStream().print("<a>5</a>");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a").description("one")))
|
||||
.document("xml-response-no-field-type", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingXmlResponseField() throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(equalTo("Fields with the following paths were not found"
|
||||
+ " in the payload: [a/b]"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
response.getOutputStream().print("<a></a>");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one"),
|
||||
fieldWithPath("a").description("one"))).document(
|
||||
"missing-xml-response-field", result(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void undocumentedXmlResponseFieldAndMissingXmlResponseField()
|
||||
throws IOException {
|
||||
this.thrown.expect(SnippetException.class);
|
||||
this.thrown
|
||||
.expectMessage(startsWith("The following parts of the payload were not"
|
||||
+ " documented:"));
|
||||
this.thrown
|
||||
.expectMessage(endsWith("Fields with the following paths were not found"
|
||||
+ " in the payload: [a/b]"));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
response.getOutputStream().print("<a><c>5</c></a>");
|
||||
new ResponseFieldsSnippet(Arrays.asList(fieldWithPath("a/b").description("one")))
|
||||
.document("undocumented-xml-request-field-and-missing-xml-request-field",
|
||||
result(response));
|
||||
}
|
||||
|
||||
private FileSystemResource snippetResource(String name) {
|
||||
|
||||
Reference in New Issue
Block a user