Make the built-in snippets more extensible

The commit opens up all of the default snippets so that it's easier
to extend them and modify their behaviour. All of the built-in
snippets are now public with protected constructors. Also, where
appropriate the models used by the snippets have been made more
fine-grained.

Closes gh-73
This commit is contained in:
Andy Wilkinson
2015-09-08 10:30:50 +01:00
parent de095eb9fe
commit c2da4c912a
21 changed files with 249 additions and 84 deletions

View File

@@ -36,31 +36,43 @@ import org.springframework.util.StringUtils;
* A {@link Snippet} that documents the curl command for a request.
*
* @author Andy Wilkinson
* @see CurlDocumentation#curlRequest()
* @see CurlDocumentation#curlRequest(Map)
*/
class CurlRequestSnippet extends TemplatedSnippet {
public class CurlRequestSnippet extends TemplatedSnippet {
CurlRequestSnippet() {
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
*/
protected CurlRequestSnippet() {
this(null);
}
CurlRequestSnippet(Map<String, Object> attributes) {
/**
* Creates a new {@code CurlRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
protected CurlRequestSnippet(Map<String, Object> attributes) {
super("curl-request", attributes);
}
@Override
public Map<String, Object> createModel(Operation operation) throws IOException {
protected Map<String, Object> createModel(Operation operation) throws IOException {
Map<String, Object> model = new HashMap<String, Object>();
model.put("arguments", getCurlCommandArguments(operation));
model.put("url", getUrl(operation));
model.put("options", getOptions(operation));
return model;
}
private String getCurlCommandArguments(Operation operation) throws IOException {
private String getUrl(Operation operation) {
return String.format("'%s'", operation.getRequest().getUri());
}
private String getOptions(Operation operation) throws IOException {
StringWriter command = new StringWriter();
PrintWriter printer = new PrintWriter(command);
printer.print("'");
printer.print(operation.getRequest().getUri());
printer.print("'");
writeOptionToIncludeHeadersInOutput(printer);
writeHttpMethodIfNecessary(operation.getRequest(), printer);
writeHeaders(operation.getRequest(), printer);
@@ -72,7 +84,7 @@ class CurlRequestSnippet extends TemplatedSnippet {
}
private void writeOptionToIncludeHeadersInOutput(PrintWriter writer) {
writer.print(" -i");
writer.print("-i");
}
private void writeHttpMethodIfNecessary(OperationRequest request, PrintWriter writer) {

View File

@@ -39,21 +39,32 @@ import org.springframework.util.StringUtils;
* A {@link Snippet} that documents an HTTP request.
*
* @author Andy Wilkinson
* @see HttpDocumentation#httpRequest()
* @see HttpDocumentation#httpRequest(Map)
*/
class HttpRequestSnippet extends TemplatedSnippet {
public class HttpRequestSnippet extends TemplatedSnippet {
private static final String MULTIPART_BOUNDARY = "6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm";
HttpRequestSnippet() {
/**
* Creates a new {@code HttpRequestSnippet} with no additional attributes.
*/
protected HttpRequestSnippet() {
this(null);
}
HttpRequestSnippet(Map<String, Object> attributes) {
/**
* Creates a new {@code HttpRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
protected HttpRequestSnippet(Map<String, Object> attributes) {
super("http-request", attributes);
}
@Override
public Map<String, Object> createModel(Operation operation) throws IOException {
protected Map<String, Object> createModel(Operation operation) throws IOException {
Map<String, Object> model = new HashMap<String, Object>();
model.put("method", operation.getRequest().getMethod());
model.put(
@@ -61,8 +72,7 @@ class HttpRequestSnippet extends TemplatedSnippet {
operation.getRequest().getUri().getRawPath()
+ (StringUtils.hasText(operation.getRequest().getUri()
.getRawQuery()) ? "?"
+ operation.getRequest().getUri().getRawQuery()
: ""));
+ operation.getRequest().getUri().getRawQuery() : ""));
model.put("headers", getHeaders(operation.getRequest()));
model.put("requestBody", getRequestBody(operation.getRequest()));
return model;

View File

@@ -33,19 +33,30 @@ import org.springframework.restdocs.snippet.TemplatedSnippet;
* A {@link Snippet} that documents an HTTP response.
*
* @author Andy Wilkinson
* @see HttpDocumentation#httpResponse()
* @see HttpDocumentation#httpResponse(Map)
*/
class HttpResponseSnippet extends TemplatedSnippet {
public class HttpResponseSnippet extends TemplatedSnippet {
HttpResponseSnippet() {
/**
* Creates a new {@code HttpResponseSnippet} with no additional attributes.
*/
protected HttpResponseSnippet() {
this(null);
}
HttpResponseSnippet(Map<String, Object> attributes) {
/**
* Creates a new {@code HttpResponseSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
protected HttpResponseSnippet(Map<String, Object> attributes) {
super("http-response", attributes);
}
@Override
public Map<String, Object> createModel(Operation operation) throws IOException {
protected Map<String, Object> createModel(Operation operation) throws IOException {
OperationResponse response = operation.getResponse();
HttpStatus status = response.getStatus();
Map<String, Object> model = new HashMap<String, Object>();

View File

@@ -67,8 +67,8 @@ public abstract class HypermediaDocumentation {
*/
public static Snippet links(Map<String, Object> attributes,
LinkDescriptor... descriptors) {
return new LinksSnippet(new ContentTypeLinkExtractor(), attributes,
Arrays.asList(descriptors));
return new LinksSnippet(new ContentTypeLinkExtractor(), Arrays.asList(descriptors),
attributes);
}
/**
@@ -98,8 +98,8 @@ public abstract class HypermediaDocumentation {
*/
public static Snippet links(LinkExtractor linkExtractor,
Map<String, Object> attributes, LinkDescriptor... descriptors) {
return new LinksSnippet(linkExtractor, attributes,
Arrays.asList(descriptors));
return new LinksSnippet(linkExtractor, Arrays.asList(descriptors),
attributes);
}
/**

View File

@@ -37,8 +37,12 @@ import org.springframework.util.Assert;
* A {@link Snippet} that documents a RESTful resource's links.
*
* @author Andy Wilkinson
* @see HypermediaDocumentation#links(LinkDescriptor...)
* @see HypermediaDocumentation#links(LinkExtractor, LinkDescriptor...)
* @see HypermediaDocumentation#links(Map, LinkDescriptor...)
* @see HypermediaDocumentation#links(LinkExtractor, Map, LinkDescriptor...)
*/
class LinksSnippet extends TemplatedSnippet {
public class LinksSnippet extends TemplatedSnippet {
private final Map<String, LinkDescriptor> descriptorsByRel = new LinkedHashMap<>();
@@ -46,12 +50,28 @@ class LinksSnippet extends TemplatedSnippet {
private final LinkExtractor linkExtractor;
LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
this(linkExtractor, null, descriptors);
/**
* Creates a new {@code LinksSnippet} that will extract links using the given
* {@code linkExtractor} and document them using the given {@code descriptors}.
*
* @param linkExtractor the link extractor
* @param descriptors the link descriptors
*/
protected LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors) {
this(linkExtractor, descriptors, null);
}
LinksSnippet(LinkExtractor linkExtractor, Map<String, Object> attributes,
List<LinkDescriptor> descriptors) {
/**
* 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.
*
* @param linkExtractor the link extractor
* @param descriptors the link descriptors
* @param attributes the additional attributes
*/
protected LinksSnippet(LinkExtractor linkExtractor, List<LinkDescriptor> descriptors,
Map<String, Object> attributes) {
super("links", attributes);
this.linkExtractor = linkExtractor;
for (LinkDescriptor descriptor : descriptors) {

View File

@@ -30,8 +30,8 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link TemplatedSnippet} that produces a snippet documenting a RESTful resource's
* request or response fields.
* Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that
* document a RESTful resource's request or response fields.
*
* @author Andreas Evers
* @author Andy Wilkinson
@@ -40,8 +40,18 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
private List<FieldDescriptor> fieldDescriptors;
AbstractFieldsSnippet(String type, Map<String, Object> attributes,
List<FieldDescriptor> descriptors) {
/**
* 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.
*
* @param type the type of the fields
* @param descriptors the field descriptors
* @param attributes the additional attributes
*/
protected AbstractFieldsSnippet(String type, List<FieldDescriptor> descriptors,
Map<String, Object> attributes) {
super(type + "-fields", attributes);
for (FieldDescriptor descriptor : descriptors) {
Assert.notNull(descriptor.getPath());
@@ -106,8 +116,23 @@ public abstract class AbstractFieldsSnippet extends TemplatedSnippet {
}
}
/**
* Returns the content type of the request or response extracted from the given
* {@code operation}.
*
* @param operation The operation
* @return The content type
*/
protected abstract MediaType getContentType(Operation operation);
/**
* Returns the content of the request or response extracted form the given
* {@code operation}.
*
* @param operation The operation
* @return The content
* @throws IOException if the content cannot be extracted
*/
protected abstract byte[] getContent(Operation operation) throws IOException;
}

View File

@@ -136,7 +136,7 @@ public abstract class PayloadDocumentation {
*/
public static Snippet requestFields(Map<String, Object> attributes,
FieldDescriptor... descriptors) {
return new RequestFieldsSnippet(attributes, Arrays.asList(descriptors));
return new RequestFieldsSnippet(Arrays.asList(descriptors), attributes);
}
/**
@@ -177,7 +177,7 @@ public abstract class PayloadDocumentation {
*/
public static Snippet responseFields(Map<String, Object> attributes,
FieldDescriptor... descriptors) {
return new ResponseFieldsSnippet(attributes, Arrays.asList(descriptors));
return new ResponseFieldsSnippet(Arrays.asList(descriptors), attributes);
}
}

View File

@@ -28,15 +28,32 @@ import org.springframework.restdocs.snippet.Snippet;
* A {@link Snippet} that documents the fields in a request.
*
* @author Andy Wilkinson
* @see PayloadDocumentation#requestFields(FieldDescriptor...)
* @see PayloadDocumentation#requestFields(Map, FieldDescriptor...)
*/
class RequestFieldsSnippet extends AbstractFieldsSnippet {
public class RequestFieldsSnippet extends AbstractFieldsSnippet {
RequestFieldsSnippet(List<FieldDescriptor> descriptors) {
this(null, descriptors);
/**
* Creates a new {@code RequestFieldsSnippet} that will document the fields in the
* request using the given {@code descriptors}.
*
* @param descriptors the descriptors
*/
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors) {
this(descriptors, null);
}
RequestFieldsSnippet(Map<String, Object> attributes, List<FieldDescriptor> descriptors) {
super("request", attributes, descriptors);
/**
* 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.
*
* @param descriptors the descriptors
* @param attributes the additional attributes
*/
protected RequestFieldsSnippet(List<FieldDescriptor> descriptors,
Map<String, Object> attributes) {
super("request", descriptors, attributes);
}
@Override

View File

@@ -27,16 +27,32 @@ import org.springframework.restdocs.snippet.Snippet;
* A {@link Snippet} that documents the fields in a response.
*
* @author Andy Wilkinson
* @see PayloadDocumentation#responseFields(FieldDescriptor...)
* @see PayloadDocumentation#responseFields(Map, FieldDescriptor...)
*/
class ResponseFieldsSnippet extends AbstractFieldsSnippet {
public class ResponseFieldsSnippet extends AbstractFieldsSnippet {
ResponseFieldsSnippet(List<FieldDescriptor> descriptors) {
this(null, descriptors);
/**
* Creates a new {@code ResponseFieldsSnippet} that will document the fields in the
* response using the given {@code descriptors}.
*
* @param descriptors the descriptors
*/
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors) {
this(descriptors, null);
}
ResponseFieldsSnippet(Map<String, Object> attributes,
List<FieldDescriptor> descriptors) {
super("response", attributes, descriptors);
/**
* 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.
*
* @param descriptors the descriptors
* @param attributes the additional attributes
*/
protected ResponseFieldsSnippet(List<FieldDescriptor> descriptors,
Map<String, Object> attributes) {
super("response", descriptors, attributes);
}
@Override

View File

@@ -30,12 +30,29 @@ import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.Assert;
abstract class AbstractParametersSnippet extends TemplatedSnippet {
/**
* Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that
* document parameters from a request sent to a RESTful resource.
*
* @author Andreas Evers
* @author Andy Wilkinson
*/
public abstract class AbstractParametersSnippet extends TemplatedSnippet {
private final Map<String, ParameterDescriptor> descriptorsByName = new LinkedHashMap<>();
/**
* 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.
*
* @param snippetName The snippet name
* @param descriptors The descriptors
* @param attributes The additional attributes
*/
protected AbstractParametersSnippet(String snippetName,
Map<String, Object> attributes, List<ParameterDescriptor> descriptors) {
List<ParameterDescriptor> descriptors, Map<String, Object> attributes) {
super(snippetName, attributes);
for (ParameterDescriptor descriptor : descriptors) {
Assert.hasText(descriptor.getName());

View File

@@ -33,28 +33,44 @@ import org.springframework.util.Assert;
* A {@link Snippet} that documents the path parameters supported by a RESTful resource.
*
* @author Andy Wilkinson
* @see RequestDocumentation#pathParameters(ParameterDescriptor...)
* @see RequestDocumentation#pathParameters(Map, ParameterDescriptor...)
*/
class PathParametersSnippet extends AbstractParametersSnippet {
public class PathParametersSnippet extends AbstractParametersSnippet {
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
PathParametersSnippet(List<ParameterDescriptor> descriptors) {
this(null, descriptors);
/**
* Creates a new {@code PathParametersSnippet} that will document the request's path
* parameters using the given {@code descriptors}.
*
* @param descriptors the parameter descriptors
*/
protected PathParametersSnippet(List<ParameterDescriptor> descriptors) {
this(descriptors, null);
}
PathParametersSnippet(Map<String, Object> attributes,
List<ParameterDescriptor> descriptors) {
super("path-parameters", attributes, descriptors);
/**
* 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.
*
* @param descriptors the parameter descriptors
* @param attributes the additional attributes
*/
protected PathParametersSnippet(List<ParameterDescriptor> descriptors,
Map<String, Object> attributes) {
super("path-parameters", descriptors, attributes);
}
@Override
protected Map<String, Object> createModel(Operation operation) throws IOException {
Map<String, Object> model = super.createModel(operation);
model.put("path", remoteQueryStringIfPresent(extractUrlTemplate(operation)));
model.put("path", removeQueryStringIfPresent(extractUrlTemplate(operation)));
return model;
}
private String remoteQueryStringIfPresent(String urlTemplate) {
private String removeQueryStringIfPresent(String urlTemplate) {
int index = urlTemplate.indexOf('?');
if (index == -1) {
return urlTemplate;

View File

@@ -71,7 +71,7 @@ public abstract class RequestDocumentation {
*/
public static Snippet pathParameters(Map<String, Object> attributes,
ParameterDescriptor... descriptors) {
return new PathParametersSnippet(attributes, Arrays.asList(descriptors));
return new PathParametersSnippet(Arrays.asList(descriptors), attributes);
}
/**
@@ -100,7 +100,7 @@ public abstract class RequestDocumentation {
*/
public static Snippet requestParameters(Map<String, Object> attributes,
ParameterDescriptor... descriptors) {
return new RequestParametersSnippet(attributes, Arrays.asList(descriptors));
return new RequestParametersSnippet(Arrays.asList(descriptors), attributes);
}
}

View File

@@ -20,32 +20,45 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletRequest;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.SnippetException;
import org.springframework.web.bind.annotation.RequestParam;
/**
* A {@link Snippet} that documents the request parameters supported by a RESTful
* resource.
* <p>
* Request parameters are sent as part of the query string or as posted from data.
* Request parameters are sent as part of the query string or as POSTed form data.
*
* @author Andy Wilkinson
* @see ServletRequest#getParameterMap()
* @see RequestParam
* @see OperationRequest#getParameters()
* @see RequestDocumentation#requestParameters(ParameterDescriptor...)
* @see RequestDocumentation#requestParameters(Map, ParameterDescriptor...)
*/
class RequestParametersSnippet extends AbstractParametersSnippet {
public class RequestParametersSnippet extends AbstractParametersSnippet {
RequestParametersSnippet(List<ParameterDescriptor> descriptors) {
this(null, descriptors);
/**
* Creates a new {@code RequestParametersSnippet} that will document the request's
* parameters using the given {@code descriptors}.
*
* @param descriptors the parameter descriptors
*/
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors) {
this(descriptors, null);
}
RequestParametersSnippet(Map<String, Object> attributes,
List<ParameterDescriptor> descriptors) {
super("request-parameters", attributes, descriptors);
/**
* 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.
*
* @param descriptors the parameter descriptors
* @param attributes the additional attributes
*/
protected RequestParametersSnippet(List<ParameterDescriptor> descriptors,
Map<String, Object> attributes) {
super("request-parameters", descriptors, attributes);
}
@Override

View File

@@ -38,6 +38,14 @@ public abstract class TemplatedSnippet implements Snippet {
private final String snippetName;
/**
* Creates a new {@code TemplatedSnippet} that will produce a snippet with the given
* {@code snippetName}. The given {@code attributes} will be included in the model
* during rendering of the template.
*
* @param snippetName The name of the snippet
* @param attributes The additional attributes
*/
protected TemplatedSnippet(String snippetName, Map<String, Object> attributes) {
this.snippetName = snippetName;
if (attributes != null) {

View File

@@ -1,4 +1,4 @@
[source,bash]
----
$ curl {{arguments}}
$ curl {{url}} {{options}}
----