Polish "Add support for documenting request part payload fields"

- Rebase on latest code, and make use of new support for the same
  TemplatedSnippet producing multiple snippets with different names
  from the same template
- Expand the documentation
- Apply code formatting
- Add support for relaxed documentation of a request part's fields

Closes gh-270
This commit is contained in:
Andy Wilkinson
2016-10-25 12:04:43 +01:00
parent 13e745a9a9
commit d2a5b38c83
12 changed files with 559 additions and 181 deletions

View File

@@ -65,10 +65,11 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
/**
* Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named
* {@code <type>-fields}. The fields will be documented using the given
* {@code descriptors} and the given {@code attributes} will be included in the model
* during template rendering. If {@code ignoreUndocumentedFields} is {@code true},
* undocumented fields will be ignored and will not trigger a failure.
* {@code <type>-fields} using a template named {@code <type>-fields}. The fields will
* be documented using the given {@code descriptors} and the given {@code attributes}
* will be included in the model during template rendering. If
* {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be
* ignored and will not trigger a failure.
*
* @param type the type of the fields
* @param descriptors the field descriptors
@@ -77,7 +78,27 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
*/
protected AbstractFieldsSnippet(String type, List<FieldDescriptor> descriptors,
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
super(type + "-fields", attributes);
this(type, type, descriptors, attributes, ignoreUndocumentedFields);
}
/**
* Creates a new {@code AbstractFieldsSnippet} that will produce a snippet named
* {@code <name>-fields} using a template named {@code <type>-fields}. The fields will
* be documented using the given {@code descriptors} and the given {@code attributes}
* will be included in the model during template rendering. If
* {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be
* ignored and will not trigger a failure.
*
* @param name the name of the snippet
* @param type the type of the fields
* @param descriptors the field descriptors
* @param attributes the additional attributes
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
*/
protected AbstractFieldsSnippet(String name, String type,
List<FieldDescriptor> descriptors, Map<String, Object> attributes,
boolean ignoreUndocumentedFields) {
super(name + "-fields", type + "-fields", attributes);
for (FieldDescriptor descriptor : descriptors) {
Assert.notNull(descriptor.getPath(), "Field descriptors must have a path");
if (!descriptor.isIgnored()) {

View File

@@ -148,56 +148,6 @@ public abstract class PayloadDocumentation {
return new RequestFieldsSnippet(descriptors);
}
/**
* Returns a {@code Snippet} that will document the fields of the specified {@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 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.
* <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.
*
* @param part the part name
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part, FieldDescriptor... descriptors) {
return requestPartFields(part, Arrays.asList(descriptors));
}
/**
* Returns a {@code Snippet} that will document the fields of the specified {@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 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.
* <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.
*
* @param part the part name
* @param descriptors the descriptions of the request payload's fields
* @return the snippet that will document the fields
* @see #fieldWithPath(String)
*/
public static RequestPartFieldsSnippet requestPartFields(String part, List<FieldDescriptor> descriptors) {
return new RequestPartFieldsSnippet(part, descriptors);
}
/**
* 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}.
@@ -318,6 +268,190 @@ public abstract class PayloadDocumentation {
return new RequestFieldsSnippet(descriptors, attributes, true);
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@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.
* <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.
*
* @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)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
FieldDescriptor... descriptors) {
return requestPartFields(part, Arrays.asList(descriptors));
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@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.
* <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.
*
* @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)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
List<FieldDescriptor> descriptors) {
return new RequestPartFieldsSnippet(part, descriptors);
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. The fields will be documented
* using the given {@code descriptors}.
* <p>
* If a field is documented, is not marked as optional, and is not present in the
* request, a failure will occur. Any undocumented fields will be ignored.
*
* @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)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
FieldDescriptor... descriptors) {
return relaxedRequestPartFields(part, Arrays.asList(descriptors));
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. The fields will be documented
* using the given {@code descriptors}.
* <p>
* If a field is documented, is not marked as optional, and is not present in the
* request, a failure will occur. Any undocumented fields will be ignored.
*
* @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)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
List<FieldDescriptor> descriptors) {
return new RequestPartFieldsSnippet(part, descriptors, true);
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. 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 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.
* <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.
*
* @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)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
Map<String, Object> attributes, FieldDescriptor... descriptors) {
return requestPartFields(part, Arrays.asList(descriptors));
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. 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 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.
* <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.
*
* @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)
*/
public static RequestPartFieldsSnippet requestPartFields(String part,
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
return new RequestPartFieldsSnippet(part, descriptors);
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. 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 documented, is not marked as optional, and is not present in the
* request, a failure will occur. Any undocumented fields will be ignored.
*
* @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)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
Map<String, Object> attributes, FieldDescriptor... descriptors) {
return relaxedRequestPartFields(part, Arrays.asList(descriptors));
}
/**
* Returns a {@code Snippet} that will document the fields of the specified
* {@code part} of the API operations's request payload. 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 documented, is not marked as optional, and is not present in the
* request, a failure will occur. Any undocumented fields will be ignored.
*
* @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)
*/
public static RequestPartFieldsSnippet relaxedRequestPartFields(String part,
Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
return new RequestPartFieldsSnippet(part, descriptors, true);
}
/**
* Returns a {@code Snippet} that will document the fields of the API operation's
* response payload. The fields will be documented using the given {@code descriptors}

View File

@@ -29,10 +29,12 @@ import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.SnippetException;
/**
* A {@link Snippet} that documents the fields in a request.
* A {@link Snippet} that documents the fields in a request part.
*
* @author Mathieu Pousse
* @author Andy Wilkinson
* @see PayloadDocumentation#requestPartFields(String, FieldDescriptor...)
* @see PayloadDocumentation#requestPartFields(String, List)
*/
public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
@@ -40,27 +42,29 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
/**
* Creates a new {@code RequestPartFieldsSnippet} that will document the fields in the
* request part using the given {@code descriptors}. Undocumented fields will trigger a
* failure.
* request part using the given {@code descriptors}. Undocumented fields will trigger
* a failure.
*
* @param partName the part name
* @param partName the part name
* @param descriptors the descriptors
*/
protected RequestPartFieldsSnippet(String partName, List<FieldDescriptor> descriptors) {
protected RequestPartFieldsSnippet(String partName,
List<FieldDescriptor> descriptors) {
this(partName, descriptors, null, false);
}
/**
* Creates a new {@code RequestPartFieldsSnippet} that will document the fields in the
* request part using the given {@code descriptors}. If {@code ignoreUndocumentedFields} is
* {@code true}, undocumented fields will be ignored and will not trigger a failure.
* request part using the given {@code descriptors}. If
* {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be
* ignored and will not trigger a failure.
*
* @param partName the part name
* @param descriptors the descriptors
* @param partName the part name
* @param descriptors the descriptors
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
*/
protected RequestPartFieldsSnippet(String partName, List<FieldDescriptor> descriptors,
boolean ignoreUndocumentedFields) {
boolean ignoreUndocumentedFields) {
this(partName, descriptors, null, ignoreUndocumentedFields);
}
@@ -70,12 +74,12 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
* included in the model during template rendering. Undocumented fields will trigger a
* failure.
*
* @param partName the part name
* @param partName the part name
* @param descriptors the descriptors
* @param attributes the additional attributes
* @param attributes the additional attributes
*/
protected RequestPartFieldsSnippet(String partName, List<FieldDescriptor> descriptors,
Map<String, Object> attributes) {
Map<String, Object> attributes) {
this(partName, descriptors, attributes, false);
}
@@ -86,44 +90,36 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
* {@code ignoreUndocumentedFields} is {@code true}, undocumented fields will be
* ignored and will not trigger a failure.
*
* @param partName the part name
* @param descriptors the descriptors
* @param attributes the additional attributes
* @param partName the part name
* @param descriptors the descriptors
* @param attributes the additional attributes
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
*/
protected RequestPartFieldsSnippet(String partName, List<FieldDescriptor> descriptors,
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
super("request", descriptors, attributes, ignoreUndocumentedFields);
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
super("request-part-" + partName, "request-part", descriptors, attributes,
ignoreUndocumentedFields);
this.partName = partName;
}
@Override
protected MediaType getContentType(Operation operation) {
for (OperationRequestPart candidate : operation.getRequest().getParts()) {
if (candidate.getName().equals(this.partName)) {
return candidate.getHeaders().getContentType();
}
}
throw new SnippetException(missingPartErrorMessage());
return findPart(operation).getHeaders().getContentType();
}
@Override
protected byte[] getContent(Operation operation) throws IOException {
for (OperationRequestPart candidate : operation.getRequest().getParts()) {
if (candidate.getName().equals(this.partName)) {
return candidate.getContent();
}
}
throw new SnippetException(missingPartErrorMessage());
return findPart(operation).getContent();
}
/**
* Prepare the error message because the requested part was not found.
*
* @return see description
*/
protected String missingPartErrorMessage() {
return "Request parts with the following names were not found in the request: " + this.partName;
private OperationRequestPart findPart(Operation operation) {
for (OperationRequestPart candidate : operation.getRequest().getParts()) {
if (candidate.getName().equals(this.partName)) {
return candidate;
}
}
throw new SnippetException("A request part named '" + this.partName
+ "' was not found in the request");
}
/**
@@ -146,7 +142,8 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
* @param additionalDescriptors the additional descriptors
* @return the new snippet
*/
public final RequestPartFieldsSnippet and(List<FieldDescriptor> additionalDescriptors) {
public final RequestPartFieldsSnippet and(
List<FieldDescriptor> additionalDescriptors) {
return andWithPrefix("", additionalDescriptors);
}
@@ -156,17 +153,18 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
* {@code additionalDescriptors}. The given {@code pathPrefix} is applied to the path
* of each additional descriptor.
*
* @param pathPrefix the prefix to apply to the additional descriptors
* @param pathPrefix the prefix to apply to the additional descriptors
* @param additionalDescriptors the additional descriptors
* @return the new snippet
*/
public final RequestPartFieldsSnippet andWithPrefix(String pathPrefix,
FieldDescriptor... additionalDescriptors) {
FieldDescriptor... additionalDescriptors) {
List<FieldDescriptor> combinedDescriptors = new ArrayList<>();
combinedDescriptors.addAll(getFieldDescriptors());
combinedDescriptors.addAll(
PayloadDocumentation.applyPathPrefix(pathPrefix, Arrays.asList(additionalDescriptors)));
return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes());
combinedDescriptors.addAll(PayloadDocumentation.applyPathPrefix(pathPrefix,
Arrays.asList(additionalDescriptors)));
return new RequestPartFieldsSnippet(this.partName, combinedDescriptors,
this.getAttributes());
}
/**
@@ -175,17 +173,18 @@ public class RequestPartFieldsSnippet extends AbstractFieldsSnippet {
* {@code additionalDescriptors}. The given {@code pathPrefix} is applied to the path
* of each additional descriptor.
*
* @param pathPrefix the prefix to apply to the additional descriptors
* @param pathPrefix the prefix to apply to the additional descriptors
* @param additionalDescriptors the additional descriptors
* @return the new snippet
*/
public final RequestPartFieldsSnippet andWithPrefix(String pathPrefix,
List<FieldDescriptor> additionalDescriptors) {
List<FieldDescriptor> additionalDescriptors) {
List<FieldDescriptor> combinedDescriptors = new ArrayList<>(
getFieldDescriptors());
combinedDescriptors.addAll(
PayloadDocumentation.applyPathPrefix(pathPrefix, additionalDescriptors));
return new RequestPartFieldsSnippet(this.partName, combinedDescriptors, this.getAttributes());
return new RequestPartFieldsSnippet(this.partName, combinedDescriptors,
this.getAttributes());
}
}

View File

@@ -0,0 +1,10 @@
|===
|Path|Type|Description
{{#fields}}
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
{{/fields}}
|===

View File

@@ -0,0 +1,5 @@
Path | Type | Description
---- | ---- | -----------
{{#fields}}
`{{path}}` | `{{type}}` | {{description}}
{{/fields}}