Introduce relaxed snippets that don’t fail when item is not documented
Previously, many of the snippets would fail when something wasn’t documented. This worked well when writing exhaustive API documentation, but was cumbersome when trying to document a scenario that might only being interested in a subset of the links, response fields, etc. It was necessary to mark things that were not of interest as being ignored. This commit introduces a relaxed variant of several snippets. A relaxed snippet will not fail if something has not been documented. Instead, the undocumented thing will be ignored. If something has been documented but it does not exist a failure will still occur. Closes gh-175
This commit is contained in:
@@ -66,6 +66,26 @@ public abstract class HypermediaDocumentation {
|
||||
Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API operation's
|
||||
* response. Links will be extracted from the response automatically based on its
|
||||
* content type and will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a link is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented links will be ignored.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param descriptors the descriptions of the response's links
|
||||
* @return the snippet that will document the links
|
||||
*/
|
||||
public static LinksSnippet relaxedLinks(LinkDescriptor... descriptors) {
|
||||
return new LinksSnippet(new ContentTypeLinkExtractor(),
|
||||
Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API call's
|
||||
* response. The given {@code attributes} will be available during snippet generation.
|
||||
@@ -80,6 +100,10 @@ public abstract class HypermediaDocumentation {
|
||||
* If you do not want to document a link, a link descriptor can be marked as
|
||||
* {@link LinkDescriptor#ignored}. This will prevent it from appearing in the
|
||||
* generated snippet while avoiding the failure described above.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the response's links
|
||||
@@ -91,6 +115,29 @@ public abstract class HypermediaDocumentation {
|
||||
Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API call's
|
||||
* response. The given {@code attributes} will be available during snippet generation.
|
||||
* Links will be extracted from the response automatically based on its content type
|
||||
* and will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a link is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented links will be ignored.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the response's links
|
||||
* @return the snippet that will document the links
|
||||
*/
|
||||
public static LinksSnippet relaxedLinks(Map<String, Object> attributes,
|
||||
LinkDescriptor... descriptors) {
|
||||
return new LinksSnippet(new ContentTypeLinkExtractor(),
|
||||
Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API operation's
|
||||
* response. Links will be extracted from the response using the given
|
||||
@@ -104,6 +151,10 @@ public abstract class HypermediaDocumentation {
|
||||
* If you do not want to document a link, a link descriptor can be marked as
|
||||
* {@link LinkDescriptor#ignored}. This will prevent it from appearing in the
|
||||
* generated snippet while avoiding the failure described above.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param linkExtractor used to extract the links from the response
|
||||
* @param descriptors the descriptions of the response's links
|
||||
@@ -114,6 +165,27 @@ public abstract class HypermediaDocumentation {
|
||||
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API operation's
|
||||
* response. Links will be extracted from the response using the given
|
||||
* {@code linkExtractor} and will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a link is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented links will be ignored.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param linkExtractor used to extract the links from the response
|
||||
* @param descriptors the descriptions of the response's links
|
||||
* @return the snippet that will document the links
|
||||
*/
|
||||
public static LinksSnippet relaxedLinks(LinkExtractor linkExtractor,
|
||||
LinkDescriptor... descriptors) {
|
||||
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API operation's
|
||||
* response. The given {@code attributes} will be available during snippet generation.
|
||||
@@ -128,6 +200,10 @@ public abstract class HypermediaDocumentation {
|
||||
* If you do not want to document a link, a link descriptor can be marked as
|
||||
* {@link LinkDescriptor#ignored}. This will prevent it from appearing in the
|
||||
* generated snippet while avoiding the failure described above.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param linkExtractor used to extract the links from the response
|
||||
@@ -139,6 +215,30 @@ public abstract class HypermediaDocumentation {
|
||||
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the links in the API operation's
|
||||
* response. The given {@code attributes} will be available during snippet generation.
|
||||
* Links will be extracted from the response using the given {@code linkExtractor} and
|
||||
* will be documented using the given {@code descriptors}.
|
||||
* <p>
|
||||
* If a link is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented links will be ignored.
|
||||
* <p>
|
||||
* If a descriptor does not have a {@link LinkDescriptor#description(Object)
|
||||
* description}, the {@link Link#getTitle() title} of the link will be used. If the
|
||||
* link does not have a title a failure will occur.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param linkExtractor used to extract the links from the response
|
||||
* @param descriptors the descriptions of the response's links
|
||||
* @return the snippet that will document the links
|
||||
*/
|
||||
public static LinksSnippet relaxedLinks(LinkExtractor linkExtractor,
|
||||
Map<String, Object> attributes, LinkDescriptor... descriptors) {
|
||||
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors), attributes,
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code LinkExtractor} capable of extracting links in Hypermedia
|
||||
* Application Language (HAL) format where the links are found in a map named
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.restdocs.hypermedia;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -50,22 +51,41 @@ public class LinksSnippet extends TemplatedSnippet {
|
||||
|
||||
private final LinkExtractor linkExtractor;
|
||||
|
||||
private final boolean ignoreUndocumentedLinks;
|
||||
|
||||
/**
|
||||
* Creates a new {@code LinksSnippet} that will extract links using the given
|
||||
* {@code linkExtractor} and document them using the given {@code descriptors}.
|
||||
* Undocumented links will trigger a failure.
|
||||
*
|
||||
* @param linkExtractor the link extractor
|
||||
* @param descriptors the link descriptors
|
||||
*/
|
||||
protected LinksSnippet(LinkExtractor linkExtractor,
|
||||
List<LinkDescriptor> descriptors) {
|
||||
this(linkExtractor, descriptors, null);
|
||||
this(linkExtractor, descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code LinksSnippet} that will extract links using the given
|
||||
* {@code linkExtractor} and document them using the given {@code descriptors}. If
|
||||
* {@code ignoreUndocumentedLinks} is {@code true}, undocumented links will be ignored
|
||||
* and will not trigger a failure.
|
||||
*
|
||||
* @param linkExtractor the link extractor
|
||||
* @param descriptors the link descriptors
|
||||
* @param ignoreUndocumentedLinks whether undocumented links should be ignored
|
||||
*/
|
||||
protected LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedLinks) {
|
||||
this(linkExtractor, descriptors, null, ignoreUndocumentedLinks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code LinksSnippet} that will extract links using the given
|
||||
* {@code linkExtractor} and document them using the given {@code descriptors}. The
|
||||
* given {@code attributes} will be included in the model during template rendering.
|
||||
* Undocumented links will trigger a failure.
|
||||
*
|
||||
* @param linkExtractor the link extractor
|
||||
* @param descriptors the link descriptors
|
||||
@@ -73,12 +93,30 @@ public class LinksSnippet extends TemplatedSnippet {
|
||||
*/
|
||||
protected LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
this(linkExtractor, descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code LinksSnippet} that will extract links using the given
|
||||
* {@code linkExtractor} and document them using the given {@code descriptors}. The
|
||||
* given {@code attributes} will be included in the model during template rendering.
|
||||
* If {@code ignoreUndocumentedLinks} is {@code true}, undocumented links will be
|
||||
* ignored and will not trigger a failure.
|
||||
*
|
||||
* @param linkExtractor the link extractor
|
||||
* @param descriptors the link descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedLinks whether undocumented links should be ignored
|
||||
*/
|
||||
protected LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedLinks) {
|
||||
super("links", attributes);
|
||||
this.linkExtractor = linkExtractor;
|
||||
for (LinkDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getRel(), "Link descriptors must have a rel");
|
||||
this.descriptorsByRel.put(descriptor.getRel(), descriptor);
|
||||
}
|
||||
this.ignoreUndocumentedLinks = ignoreUndocumentedLinks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,8 +138,14 @@ public class LinksSnippet extends TemplatedSnippet {
|
||||
private void validate(Map<String, List<Link>> links) {
|
||||
Set<String> actualRels = links.keySet();
|
||||
|
||||
Set<String> undocumentedRels = new HashSet<>(actualRels);
|
||||
undocumentedRels.removeAll(this.descriptorsByRel.keySet());
|
||||
Set<String> undocumentedRels;
|
||||
if (this.ignoreUndocumentedLinks) {
|
||||
undocumentedRels = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
undocumentedRels = new HashSet<>(actualRels);
|
||||
undocumentedRels.removeAll(this.descriptorsByRel.keySet());
|
||||
}
|
||||
|
||||
Set<String> requiredRels = new HashSet<>();
|
||||
for (Entry<String, LinkDescriptor> relAndDescriptor : this.descriptorsByRel
|
||||
|
||||
@@ -39,20 +39,42 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
|
||||
private List<FieldDescriptor> fieldDescriptors;
|
||||
private final List<FieldDescriptor> fieldDescriptors;
|
||||
|
||||
private final boolean ignoreUndocumentedFields;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* during template rendering. Undocumented fields will trigger a failure.
|
||||
*
|
||||
* @param type the type of the fields
|
||||
* @param descriptors the field descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @deprecated since 1.1 in favor of
|
||||
* {@link #AbstractFieldsSnippet(String, List, Map, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected AbstractFieldsSnippet(String type, List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
this(type, descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 type, List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
|
||||
super(type + "-fields", attributes);
|
||||
for (FieldDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getPath(), "Field descriptors must have a path");
|
||||
@@ -65,6 +87,7 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
|
||||
}
|
||||
this.fieldDescriptors = descriptors;
|
||||
this.ignoreUndocumentedFields = ignoreUndocumentedFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,8 +134,9 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
|
||||
private void validateFieldDocumentation(ContentHandler payloadHandler) {
|
||||
List<FieldDescriptor> missingFields = payloadHandler
|
||||
.findMissingFields(this.fieldDescriptors);
|
||||
String undocumentedPayload = payloadHandler
|
||||
.getUndocumentedContent(this.fieldDescriptors);
|
||||
|
||||
String undocumentedPayload = this.ignoreUndocumentedFields ? null
|
||||
: payloadHandler.getUndocumentedContent(this.fieldDescriptors);
|
||||
|
||||
if (!missingFields.isEmpty() || StringUtils.hasText(undocumentedPayload)) {
|
||||
String message = "";
|
||||
|
||||
@@ -119,6 +119,22 @@ public abstract class PayloadDocumentation {
|
||||
return new RequestFieldsSnippet(Arrays.asList(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}.
|
||||
* <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 descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
FieldDescriptor... descriptors) {
|
||||
return new RequestFieldsSnippet(Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the fields of the API operation's
|
||||
* request payload. The fields will be documented using the given {@code descriptors}
|
||||
@@ -145,6 +161,24 @@ public abstract class PayloadDocumentation {
|
||||
return new RequestFieldsSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the fields of the API operation'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 attributes the attributes
|
||||
* @param descriptors the descriptions of the request payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static RequestFieldsSnippet relaxedRequestFields(
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
return new RequestFieldsSnippet(Arrays.asList(descriptors), attributes, 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}
|
||||
@@ -169,6 +203,23 @@ public abstract class PayloadDocumentation {
|
||||
return new ResponseFieldsSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* .
|
||||
* <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 descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
FieldDescriptor... descriptors) {
|
||||
return new ResponseFieldsSnippet(Arrays.asList(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}
|
||||
@@ -195,4 +246,22 @@ public abstract class PayloadDocumentation {
|
||||
return new ResponseFieldsSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* 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 attributes the attributes
|
||||
* @param descriptors the descriptions of the response payload's fields
|
||||
* @return the snippet that will document the fields
|
||||
* @see #fieldWithPath(String)
|
||||
*/
|
||||
public static ResponseFieldsSnippet relaxedResponseFields(
|
||||
Map<String, Object> attributes, FieldDescriptor... descriptors) {
|
||||
return new ResponseFieldsSnippet(Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,25 +37,56 @@ public class RequestFieldsSnippet extends AbstractFieldsSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestFieldsSnippet} that will document the fields in the
|
||||
* request using the given {@code descriptors}.
|
||||
* request using the given {@code descriptors}. Undocumented fields will trigger a
|
||||
* failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
*/
|
||||
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
this(descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestFieldsSnippet} that will document the fields in the
|
||||
* request using the given {@code descriptors}. If {@code ignoreUndocumentedFields} is
|
||||
* {@code true}, undocumented fields will be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
|
||||
*/
|
||||
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedFields) {
|
||||
this(descriptors, null, ignoreUndocumentedFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestFieldsSnippet} that will document the fields in the
|
||||
* request using the given {@code descriptors}. The given {@code attributes} will be
|
||||
* included in the model during template rendering.
|
||||
* included in the model during template rendering. Undocumented fields will trigger a
|
||||
* failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("request", descriptors, attributes);
|
||||
this(descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestFieldsSnippet} that will document the fields in the
|
||||
* request using the given {@code descriptors}. 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 descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
|
||||
*/
|
||||
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
|
||||
super("request", descriptors, attributes, ignoreUndocumentedFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,25 +37,57 @@ public class ResponseFieldsSnippet extends AbstractFieldsSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseFieldsSnippet} that will document the fields in the
|
||||
* response using the given {@code descriptors}.
|
||||
* response using the given {@code descriptors}. Undocumented fields will trigger a
|
||||
* failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
*/
|
||||
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
this(descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseFieldsSnippet} that will document the fields in the
|
||||
* response using the given {@code descriptors}. If {@code ignoreUndocumentedFields}
|
||||
* is {@code true}, undocumented fields will be ignored and will not trigger a
|
||||
* failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
|
||||
*/
|
||||
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedFields) {
|
||||
this(descriptors, null, ignoreUndocumentedFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseFieldsSnippet} that will document the fields in the
|
||||
* response using the given {@code descriptors}. The given {@code attributes} will be
|
||||
* included in the model during template rendering.
|
||||
* included in the model during template rendering. Undocumented fields will trigger a
|
||||
* failure.
|
||||
*
|
||||
* @param descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("response", descriptors, attributes);
|
||||
this(descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code ResponseFieldsSnippet} that will document the fields in the
|
||||
* response using the given {@code descriptors}. 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 descriptors the descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedFields whether undocumented fields should be ignored
|
||||
*/
|
||||
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedFields) {
|
||||
super("response", descriptors, attributes, ignoreUndocumentedFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.restdocs.request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -40,18 +41,42 @@ public abstract class AbstractParametersSnippet extends TemplatedSnippet {
|
||||
|
||||
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
|
||||
|
||||
private final boolean ignoreUndocumentedParameters;
|
||||
|
||||
/**
|
||||
* Creates a new {@code AbstractParametersSnippet} that will produce a snippet with
|
||||
* the given {@code snippetName} that will document parameters using the given
|
||||
* {@code descriptors}. The given {@code attributes} will be included in the model
|
||||
* during template rendering.
|
||||
* during template rendering. Undocumented parameters will trigger a failure.
|
||||
*
|
||||
* @param snippetName The snippet name
|
||||
* @param descriptors The descriptors
|
||||
* @param attributes The additional attributes
|
||||
* @deprecated since 1.1 in favour of
|
||||
* {@link #AbstractParametersSnippet(String, List, Map, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected AbstractParametersSnippet(String snippetName,
|
||||
List<ParameterDescriptor> descriptors, Map<String, Object> attributes) {
|
||||
this(snippetName, descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code AbstractParametersSnippet} that will produce a snippet with
|
||||
* the given {@code snippetName} that will document parameters using the given
|
||||
* {@code descriptors}. The given {@code attributes} will be included in the model
|
||||
* during template rendering. If {@code ignoreUndocumentedParameters} is {@code true},
|
||||
* undocumented parameters will be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param snippetName The snippet name
|
||||
* @param descriptors The descriptors
|
||||
* @param attributes The additional attributes
|
||||
* @param ignoreUndocumentedParameters whether undocumented parameters should be
|
||||
* ignored
|
||||
*/
|
||||
protected AbstractParametersSnippet(String snippetName,
|
||||
List<ParameterDescriptor> descriptors, Map<String, Object> attributes,
|
||||
boolean ignoreUndocumentedParameters) {
|
||||
super(snippetName, attributes);
|
||||
for (ParameterDescriptor descriptor : descriptors) {
|
||||
Assert.notNull(descriptor.getName(),
|
||||
@@ -64,6 +89,7 @@ public abstract class AbstractParametersSnippet extends TemplatedSnippet {
|
||||
}
|
||||
this.descriptorsByName.put(descriptor.getName(), descriptor);
|
||||
}
|
||||
this.ignoreUndocumentedParameters = ignoreUndocumentedParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,8 +118,14 @@ public abstract class AbstractParametersSnippet extends TemplatedSnippet {
|
||||
expectedParameters.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
Set<String> undocumentedParameters = new HashSet<>(actualParameters);
|
||||
undocumentedParameters.removeAll(expectedParameters);
|
||||
Set<String> undocumentedParameters;
|
||||
if (this.ignoreUndocumentedParameters) {
|
||||
undocumentedParameters = Collections.emptySet();
|
||||
}
|
||||
else {
|
||||
undocumentedParameters = new HashSet<>(actualParameters);
|
||||
undocumentedParameters.removeAll(expectedParameters);
|
||||
}
|
||||
Set<String> missingParameters = new HashSet<>(expectedParameters);
|
||||
missingParameters.removeAll(actualParameters);
|
||||
|
||||
|
||||
@@ -44,25 +44,59 @@ public class PathParametersSnippet extends AbstractParametersSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code PathParametersSnippet} that will document the request's path
|
||||
* parameters using the given {@code descriptors}.
|
||||
* parameters using the given {@code descriptors}. Undocumented parameters will
|
||||
* trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
*/
|
||||
protected PathParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
this(descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PathParametersSnippet} that will document the request's path
|
||||
* parameters using the given {@code descriptors}. If
|
||||
* {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will
|
||||
* be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param ignoreUndocumentedParameters whether undocumented parameters should be
|
||||
* ignored
|
||||
*/
|
||||
protected PathParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedParameters) {
|
||||
this(descriptors, null, ignoreUndocumentedParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PathParametersSnippet} that will document the request's path
|
||||
* parameters using the given {@code descriptors}. The given {@code attributes} will
|
||||
* be included in the model during template rendering.
|
||||
* be included in the model during template rendering. Undocumented parameters will
|
||||
* trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected PathParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("path-parameters", descriptors, attributes);
|
||||
this(descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PathParametersSnippet} that will document the request's path
|
||||
* parameters using the given {@code descriptors}. The given {@code attributes} will
|
||||
* be included in the model during template rendering. If
|
||||
* {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will
|
||||
* be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedParameters whether undocumented parameters should be
|
||||
* ignored
|
||||
*/
|
||||
protected PathParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedParameters) {
|
||||
super("path-parameters", descriptors, attributes, ignoreUndocumentedParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.
|
||||
@@ -65,6 +65,22 @@ public abstract class RequestDocumentation {
|
||||
return new PathParametersSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the path parameters from the API
|
||||
* operation's request. The parameters will be documented using the given
|
||||
* {@code descriptors}.
|
||||
* <p>
|
||||
* If a parameter is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented parameters will be ignored.
|
||||
*
|
||||
* @param descriptors the descriptions of the parameters in the request's path
|
||||
* @return the snippet that will document the parameters
|
||||
*/
|
||||
public static PathParametersSnippet relaxedPathParameters(
|
||||
ParameterDescriptor... descriptors) {
|
||||
return new PathParametersSnippet(Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the path parameters from the API
|
||||
* operation's request. The given {@code attributes} will be available during snippet
|
||||
@@ -89,6 +105,24 @@ public abstract class RequestDocumentation {
|
||||
return new PathParametersSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the path parameters from the API
|
||||
* operation's request. The given {@code attributes} will be available during snippet
|
||||
* rendering and the parameters will be documented using the given {@code descriptors}
|
||||
* .
|
||||
* <p>
|
||||
* If a parameter is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented parameters will be ignored.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the parameters in the request's path
|
||||
* @return the snippet that will document the parameters
|
||||
*/
|
||||
public static PathParametersSnippet relaxedPathParameters(
|
||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
||||
return new PathParametersSnippet(Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parameters from the API
|
||||
* operation's request. The parameters will be documented using the given
|
||||
@@ -112,6 +146,23 @@ public abstract class RequestDocumentation {
|
||||
return new RequestParametersSnippet(Arrays.asList(descriptors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parameters from the API
|
||||
* operation's request. The parameters will be documented using the given
|
||||
* {@code descriptors}.
|
||||
* <p>
|
||||
* If a parameter is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented parameters will be ignored.
|
||||
*
|
||||
* @param descriptors The descriptions of the request's parameters
|
||||
* @return the snippet
|
||||
* @see OperationRequest#getParameters()
|
||||
*/
|
||||
public static RequestParametersSnippet relaxedRequestParameters(
|
||||
ParameterDescriptor... descriptors) {
|
||||
return new RequestParametersSnippet(Arrays.asList(descriptors), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parameters from the API
|
||||
* operation's request. The given {@code attributes} will be available during snippet
|
||||
@@ -137,4 +188,23 @@ public abstract class RequestDocumentation {
|
||||
return new RequestParametersSnippet(Arrays.asList(descriptors), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code Snippet} that will document the parameters from the API
|
||||
* operation's request. The given {@code attributes} will be available during snippet
|
||||
* rendering and the parameters will be documented using the given {@code descriptors}
|
||||
* .
|
||||
* <p>
|
||||
* If a parameter is documented, is not marked as optional, and is not present in the
|
||||
* response, a failure will occur. Any undocumented parameters will be ignored.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param descriptors the descriptions of the request's parameters
|
||||
* @return the snippet that will document the parameters
|
||||
* @see OperationRequest#getParameters()
|
||||
*/
|
||||
public static RequestParametersSnippet relaxedRequestParameters(
|
||||
Map<String, Object> attributes, ParameterDescriptor... descriptors) {
|
||||
return new RequestParametersSnippet(Arrays.asList(descriptors), attributes, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -42,25 +42,60 @@ public class RequestParametersSnippet extends AbstractParametersSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestParametersSnippet} that will document the request's
|
||||
* parameters using the given {@code descriptors}.
|
||||
* parameters using the given {@code descriptors}. Undocumented parameters will
|
||||
* trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
*/
|
||||
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors) {
|
||||
this(descriptors, null);
|
||||
this(descriptors, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestParametersSnippet} that will document the request's
|
||||
* parameters using the given {@code descriptors}. If
|
||||
* {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will
|
||||
* be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param ignoreUndocumentedParameters whether undocumented parameters should be
|
||||
* ignored
|
||||
*/
|
||||
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
boolean ignoreUndocumentedParameters) {
|
||||
this(descriptors, null, ignoreUndocumentedParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestParametersSnippet} that will document the request's
|
||||
* parameters using the given {@code descriptors}. The given {@code attributes} will
|
||||
* be included in the model during template rendering.
|
||||
* be included in the model during template rendering. Undocumented parameters will
|
||||
* trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
*/
|
||||
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
Map<String, Object> attributes) {
|
||||
super("request-parameters", descriptors, attributes);
|
||||
this(descriptors, attributes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RequestParametersSnippet} that will document the request's
|
||||
* parameters using the given {@code descriptors}. The given {@code attributes} will
|
||||
* be included in the model during template rendering. If
|
||||
* {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will
|
||||
* be ignored and will not trigger a failure.
|
||||
*
|
||||
* @param descriptors the parameter descriptors
|
||||
* @param attributes the additional attributes
|
||||
* @param ignoreUndocumentedParameters whether undocumented parameters should be
|
||||
* ignored
|
||||
*/
|
||||
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors,
|
||||
Map<String, Object> attributes, boolean ignoreUndocumentedParameters) {
|
||||
super("request-parameters", descriptors, attributes,
|
||||
ignoreUndocumentedParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -56,6 +56,17 @@ public class LinksSnippetTests extends AbstractSnippetTests {
|
||||
.document(operationBuilder("ignored-link").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedLinksCanBeIgnored() throws IOException {
|
||||
this.snippet.expectLinks("ignore-all-undocumented").withContents(
|
||||
tableWithHeader("Relation", "Description").row("b", "Link b"));
|
||||
new LinksSnippet(
|
||||
new StubLinkExtractor().withLinks(new Link("a", "alpha"),
|
||||
new Link("b", "bravo")),
|
||||
Arrays.asList(new LinkDescriptor("b").description("Link b")), true)
|
||||
.document(operationBuilder("ignore-all-undocumented").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void documentedOptionalLink() throws IOException {
|
||||
this.snippet.expectLinks("documented-optional-link").withContents(
|
||||
|
||||
@@ -93,6 +93,19 @@ public class RequestFieldsSnippetTests extends AbstractSnippetTests {
|
||||
.content("{\"a\": 5, \"b\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedRequestFieldsCanBeIgnored() throws IOException {
|
||||
this.snippet.expectRequestFields("ignore-all-undocumented")
|
||||
.withContents(tableWithHeader("Path", "Type", "Description").row("b",
|
||||
"Number", "Field b"));
|
||||
|
||||
new RequestFieldsSnippet(Arrays.asList(fieldWithPath("b").description("Field b")),
|
||||
true).document(
|
||||
operationBuilder("ignore-all-undocumented")
|
||||
.request("http://localhost")
|
||||
.content("{\"a\": 5, \"b\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestFieldsWithCustomAttributes() throws IOException {
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
|
||||
@@ -105,6 +105,18 @@ public class ResponseFieldsSnippetTests extends AbstractSnippetTests {
|
||||
.content("{\"a\": 5, \"b\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedFieldsCanBeIgnored() throws IOException {
|
||||
this.snippet.expectResponseFields("ignore-all-undocumented")
|
||||
.withContents(tableWithHeader("Path", "Type", "Description").row("b",
|
||||
"Number", "Field b"));
|
||||
|
||||
new ResponseFieldsSnippet(
|
||||
Arrays.asList(fieldWithPath("b").description("Field b")), true)
|
||||
.document(operationBuilder("ignore-all-undocumented").response()
|
||||
.content("{\"a\": 5, \"b\": 4}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseFieldsWithCustomAttributes() throws IOException {
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
|
||||
@@ -71,6 +71,19 @@ public class PathParametersSnippetTests extends AbstractSnippetTests {
|
||||
"/{a}/{b}").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedPathParametersCanBeIgnored() throws IOException {
|
||||
this.snippet.expectPathParameters("ignore-all-undocumented").withContents(
|
||||
tableWithTitleAndHeader(getTitle(), "Parameter", "Description").row("b",
|
||||
"two"));
|
||||
new PathParametersSnippet(
|
||||
Arrays.asList(parameterWithName("b").description("two")),
|
||||
true).document(operationBuilder("ignore-all-undocumented")
|
||||
.attribute(RestDocumentationGenerator.ATTRIBUTE_NAME_URL_TEMPLATE,
|
||||
"/{a}/{b}")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingOptionalPathParameter() throws IOException {
|
||||
this.snippet
|
||||
|
||||
@@ -80,6 +80,17 @@ public class RequestParametersSnippetTests extends AbstractSnippetTests {
|
||||
.param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allUndocumentedRequestParametersCanBeIgnored() throws IOException {
|
||||
this.snippet.expectRequestParameters("ignore-all-undocumented").withContents(
|
||||
tableWithHeader("Parameter", "Description").row("b", "two"));
|
||||
new RequestParametersSnippet(
|
||||
Arrays.asList(parameterWithName("b").description("two")), true)
|
||||
.document(operationBuilder("ignore-all-undocumented")
|
||||
.request("http://localhost").param("a", "bravo")
|
||||
.param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingOptionalRequestParameter() throws IOException {
|
||||
this.snippet.expectRequestParameters("missing-optional-request-parameter")
|
||||
|
||||
Reference in New Issue
Block a user