Improve field type resolution for fields with optional ancestors
Fixes gh-567
This commit is contained in:
@@ -153,7 +153,8 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
if (this.subsectionExtractor != null) {
|
||||
content = verifyContent(this.subsectionExtractor.extractSubsection(content, contentType));
|
||||
}
|
||||
ContentHandler contentHandler = ContentHandler.forContent(content, contentType);
|
||||
ContentHandler contentHandler = ContentHandler.forContentWithDescriptors(content, contentType,
|
||||
this.fieldDescriptors);
|
||||
|
||||
validateFieldDocumentation(contentHandler);
|
||||
|
||||
@@ -193,10 +194,9 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
}
|
||||
|
||||
private void validateFieldDocumentation(ContentHandler payloadHandler) {
|
||||
List<FieldDescriptor> missingFields = payloadHandler.findMissingFields(this.fieldDescriptors);
|
||||
List<FieldDescriptor> missingFields = payloadHandler.findMissingFields();
|
||||
|
||||
String undocumentedPayload = this.ignoreUndocumentedFields ? null
|
||||
: payloadHandler.getUndocumentedContent(this.fieldDescriptors);
|
||||
String undocumentedPayload = this.ignoreUndocumentedFields ? null : payloadHandler.getUndocumentedContent();
|
||||
|
||||
if (!missingFields.isEmpty() || StringUtils.hasText(undocumentedPayload)) {
|
||||
String message = "";
|
||||
|
||||
@@ -30,41 +30,40 @@ interface ContentHandler extends FieldTypeResolver {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* it is described but is not present in the payload.
|
||||
* @return descriptors for the fields that are missing from the payload
|
||||
* @throws PayloadHandlingException if a failure occurs
|
||||
*/
|
||||
List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors);
|
||||
List<FieldDescriptor> findMissingFields();
|
||||
|
||||
/**
|
||||
* Returns modified content, formatted as a String, that only contains the fields that
|
||||
* are undocumented. A field is undocumented if it is present in the handler's content
|
||||
* but is not described by the given {@code fieldDescriptors}. If the content is
|
||||
* completely documented, {@code null} is returned
|
||||
* @param fieldDescriptors the descriptors
|
||||
* but is not described. If the content is completely documented, {@code null} is
|
||||
* returned
|
||||
* @return the undocumented content, or {@code null} if all of the content is
|
||||
* documented
|
||||
* @throws PayloadHandlingException if a failure occurs
|
||||
*/
|
||||
String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors);
|
||||
String getUndocumentedContent();
|
||||
|
||||
/**
|
||||
* Create a {@link ContentHandler} for the given content type and payload.
|
||||
* Create a {@link ContentHandler} for the given content type and payload, described
|
||||
* by the given descriptors.
|
||||
* @param content the payload
|
||||
* @param contentType the content type
|
||||
* @param descriptors descriptors of the content
|
||||
* @return the ContentHandler
|
||||
* @throws PayloadHandlingException if no known ContentHandler can handle the content
|
||||
*/
|
||||
static ContentHandler forContent(byte[] content, MediaType contentType) {
|
||||
|
||||
static ContentHandler forContentWithDescriptors(byte[] content, MediaType contentType,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
try {
|
||||
return new JsonContentHandler(content);
|
||||
return new JsonContentHandler(content, descriptors);
|
||||
}
|
||||
catch (Exception je) {
|
||||
try {
|
||||
return new XmlContentHandler(content);
|
||||
return new XmlContentHandler(content, descriptors);
|
||||
}
|
||||
catch (Exception xe) {
|
||||
throw new PayloadHandlingException(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2018 the original author or authors.
|
||||
* Copyright 2014-2019 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.
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.restdocs.payload;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
@@ -33,9 +36,25 @@ public interface FieldTypeResolver {
|
||||
* @param content the payload that the {@code FieldTypeResolver} should handle
|
||||
* @param contentType the content type of the payload
|
||||
* @return the {@code FieldTypeResolver}
|
||||
* @deprecated since 2.0.4 in favor of
|
||||
* {@link #forContentWithDescriptors(byte[], MediaType, List)}
|
||||
*/
|
||||
@Deprecated
|
||||
static FieldTypeResolver forContent(byte[] content, MediaType contentType) {
|
||||
return ContentHandler.forContent(content, contentType);
|
||||
return forContentWithDescriptors(content, contentType, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code FieldTypeResolver} for the given {@code content} and
|
||||
* {@code contentType}, described by the given {@code descriptors}.
|
||||
* @param content the payload that the {@code FieldTypeResolver} should handle
|
||||
* @param contentType the content type of the payload
|
||||
* @param descriptors the descriptors of the content
|
||||
* @return the {@code FieldTypeResolver}
|
||||
*/
|
||||
static FieldTypeResolver forContentWithDescriptors(byte[] content, MediaType contentType,
|
||||
List<FieldDescriptor> descriptors) {
|
||||
return ContentHandler.forContentWithDescriptors(content, contentType, descriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,18 +44,21 @@ class JsonContentHandler implements ContentHandler {
|
||||
|
||||
private final byte[] rawContent;
|
||||
|
||||
JsonContentHandler(byte[] content) {
|
||||
private final List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
JsonContentHandler(byte[] content, List<FieldDescriptor> fieldDescriptors) {
|
||||
this.rawContent = content;
|
||||
this.fieldDescriptors = fieldDescriptors;
|
||||
readContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
|
||||
public List<FieldDescriptor> findMissingFields() {
|
||||
List<FieldDescriptor> missingFields = new ArrayList<>();
|
||||
Object payload = readContent();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
|
||||
if (!fieldDescriptor.isOptional() && !this.fieldProcessor.hasField(fieldDescriptor.getPath(), payload)
|
||||
&& !isNestedBeneathMissingOptionalField(fieldDescriptor, fieldDescriptors, payload)) {
|
||||
&& !isNestedBeneathMissingOptionalField(fieldDescriptor, payload)) {
|
||||
missingFields.add(fieldDescriptor);
|
||||
}
|
||||
}
|
||||
@@ -63,9 +66,8 @@ class JsonContentHandler implements ContentHandler {
|
||||
return missingFields;
|
||||
}
|
||||
|
||||
private boolean isNestedBeneathMissingOptionalField(FieldDescriptor missing, List<FieldDescriptor> fieldDescriptors,
|
||||
Object payload) {
|
||||
List<FieldDescriptor> candidates = new ArrayList<>(fieldDescriptors);
|
||||
private boolean isNestedBeneathMissingOptionalField(FieldDescriptor missing, Object payload) {
|
||||
List<FieldDescriptor> candidates = new ArrayList<>(this.fieldDescriptors);
|
||||
candidates.remove(missing);
|
||||
for (FieldDescriptor candidate : candidates) {
|
||||
if (candidate.isOptional() && missing.getPath().startsWith(candidate.getPath())
|
||||
@@ -98,9 +100,9 @@ class JsonContentHandler implements ContentHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
|
||||
public String getUndocumentedContent() {
|
||||
Object content = readContent();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
|
||||
if (describesSubsection(fieldDescriptor)) {
|
||||
this.fieldProcessor.removeSubsection(fieldDescriptor.getPath(), content);
|
||||
}
|
||||
@@ -154,7 +156,9 @@ class JsonContentHandler implements ContentHandler {
|
||||
.discoverFieldTypes(fieldDescriptor.getPath(), readContent())
|
||||
.coalesce(fieldDescriptor.isOptional());
|
||||
if (descriptorFieldType == JsonFieldType.VARIES || descriptorFieldType == actualFieldType
|
||||
|| (fieldDescriptor.isOptional() && actualFieldType == JsonFieldType.NULL)) {
|
||||
|| (fieldDescriptor.isOptional() && actualFieldType == JsonFieldType.NULL)
|
||||
|| (isNestedBeneathMissingOptionalField(fieldDescriptor, readContent())
|
||||
&& actualFieldType == JsonFieldType.VARIES)) {
|
||||
return descriptorFieldType;
|
||||
}
|
||||
throw new FieldTypesDoNotMatchException(fieldDescriptor, actualFieldType);
|
||||
|
||||
@@ -53,7 +53,9 @@ class XmlContentHandler implements ContentHandler {
|
||||
|
||||
private final byte[] rawContent;
|
||||
|
||||
XmlContentHandler(byte[] rawContent) {
|
||||
private final List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
XmlContentHandler(byte[] rawContent, List<FieldDescriptor> fieldDescriptors) {
|
||||
try {
|
||||
this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
}
|
||||
@@ -61,14 +63,15 @@ class XmlContentHandler implements ContentHandler {
|
||||
throw new IllegalStateException("Failed to create document builder", ex);
|
||||
}
|
||||
this.rawContent = rawContent;
|
||||
this.fieldDescriptors = fieldDescriptors;
|
||||
readPayload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldDescriptor> findMissingFields(List<FieldDescriptor> fieldDescriptors) {
|
||||
public List<FieldDescriptor> findMissingFields() {
|
||||
List<FieldDescriptor> missingFields = new ArrayList<>();
|
||||
Document payload = readPayload();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
|
||||
if (!fieldDescriptor.isOptional()) {
|
||||
NodeList matchingNodes = findMatchingNodes(fieldDescriptor, payload);
|
||||
if (matchingNodes.getLength() == 0) {
|
||||
@@ -103,10 +106,10 @@ class XmlContentHandler implements ContentHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
|
||||
public String getUndocumentedContent() {
|
||||
Document payload = readPayload();
|
||||
List<Node> matchedButNotRemoved = new ArrayList<>();
|
||||
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
|
||||
for (FieldDescriptor fieldDescriptor : this.fieldDescriptors) {
|
||||
NodeList matchingNodes;
|
||||
try {
|
||||
matchingNodes = (NodeList) createXPath(fieldDescriptor.getPath()).evaluate(payload,
|
||||
|
||||
Reference in New Issue
Block a user