Update field snippets to no longer document whole subsection by default

Previously, when a field was documented it would implicitly document
the whole subsection of the payload identified by that field. This
could lead to users inadvertently failing to document part of the
payload. Arguably, this was a bug as it violated REST Docs' principle
of producing accurate, detail documentation. However, fixing it
requires a breaking change as people may also be relying on this
behaviour. A balance needed to be struck so the fix is being made in
a minor release.

This commit introduces a new subsectionWithPath method which returns a
SubsectionDescriptor; a specialisation of FieldDescriptor. Users
that were intentionally relying on the old behaviour will have to
replace some usage of fieldWithPath with subsectionWithPath instead.
Users who were unintentionally relying on the old behaviour will have
to add some additional descriptors produced using fieldWithPath and
will receive more accurate documentation in return.

Closes gh-274
This commit is contained in:
Andy Wilkinson
2016-10-27 17:33:34 +01:00
parent 7bcfbd9e35
commit cbd96f301d
19 changed files with 830 additions and 252 deletions

View File

@@ -155,12 +155,10 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
for (FieldDescriptor descriptor : descriptors) {
Assert.notNull(descriptor.getPath(), "Field descriptors must have a path");
if (!descriptor.isIgnored()) {
Assert.notNull(descriptor.getDescription(),
"The descriptor for field '" + descriptor.getPath()
+ "' must either have a description or" + " be marked as "
+ "ignored");
Assert.notNull(descriptor.getDescription() != null,
"The descriptor for '" + descriptor.getPath() + "' must have a"
+ " description or it must be marked as ignored");
}
}
this.fieldDescriptors = descriptors;
this.ignoreUndocumentedFields = ignoreUndocumentedFields;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -65,7 +65,12 @@ class JsonContentHandler implements ContentHandler {
Object content = readContent();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
JsonFieldPath path = JsonFieldPath.compile(fieldDescriptor.getPath());
this.fieldProcessor.remove(path, content);
if (describesSubsection(fieldDescriptor)) {
this.fieldProcessor.removeSubsection(path, content);
}
else {
this.fieldProcessor.remove(path, content);
}
}
if (!isEmpty(content)) {
try {
@@ -78,6 +83,10 @@ class JsonContentHandler implements ContentHandler {
return null;
}
private boolean describesSubsection(FieldDescriptor fieldDescriptor) {
return fieldDescriptor instanceof SubsectionDescriptor;
}
private Object readContent() {
try {
return new ObjectMapper().readValue(this.rawContent, Object.class);

View File

@@ -76,6 +76,17 @@ final class JsonFieldProcessor {
});
}
void removeSubsection(final JsonFieldPath path, Object payload) {
traverse(new ProcessingContext(payload, path), new MatchCallback() {
@Override
public void foundMatch(Match match) {
match.removeSubsection();
}
});
}
private void traverse(ProcessingContext context, MatchCallback matchCallback) {
final String segment = context.getSegment();
if (JsonFieldPath.isArraySegment(segment)) {
@@ -149,12 +160,41 @@ final class JsonFieldProcessor {
@Override
public void remove() {
Object removalCandidate = this.map.get(this.segment);
if (isMapWithEntries(removalCandidate)
|| isListWithNonScalarEntries(removalCandidate)) {
return;
}
this.map.remove(this.segment);
if (this.map.isEmpty() && this.parent != null) {
this.parent.remove();
}
}
@Override
public void removeSubsection() {
this.map.remove(this.segment);
if (this.map.isEmpty() && this.parent != null) {
this.parent.removeSubsection();
}
}
private boolean isMapWithEntries(Object object) {
return object instanceof Map && !((Map<?, ?>) object).isEmpty();
}
private boolean isListWithNonScalarEntries(Object object) {
if (!(object instanceof List)) {
return false;
}
for (Object entry : (List<?>) object) {
if (entry instanceof Map || entry instanceof List) {
return true;
}
}
return false;
}
}
private static final class ListMatch implements Match {
@@ -181,12 +221,35 @@ final class JsonFieldProcessor {
@Override
public void remove() {
if (!itemIsEmpty()) {
return;
}
this.items.remove();
if (this.list.isEmpty() && this.parent != null) {
this.parent.remove();
}
}
@Override
public void removeSubsection() {
this.items.remove();
if (this.list.isEmpty() && this.parent != null) {
this.parent.removeSubsection();
}
}
private boolean itemIsEmpty() {
return !isMapWithEntries(this.item) && !isListWithEntries(this.item);
}
private boolean isMapWithEntries(Object object) {
return object instanceof Map && !((Map<?, ?>) object).isEmpty();
}
private boolean isListWithEntries(Object object) {
return object instanceof List && !((List<?>) object).isEmpty();
}
}
private interface MatchCallback {
@@ -200,6 +263,8 @@ final class JsonFieldProcessor {
Object getValue();
void remove();
void removeSubsection();
}
private static final class ProcessingContext {

View File

@@ -102,6 +102,74 @@ public abstract class PayloadDocumentation {
return new FieldDescriptor(path);
}
/**
* Creates a {@code FieldDescriptor} that describes a subsection, i.e. a field and all
* of its descendants, with the given {@code path}.
* <p>
* 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>
* {
* "a":{
* "b":[
* {
* "c":"one"
* },
* {
* "c":"two"
* },
* {
* "d":"three"
* }
* ]
* }
* }
* </pre>
*
* The following paths are all present:
*
* <table summary="Paths and their values">
* <tr>
* <th>Path</th>
* <th>Value</th>
* </tr>
* <tr>
* <td>{@code a}</td>
* <td>An object containing "b"</td>
* </tr>
* <tr>
* <td>{@code a.b}</td>
* <td>An array containing three objects</td>
* </tr>
* <tr>
* <td>{@code a.b[]}</td>
* <td>An array containing three objects</td>
* </tr>
* <tr>
* <td>{@code a.b[].c}</td>
* <td>An array containing the strings "one" and "two"</td>
* </tr>
* <tr>
* <td>{@code a.b[].d}</td>
* <td>The string "three"</td>
* </tr>
* </table>
* <p>
* A subsection descriptor for the array with the path {@code a.b[]} will also
* describe its descendants {@code a.b[].c} and {@code a.b[].d}.
*
* @param path The path of the subsection
* @return a {@code SubsectionDescriptor} ready for further configuration
*/
public static SubsectionDescriptor subsectionWithPath(String path) {
return new SubsectionDescriptor(path);
}
/**
* Returns a {@code Snippet} that will document the fields of the API operations's
* request payload. The fields will be documented using the given {@code descriptors}.
@@ -110,16 +178,19 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field is sufficient for all of its descendants to also be treated as having been
* documented.
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see FieldDescriptor#description(Object)
*/
public static RequestFieldsSnippet requestFields(FieldDescriptor... descriptors) {
return requestFields(Arrays.asList(descriptors));
@@ -133,16 +204,18 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field is sufficient for all of its descendants to also be treated as having been
* documented.
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet requestFields(List<FieldDescriptor> descriptors) {
return new RequestFieldsSnippet(descriptors);
@@ -158,6 +231,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
FieldDescriptor... descriptors) {
@@ -174,6 +248,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
List<FieldDescriptor> descriptors) {
@@ -187,19 +262,21 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param attributes the attributes
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet requestFields(Map<String, Object> attributes,
FieldDescriptor... descriptors) {
@@ -213,19 +290,21 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param attributes the attributes
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet requestFields(Map<String, Object> attributes,
List<FieldDescriptor> descriptors) {
@@ -244,6 +323,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
Map<String, Object> attributes, FieldDescriptor... descriptors) {
@@ -262,6 +342,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
@@ -273,22 +354,25 @@ public abstract class PayloadDocumentation {
* operations's request payload extracted by the given {@code subsectionExtractor}.
* The fields will be documented using the given {@code descriptors}.
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field is sufficient for all of its descendants to also be treated as having been
* documented.
* If a field is present in the subsection of the request payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur.For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet requestFields(
@@ -302,22 +386,25 @@ public abstract class PayloadDocumentation {
* API operations's request payload extracted by the given {@code subsectionExtractor}
* . The fields will be documented using the given {@code descriptors}.
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field is sufficient for all of its descendants to also be treated as having been
* documented.
* If a field is present in the subsection of the request payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet requestFields(
@@ -339,6 +426,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
@@ -360,6 +448,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
@@ -374,16 +463,18 @@ public abstract class PayloadDocumentation {
* The fields will be documented using the given {@code descriptors} and the given
* {@code attributes} will be available during snippet generation.
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
@@ -391,6 +482,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet requestFields(
@@ -405,16 +497,18 @@ public abstract class PayloadDocumentation {
* The fields will be documented using the given {@code descriptors} and the given
* {@code attributes} will be available during snippet generation.
* <p>
* If a field is present in the request payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
@@ -422,6 +516,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet requestFields(
@@ -445,6 +540,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
@@ -469,6 +565,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestFieldsSnippet relaxedRequestFields(
@@ -483,22 +580,25 @@ public abstract class PayloadDocumentation {
* {@code part} of the API operations's request payload. The fields will be documented
* using the given {@code descriptors}.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the payload of the request part, but is not documented by
* one of the descriptors, a failure will occur when the snippet is invoked.
* Similarly, if a field is documented, is not marked as optional, and is not present
* in the request part's payload, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
FieldDescriptor... descriptors) {
@@ -510,21 +610,24 @@ public abstract class PayloadDocumentation {
* {@code part} of the API operations's request payload. The fields will be documented
* using the given {@code descriptors}.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the payload of the request part, but is not documented by
* one of the descriptors, a failure will occur when the snippet is invoked.
* Similarly, if a field is documented, is not marked as optional, and is not present
* in the request part's payload, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
List<FieldDescriptor> descriptors) {
@@ -543,6 +646,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
FieldDescriptor... descriptors) {
@@ -561,6 +665,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
List<FieldDescriptor> descriptors) {
@@ -573,22 +678,25 @@ public abstract class PayloadDocumentation {
* using the given {@code descriptors} and the given {@code attributes} will be
* available during snippet generation.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the payload of the request part, but is not documented by
* one of the descriptors, a failure will occur when the snippet is invoked.
* Similarly, if a field is documented, is not marked as optional, and is not present
* in the request part's payload, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param attributes the attributes
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
Map<String, Object> attributes, FieldDescriptor... descriptors) {
@@ -601,22 +709,25 @@ public abstract class PayloadDocumentation {
* using the given {@code descriptors} and the given {@code attributes} will be
* available during snippet generation.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the payload of the request part, but is not documented by
* one of the descriptors, a failure will occur when the snippet is invoked.
* Similarly, if a field is documented, is not marked as optional, and is not present
* in the request part's payload, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param attributes the attributes
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
@@ -637,6 +748,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
Map<String, Object> attributes, FieldDescriptor... descriptors) {
@@ -657,6 +769,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the request part's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
@@ -669,16 +782,18 @@ public abstract class PayloadDocumentation {
* be extracted by the given {@code subsectionExtractor}. The fields will be
* documented using the given {@code descriptors}.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request part payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param subsectionExtractor the subsection extractor
@@ -686,6 +801,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
@@ -700,16 +816,18 @@ public abstract class PayloadDocumentation {
* be extracted by the given {@code subsectionExtractor}. The fields will be
* documented using the given {@code descriptors}.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request part payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param subsectionExtractor the subsection extractor
@@ -717,6 +835,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
@@ -740,6 +859,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
@@ -764,6 +884,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
@@ -779,16 +900,18 @@ public abstract class PayloadDocumentation {
* documented using the given {@code descriptors} and the given {@code attributes}
* will be available during snippet generation.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request part payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param subsectionExtractor the subsection extractor
@@ -797,6 +920,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
@@ -813,16 +937,18 @@ public abstract class PayloadDocumentation {
* documented using the given {@code descriptors} and the given {@code attributes}
* will be available during snippet generation.
* <p>
* If a field is present in the request part, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the request
* part, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* If a field is present in the subsection of the request part payload, but is not
* documented by one of the descriptors, a failure will occur when the snippet is
* invoked. Similarly, if a field is documented, is not marked as optional, and is not
* present in the subsection, a failure will also occur. For payloads with a
* hierarchical structure, documenting a field with a
* {@link #subsectionWithPath(String) subsection descriptor} will mean that all of its
* descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param part the part name
* @param subsectionExtractor the subsection extractor
@@ -831,6 +957,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
@@ -857,6 +984,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
@@ -883,6 +1011,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
@@ -899,18 +1028,20 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the response payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the response,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet responseFields(FieldDescriptor... descriptors) {
return responseFields(Arrays.asList(descriptors));
@@ -923,19 +1054,21 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the response payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the response,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet responseFields(
@@ -955,6 +1088,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
@@ -973,6 +1107,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
List<FieldDescriptor> descriptors) {
@@ -986,19 +1121,21 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the response payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the response,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param attributes the attributes
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet responseFields(Map<String, Object> attributes,
FieldDescriptor... descriptors) {
@@ -1012,19 +1149,21 @@ public abstract class PayloadDocumentation {
* <p>
* If a field is present in the response payload, but is not documented by one of the
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* field is documented, is not marked as optional, and is not present in the response,
* a failure will also occur. For payloads with a hierarchical structure, documenting
* a field with a {@link #subsectionWithPath(String) subsection descriptor} will mean
* that all of its descendants are also treated as having been documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param attributes the attributes
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet responseFields(Map<String, Object> attributes,
List<FieldDescriptor> descriptors) {
@@ -1043,6 +1182,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
Map<String, Object> attributes, FieldDescriptor... descriptors) {
@@ -1061,6 +1201,7 @@ public abstract class PayloadDocumentation {
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
@@ -1077,18 +1218,21 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* documenting a field with a {@link #subsectionWithPath(String) subsection
* descriptor} will mean that all of its descendants are also treated as having been
* documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet responseFields(
@@ -1107,18 +1251,21 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* documenting a field with a {@link #subsectionWithPath(String) subsection
* descriptor} will mean that all of its descendants are also treated as having been
* documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param descriptors the descriptions of the response payload's fields
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet responseFields(
@@ -1141,6 +1288,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
@@ -1163,6 +1311,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
@@ -1182,12 +1331,14 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* documenting a field with a {@link #subsectionWithPath(String) subsection
* descriptor} will mean that all of its descendants are also treated as having been
* documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
@@ -1195,6 +1346,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet responseFields(
@@ -1215,12 +1367,14 @@ public abstract class PayloadDocumentation {
* descriptors, a failure will occur when the snippet is invoked. Similarly, if a
* field is documented, is not marked as optional, and is not present in the response
* payload, a failure will also occur. For payloads with a hierarchical structure,
* documenting a field is sufficient for all of its descendants to also be treated as
* having been documented.
* documenting a field with a {@link #subsectionWithPath(String) subsection
* descriptor} will mean that all of its descendants are also treated as having been
* documented.
* <p>
* If you do not want to document a field, a field descriptor can be marked as
* {@link FieldDescriptor#ignored}. This will prevent it from appearing in the
* generated snippet while avoiding the failure described above.
* If you do not want to document a field or subsection, a descriptor can be
* {@link FieldDescriptor#ignored configured to ignore it}. The ignored field or
* subsection will not appear in the generated snippet and the failure described above
* will not occur.
*
* @param subsectionExtractor the subsection extractor
* @param attributes the attributes
@@ -1228,6 +1382,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet responseFields(
@@ -1252,6 +1407,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
@@ -1277,6 +1433,7 @@ public abstract class PayloadDocumentation {
* @return the snippet that will document the fields
* @since 1.2.0
* @see #fieldWithPath(String)
* @see #subsectionWithPath(String)
* @see #beneathPath(String)
*/
public static ResponseFieldsSnippet relaxedResponseFields(
@@ -1298,11 +1455,13 @@ public abstract class PayloadDocumentation {
List<FieldDescriptor> descriptors) {
List<FieldDescriptor> prefixedDescriptors = new ArrayList<>();
for (FieldDescriptor descriptor : descriptors) {
FieldDescriptor prefixedDescriptor = new FieldDescriptor(
pathPrefix + descriptor.getPath())
.description(descriptor.getDescription())
.type(descriptor.getType())
.attributes(asArray(descriptor.getAttributes()));
String prefixedPath = pathPrefix + descriptor.getPath();
FieldDescriptor prefixedDescriptor = descriptor instanceof SubsectionDescriptor
? new SubsectionDescriptor(prefixedPath)
: new FieldDescriptor(prefixedPath);
prefixedDescriptor.description(descriptor.getDescription())
.type(descriptor.getType())
.attributes(asArray(descriptor.getAttributes()));
if (descriptor.isIgnored()) {
prefixedDescriptor.ignored();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
/**
* A description of a subsection, i.e. a field and all of its descendants, in a request or
* response payload.
*
* @author Andy Wilkinson
* @since 1.2.0
*/
public class SubsectionDescriptor extends FieldDescriptor {
/**
* Creates a new {@code SubsectionDescriptor} describing the subsection with the given
* {@code path}.
* @param path the path
*/
protected SubsectionDescriptor(String path) {
super(path);
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.restdocs.payload;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
@@ -36,6 +37,7 @@ import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@@ -108,6 +110,7 @@ class XmlContentHandler implements ContentHandler {
@Override
public String getUndocumentedContent(List<FieldDescriptor> fieldDescriptors) {
Document payload = readPayload();
List<Node> matchedButNotRemoved = new ArrayList<>();
for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
NodeList matchingNodes;
try {
@@ -124,17 +127,50 @@ class XmlContentHandler implements ContentHandler {
attr.getOwnerElement().removeAttributeNode(attr);
}
else {
node.getParentNode().removeChild(node);
if (fieldDescriptor instanceof SubsectionDescriptor
|| isLeafNode(node)) {
node.getParentNode().removeChild(node);
}
else {
matchedButNotRemoved.add(node);
}
}
}
}
removeLeafNodes(matchedButNotRemoved);
if (payload.getChildNodes().getLength() > 0) {
return prettyPrint(payload);
}
return null;
}
private void removeLeafNodes(List<Node> candidates) {
boolean changed = true;
while (changed) {
changed = false;
Iterator<Node> iterator = candidates.iterator();
while (iterator.hasNext()) {
Node node = iterator.next();
if (isLeafNode(node)) {
node.getParentNode().removeChild(node);
iterator.remove();
changed = true;
}
}
}
}
private boolean isLeafNode(Node node) {
NodeList childNodes = node.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
if (childNodes.item(i) instanceof Element) {
return false;
}
}
return true;
}
private String prettyPrint(Document document) {
try {
StringWriter stringWriter = new StringWriter();

View File

@@ -201,6 +201,26 @@ public class JsonFieldProcessorTests {
assertThat(payload.size(), equalTo(0));
}
@Test
public void mapWithEntriesIsNotRemovedWhenNotAlsoRemovingDescendants() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
alpha.put("b", "bravo");
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
assertThat(payload.size(), equalTo(1));
}
@Test
public void removeSubsectionRemovesMapWithEntries() {
Map<String, Object> payload = new HashMap<>();
Map<String, Object> alpha = new HashMap<>();
payload.put("a", alpha);
alpha.put("b", "bravo");
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a"), payload);
assertThat(payload.size(), equalTo(0));
}
@Test
public void removeNestedMapEntry() {
Map<String, Object> payload = new HashMap<>();
@@ -229,6 +249,51 @@ public class JsonFieldProcessorTests {
assertThat(payload.size(), equalTo(0));
}
@SuppressWarnings("unchecked")
@Test
public void removeDoesNotRemoveArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper()
.readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload);
assertThat(payload.size(), equalTo(1));
}
@SuppressWarnings("unchecked")
@Test
public void removeDoesNotRemoveArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}",
Map.class);
this.fieldProcessor.remove(JsonFieldPath.compile("a[]"), payload);
assertThat(payload.size(), equalTo(1));
}
@SuppressWarnings("unchecked")
@Test
public void removeRemovesArrayWithOnlyScalarEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper()
.readValue("{\"a\": [\"bravo\", \"charlie\"]}", Map.class);
this.fieldProcessor.remove(JsonFieldPath.compile("a"), payload);
assertThat(payload.size(), equalTo(0));
}
@SuppressWarnings("unchecked")
@Test
public void removeSubsectionRemovesArrayWithMapEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper()
.readValue("{\"a\": [{\"b\":\"bravo\"},{\"b\":\"bravo\"}]}", Map.class);
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload);
assertThat(payload.size(), equalTo(0));
}
@SuppressWarnings("unchecked")
@Test
public void removeSubsectionRemovesArrayWithListEntries() throws IOException {
Map<String, Object> payload = new ObjectMapper().readValue("{\"a\": [[2],[3]]}",
Map.class);
this.fieldProcessor.removeSubsection(JsonFieldPath.compile("a[]"), payload);
assertThat(payload.size(), equalTo(0));
}
@Test
public void extractNestedEntryWithDotInKeys() throws IOException {
Map<String, Object> payload = new HashMap<>();

View File

@@ -131,8 +131,8 @@ public class RequestFieldsSnippetFailureTests {
@Test
public void undocumentedXmlRequestField() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(startsWith(
"The following parts of the payload were not" + " documented:"));
this.thrown.expectMessage(
startsWith("The following parts of the payload were not documented:"));
new RequestFieldsSnippet(Collections.<FieldDescriptor>emptyList())
.document(this.operationBuilder.request("http://localhost")
.content("<a><b>5</b></a>").header(HttpHeaders.CONTENT_TYPE,
@@ -140,6 +140,20 @@ public class RequestFieldsSnippetFailureTests {
.build());
}
@Test
public void xmlDescendentsAreNotDocumentedByFieldDescriptor() throws IOException {
this.thrown.expect(SnippetException.class);
this.thrown.expectMessage(
startsWith("The following parts of the payload were not documented:"));
new RequestFieldsSnippet(
Arrays.asList(fieldWithPath("a").type("a").description("one")))
.document(this.operationBuilder.request("http://localhost")
.content("<a><b>5</b></a>")
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void xmlRequestFieldWithNoType() throws IOException {
this.thrown.expect(FieldTypeRequiredException.class);

View File

@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.snippet.Attributes.attributes;
import static org.springframework.restdocs.snippet.Attributes.key;
@@ -65,6 +66,19 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
.build());
}
@Test
public void entireSubsectionsCanBeDocumented() throws IOException {
this.snippets.expectRequestFields()
.withContents(tableWithHeader("Path", "Type", "Description").row("`a`",
"`Object`", "one"));
new RequestFieldsSnippet(
Arrays.asList(subsectionWithPath("a").description("one")))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}")
.build());
}
@Test
public void subsectionOfMapRequest() throws IOException {
this.snippets.expect("request-fields-beneath-a")
@@ -121,6 +135,18 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
.content("{\"a\": 5, \"b\": 4}").build());
}
@Test
public void entireSubsectionCanBeIgnored() throws IOException {
this.snippets.expectRequestFields()
.withContents(tableWithHeader("Path", "Type", "Description").row("`c`",
"`Number`", "Field c"));
new RequestFieldsSnippet(Arrays.asList(subsectionWithPath("a").ignored(),
fieldWithPath("c").description("Field c")))
.document(this.operationBuilder.request("http://localhost")
.content("{\"a\": {\"b\": 5}, \"c\": 4}").build());
}
@Test
public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException {
this.snippets.expectRequestFields()
@@ -256,6 +282,20 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
.build());
}
@Test
public void entireSubsectionOfXmlPayloadCanBeDocumented() throws IOException {
this.snippets.expectRequestFields().withContents(
tableWithHeader("Path", "Type", "Description").row("`a`", "`a`", "one"));
new RequestFieldsSnippet(
Arrays.asList(subsectionWithPath("a").description("one").type("a")))
.document(this.operationBuilder.request("http://localhost")
.content("<a><b>5</b><c>charlie</c></a>")
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_XML_VALUE)
.build());
}
@Test
public void additionalDescriptors() throws IOException {
this.snippets.expectRequestFields()

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.payload;
import java.util.Arrays;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
/**
* Tests for {@link XmlContentHandler}.
*
* @author Andy Wilkinson
*/
public class XmlContentHandlerTests {
@Test
public void topLevelElementCanBeDocumented() {
String undocumentedContent = createHandler("<a>5</a>").getUndocumentedContent(
Arrays.asList(fieldWithPath("a").type("a").description("description")));
assertThat(undocumentedContent, is(nullValue()));
}
@Test
public void nestedElementCanBeDocumentedLeavingAncestors() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(
fieldWithPath("a/b").type("b").description("description")));
assertThat(undocumentedContent, is(equalTo(String.format("<a/>%n"))));
}
@Test
public void fieldDescriptorDoesNotDocumentEntireSubsection() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays
.asList(fieldWithPath("a").type("a").description("description")));
assertThat(undocumentedContent,
is(equalTo(String.format("<a>%n <b>5</b>%n</a>%n"))));
}
@Test
public void subsectionDescriptorDocumentsEntireSubsection() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(
subsectionWithPath("a").type("a").description("description")));
assertThat(undocumentedContent, is(nullValue()));
}
@Test
public void multipleElementsCanBeInDescendingOrderDocumented() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(
fieldWithPath("a").type("a").description("description"),
fieldWithPath("a/b").type("b").description("description")));
assertThat(undocumentedContent, is(nullValue()));
}
@Test
public void multipleElementsCanBeInAscendingOrderDocumented() {
String undocumentedContent = createHandler("<a><b>5</b></a>")
.getUndocumentedContent(Arrays.asList(
fieldWithPath("a/b").type("b").description("description"),
fieldWithPath("a").type("a").description("description")));
assertThat(undocumentedContent, is(nullValue()));
}
private XmlContentHandler createHandler(String xml) {
return new XmlContentHandler(xml.getBytes());
}
}