diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index edac1e2d..3915fecb 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -283,39 +283,52 @@ override the template for the `curl-request.adoc` snippet, create a template nam [[documenting-your-api-customizing-including-extra-information]] ==== Including extra information -The descriptors for fields, links, and query parameters all have an `attribute` method -that can be used to associate one or more key-value pairs with the descriptor. These -attributes are made available during the template rendering process. Coupled with -a custom snippet template, this makes it possible to include extra information in a -generated snippet. -A concrete example of the above is the addition of a constraints column when documenting -request fields. The first step is to provide a `constraints` attribute for each field that -you are documenting: +There are two ways to provide extra information for inclusion in a generated snippet: + +. Use the `attributes` method on a field, link or query parameter descriptor to add one or + more attributes to an individual descriptor +. Pass in some attributes when calling `withCurlRequest`, `withHttpRequest`, + `withHttpResponse`, etc on `RestDocumentationResultHandler`. Such attributes will be + associated with the snippet as a whole. + +Any additional attributes are made available during the template rendering process. +Coupled with a custom snippet template, this makes it possible to include extra +information in a generated snippet. + +A concrete example of the above is the addition of a constraints column and a title when +documenting request fields. The first step is to provide a `constraints` attribute for +each field that you are documenting and to provide a `title` attribute: [source,java,indent=0] ---- include::{examples-dir}/com/example/Payload.java[tags=constraints] ---- +<1> Configure the `title` attribute for the request fields snippet +<2> Set the `constraints` attribute for the `name` field +<3> Set the `constraints` attribute for the `email` field The second step is to provide a custom template named `request-fields.snippet` that -includes the information about the fields' constraints in the generated snippet's table: +includes the information about the fields' constraints in the generated snippet's table +and adds a title: [source,indent=0] ---- + .{{title}} <1> |=== - |Path|Type|Description|Constraints <1> + |Path|Type|Description|Constraints <2> {{#fields}} |{{path}} |{{type}} |{{description}} - |{{constraints}} <2> + |{{constraints}} <3> {{/fields}} |=== ---- -<1> Add a new column named "Constraints" -<2> Include the descriptors' `constraints` attribute in each row of the table +<1> Add a title to the table +<2> Add a new column named "Constraints" +<3> Include the descriptors' `constraints` attribute in each row of the table diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java index 7fcd725a..04c32b36 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/Payload.java @@ -17,12 +17,16 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.restdocs.Attributes.key; import org.springframework.http.MediaType; +import org.springframework.restdocs.Attributes; import org.springframework.restdocs.payload.FieldType; import org.springframework.test.web.servlet.MockMvc; @@ -57,12 +61,16 @@ private MockMvc mockMvc; .andExpect(status().isOk()) // tag::constraints[] .andDo(document("create-user").withRequestFields( + attributes( + key("title").value("Fields for user creation")), // <1> fieldWithPath("name") .description("The user's name") - .attribute("constraints", "Must not be null. Must not be empty"), + .attributes( + key("constraints").value("Must not be null. Must not be empty")), // <2> fieldWithPath("email") .description("The user's email address") - .attribute("constrains", "Must be a valid email address"))); + .attributes( + key("constraints").value("Must be a valid email address")))); // <3> // end::constraints[] } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/AbstractDescriptor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/AbstractDescriptor.java index c87637ff..70150bff 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/AbstractDescriptor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/AbstractDescriptor.java @@ -3,6 +3,8 @@ package org.springframework.restdocs; import java.util.HashMap; import java.util.Map; +import org.springframework.restdocs.Attributes.Attribute; + /** * Base class for descriptors. Provides the ability to associate arbitrary attributes with * a descriptor. @@ -13,17 +15,19 @@ import java.util.Map; */ public abstract class AbstractDescriptor> { - private final Map attributes = new HashMap<>(); + private Map attributes = new HashMap<>(); /** - * Sets an attribute with the given {@code name} and {@code value}. + * Sets the descriptor's attributes * - * @param name The name of the attribute - * @param value The value of the attribute + * @param attributes the attributes + * @return the descriptor */ @SuppressWarnings("unchecked") - public T attribute(String name, Object value) { - this.attributes.put(name, value); + public T attributes(Attribute... attributes) { + for (Attribute attribute : attributes) { + this.attributes.put(attribute.getKey(), attribute.getValue()); + } return (T) this; } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/Attributes.java b/spring-restdocs/src/main/java/org/springframework/restdocs/Attributes.java new file mode 100644 index 00000000..0ee204ab --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/Attributes.java @@ -0,0 +1,105 @@ +/* + * Copyright 2014-2015 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; + +import java.util.HashMap; +import java.util.Map; + +/** + * A fluent API for building a map of attributes + * + * @author Andy Wilkinson + */ +public abstract class Attributes { + + private Attributes() { + + } + + /** + * Creates an attribute with the given {@code key}. A value for the attribute must + * still be specified. + * + * @param key The key of the attribute + * @return An {@code AttributeBuilder} to use to specify the value of the attribute + * @see AttributeBuilder#value(Object) + */ + public static AttributeBuilder key(String key) { + return new AttributeBuilder(key); + } + + /** + * Creates a {@code Map} of the given {@code attributes}. + * + * @param attributes The attributes + * @return A Map of the attributes + */ + public static Map attributes(Attribute... attributes) { + Map attributeMap = new HashMap<>(); + for (Attribute attribute : attributes) { + attributeMap.put(attribute.getKey(), attribute.getValue()); + } + return attributeMap; + } + + /** + * A simple builder for an attribute (key-value pair) + */ + public static class AttributeBuilder { + + private final String key; + + private AttributeBuilder(String key) { + this.key = key; + } + + /** + * Configures the value of the attribute + * + * @param value The attribute's value + * @return A newly created {@code Attribute} + */ + public Attribute value(Object value) { + return new Attribute(this.key, value); + } + } + + /** + * An attribute (key-value pair). + * + * @author Andy Wilkinson + */ + public static class Attribute { + + private final String key; + + private final Object value; + + public Attribute(String key, Object value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return this.key; + } + + public Object getValue() { + return this.value; + } + + } +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java index 203d3152..6d26ba63 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -26,6 +26,7 @@ import static org.springframework.restdocs.request.RequestDocumentation.document import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.springframework.restdocs.hypermedia.HypermediaDocumentation; import org.springframework.restdocs.hypermedia.LinkDescriptor; @@ -35,6 +36,7 @@ import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.PayloadDocumentation; import org.springframework.restdocs.request.ParameterDescriptor; import org.springframework.restdocs.request.RequestDocumentation; +import org.springframework.restdocs.snippet.SnippetWritingResultHandler; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; @@ -49,15 +51,55 @@ public class RestDocumentationResultHandler implements ResultHandler { private final String outputDir; - private List delegates = new ArrayList<>(); + private SnippetWritingResultHandler curlRequest; + + private SnippetWritingResultHandler httpRequest; + + private SnippetWritingResultHandler httpResponse; + + private List delegates = new ArrayList<>(); RestDocumentationResultHandler(String outputDir) { this.outputDir = outputDir; + this.curlRequest = documentCurlRequest(this.outputDir, null); + this.httpRequest = documentHttpRequest(this.outputDir, null); + this.httpResponse = documentHttpResponse(this.outputDir, null); + } - this.delegates = new ArrayList(); - this.delegates.add(documentCurlRequest(this.outputDir)); - this.delegates.add(documentHttpRequest(this.outputDir)); - this.delegates.add(documentHttpResponse(this.outputDir)); + /** + * Customizes the default curl request snippet generation to make the given attributes + * available. + * + * @param attributes the attributes + * @return {@code this} + */ + public RestDocumentationResultHandler withCurlRequest(Map attributes) { + this.curlRequest = documentCurlRequest(this.outputDir, attributes); + return this; + } + + /** + * Customizes the default HTTP request snippet generation to make the given attributes + * available. + * + * @param attributes the attributes + * @return {@code this} + */ + public RestDocumentationResultHandler withHttpRequest(Map attributes) { + this.httpRequest = documentHttpRequest(this.outputDir, attributes); + return this; + } + + /** + * Customizes the default HTTP response snippet generation to make the given + * attributes available. + * + * @param attributes the attributes + * @return {@code this} + */ + public RestDocumentationResultHandler withHttpResponse(Map attributes) { + this.httpResponse = documentHttpResponse(this.outputDir, attributes); + return this; } /** @@ -75,7 +117,7 @@ public class RestDocumentationResultHandler implements ResultHandler { * @see LinkExtractors#extractorForContentType(String) */ public RestDocumentationResultHandler withLinks(LinkDescriptor... descriptors) { - return withLinks(null, descriptors); + return withLinks(null, null, descriptors); } /** @@ -94,7 +136,50 @@ public class RestDocumentationResultHandler implements ResultHandler { */ public RestDocumentationResultHandler withLinks(LinkExtractor linkExtractor, LinkDescriptor... descriptors) { - this.delegates.add(documentLinks(this.outputDir, linkExtractor, descriptors)); + return this.withLinks(null, linkExtractor, descriptors); + } + + /** + * Document the links in the response using the given {@code descriptors}. The links + * are extracted from the response based on its content type. The given + * {@code attributes} are made available during the generation of the links snippet. + *

+ * If a link is present in the response but is not described by one of the descriptors + * a failure will occur when this handler is invoked. Similarly, if a link is + * described but is not present in the response a failure will also occur when this + * handler is invoked. + * + * @param attributes the attributes + * @param descriptors the link descriptors + * @return {@code this} + * @see HypermediaDocumentation#linkWithRel(String) + * @see LinkExtractors#extractorForContentType(String) + */ + public RestDocumentationResultHandler withLinks(Map attributes, + LinkDescriptor... descriptors) { + return withLinks(attributes, null, descriptors); + } + + /** + * Document the links in the response using the given {@code descriptors}. The links + * are extracted from the response using the given {@code linkExtractor}. The given + * {@code attributes} are made available during the generation of the links snippet. + *

+ * If a link is present in the response but is not described by one of the descriptors + * a failure will occur when this handler is invoked. Similarly, if a link is + * described but is not present in the response a failure will also occur when this + * handler is invoked. + * + * @param attributes the attributes + * @param linkExtractor used to extract the links from the response + * @param descriptors the link descriptors + * @return {@code this} + * @see HypermediaDocumentation#linkWithRel(String) + */ + public RestDocumentationResultHandler withLinks(Map attributes, + LinkExtractor linkExtractor, LinkDescriptor... descriptors) { + this.delegates.add(documentLinks(this.outputDir, attributes, linkExtractor, + descriptors)); return this; } @@ -114,7 +199,30 @@ public class RestDocumentationResultHandler implements ResultHandler { */ public RestDocumentationResultHandler withRequestFields( FieldDescriptor... descriptors) { - this.delegates.add(documentRequestFields(this.outputDir, descriptors)); + return this.withRequestFields(null, descriptors); + } + + /** + * Document the fields in the request using the given {@code descriptors}. The given + * {@code attributes} are made available during the generation of the request fields + * snippet. + *

+ * If a field is present in the request but is not documented by one of the + * descriptors a failure will occur when this handler 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. + * + * @param descriptors the link descriptors + * @param attributes the attributes + * @return {@code this} + * @see PayloadDocumentation#fieldWithPath(String) + */ + public RestDocumentationResultHandler withRequestFields( + Map attributes, FieldDescriptor... descriptors) { + this.delegates + .add(documentRequestFields(this.outputDir, attributes, descriptors)); return this; } @@ -134,7 +242,30 @@ public class RestDocumentationResultHandler implements ResultHandler { */ public RestDocumentationResultHandler withResponseFields( FieldDescriptor... descriptors) { - this.delegates.add(documentResponseFields(this.outputDir, descriptors)); + return this.withResponseFields(null, descriptors); + } + + /** + * Document the fields in the response using the given {@code descriptors}. The given + * {@code attributes} are made available during the generation of the request fields + * snippet. + *

+ * If a field is present in the response but is not documented by one of the + * descriptors a failure will occur when this handler is invoked. Similarly, if a + * 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 is sufficient for all of its descendants to also be treated as having been + * documented. + * + * @param descriptors the link descriptors + * @param attributes the attributes + * @return {@code this} + * @see PayloadDocumentation#fieldWithPath(String) + */ + public RestDocumentationResultHandler withResponseFields( + Map attributes, FieldDescriptor... descriptors) { + this.delegates + .add(documentResponseFields(this.outputDir, attributes, descriptors)); return this; } @@ -153,12 +284,36 @@ public class RestDocumentationResultHandler implements ResultHandler { */ public RestDocumentationResultHandler withQueryParameters( ParameterDescriptor... descriptors) { - this.delegates.add(documentQueryParameters(this.outputDir, descriptors)); + return this.withQueryParameters(null, descriptors); + } + + /** + * Documents the parameters in the request's query string using the given + * {@code descriptors}. The given {@code attributes} are made available during the + * generation of the query parameters snippet. + *

+ * If a parameter is present in the query string but is not described by one of the + * descriptors a failure will occur when this handler is invoked. Similarly, if a + * parameter is described but is not present in the request a failure will also occur + * when this handler is invoked. + * + * @param descriptors the parameter descriptors + * @param attributes the attributes + * @return {@code this} + * @see RequestDocumentation#parameterWithName(String) + */ + public RestDocumentationResultHandler withQueryParameters( + Map attributes, ParameterDescriptor... descriptors) { + this.delegates.add(documentQueryParameters(this.outputDir, attributes, + descriptors)); return this; } @Override public void handle(MvcResult result) throws Exception { + this.curlRequest.handle(result); + this.httpRequest.handle(result); + this.httpResponse.handle(result); for (ResultHandler delegate : this.delegates) { delegate.handle(result); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java index e3161c52..de7ac531 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java @@ -50,10 +50,13 @@ public abstract class CurlDocumentation { * Produces a documentation snippet containing the request formatted as a cURL command * * @param outputDir The directory to which snippet should be written + * @param attributes Attributes made available during rendering of the curl request + * snippet * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentCurlRequest(String outputDir) { - return new CurlRequestWritingResultHandler(outputDir); + public static SnippetWritingResultHandler documentCurlRequest(String outputDir, + Map attributes) { + return new CurlRequestWritingResultHandler(outputDir, attributes); } private static final class CurlRequestWritingResultHandler extends @@ -67,15 +70,16 @@ public abstract class CurlDocumentation { private static final int STANDARD_PORT_HTTPS = 443; - private CurlRequestWritingResultHandler(String outputDir) { - super(outputDir, "curl-request"); + private CurlRequestWritingResultHandler(String outputDir, + Map attributes) { + super(outputDir, "curl-request", attributes); } @Override public void handle(MvcResult result, PrintWriter writer) throws IOException { Map context = new HashMap(); - context.put("language", "bash"); context.put("arguments", getCurlCommandArguments(result)); + context.putAll(getAttributes()); TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java index 2f7448fd..44690f81 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/http/HttpDocumentation.java @@ -54,10 +54,13 @@ public abstract class HttpDocumentation { * request * * @param outputDir The directory to which snippet should be written + * @param attributes Attributes made available during rendering of the HTTP requst + * snippet * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentHttpRequest(String outputDir) { - return new HttpRequestWritingResultHandler(outputDir); + public static SnippetWritingResultHandler documentHttpRequest(String outputDir, + Map attributes) { + return new HttpRequestWritingResultHandler(outputDir, attributes); } /** @@ -65,18 +68,22 @@ public abstract class HttpDocumentation { * response sent by the server * * @param outputDir The directory to which snippet should be written + * @param attributes Attributes made available during rendering of the HTTP response + * snippet * @return the handler that will produce the snippet */ - public static SnippetWritingResultHandler documentHttpResponse(String outputDir) { - return new HttpResponseWritingResultHandler(outputDir); + public static SnippetWritingResultHandler documentHttpResponse(String outputDir, + Map attributes) { + return new HttpResponseWritingResultHandler(outputDir, attributes); } private static final class HttpRequestWritingResultHandler extends SnippetWritingResultHandler { - private HttpRequestWritingResultHandler(String outputDir) { - super(outputDir, "http-request"); + private HttpRequestWritingResultHandler(String outputDir, + Map attributes) { + super(outputDir, "http-request", attributes); } @Override @@ -84,11 +91,11 @@ public abstract class HttpDocumentation { DocumentableHttpServletRequest request = new DocumentableHttpServletRequest( result.getRequest()); Map context = new HashMap(); - context.put("language", "http"); context.put("method", result.getRequest().getMethod()); context.put("path", request.getRequestUriWithQueryString()); context.put("headers", getHeaders(request)); context.put("requestBody", getRequestBody(request)); + context.putAll(getAttributes()); TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); @@ -212,8 +219,9 @@ public abstract class HttpDocumentation { private static final class HttpResponseWritingResultHandler extends SnippetWritingResultHandler { - private HttpResponseWritingResultHandler(String outputDir) { - super(outputDir, "http-response"); + private HttpResponseWritingResultHandler(String outputDir, + Map attributes) { + super(outputDir, "http-response", attributes); } @Override @@ -225,7 +233,6 @@ public abstract class HttpDocumentation { StringUtils.hasLength(result.getResponse().getContentAsString()) ? String .format("%n%s", result.getResponse().getContentAsString()) : ""); - context.put("language", "http"); context.put("statusCode", status.value()); context.put("statusReason", status.getReasonPhrase()); @@ -238,6 +245,8 @@ public abstract class HttpDocumentation { } } + context.putAll(getAttributes()); + TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java index 2393fa62..9f919974 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/HypermediaDocumentation.java @@ -17,6 +17,7 @@ package org.springframework.restdocs.hypermedia; import java.util.Arrays; +import java.util.Map; import org.springframework.restdocs.RestDocumentationResultHandler; @@ -48,6 +49,7 @@ public abstract class HypermediaDocumentation { * snippet for a response's links. * * @param outputDir The directory to which the snippet should be written + * @param attributes Attributes made available during rendering of the links snippet * @param linkExtractor Used to extract the links from the response * @param descriptors The descriptions of the response's links * @return the handler @@ -55,8 +57,9 @@ public abstract class HypermediaDocumentation { * @see RestDocumentationResultHandler#withLinks(LinkExtractor, LinkDescriptor...) */ public static LinkSnippetResultHandler documentLinks(String outputDir, - LinkExtractor linkExtractor, LinkDescriptor... descriptors) { - return new LinkSnippetResultHandler(outputDir, linkExtractor, + Map attributes, LinkExtractor linkExtractor, + LinkDescriptor... descriptors) { + return new LinkSnippetResultHandler(outputDir, attributes, linkExtractor, Arrays.asList(descriptors)); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java index 06b239dd..ecc78dbb 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkSnippetResultHandler.java @@ -43,13 +43,13 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { private final Map descriptorsByRel = new LinkedHashMap<>(); - private final Set requiredRels = new HashSet(); + private final Set requiredRels = new HashSet<>(); private final LinkExtractor extractor; - LinkSnippetResultHandler(String outputDir, LinkExtractor linkExtractor, - List descriptors) { - super(outputDir, "links"); + LinkSnippetResultHandler(String outputDir, Map attributes, + LinkExtractor linkExtractor, List descriptors) { + super(outputDir, "links", attributes); this.extractor = linkExtractor; for (LinkDescriptor descriptor : descriptors) { Assert.hasText(descriptor.getRel()); @@ -117,6 +117,7 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { .getAttribute(TemplateEngine.class.getName()); Map context = new HashMap<>(); context.put("links", createLinksModel()); + context.putAll(getAttributes()); writer.print(templateEngine.compileTemplate("links").render(context)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java index 3ad66c08..bb08851b 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldSnippetResultHandler.java @@ -55,8 +55,8 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand private List fieldDescriptors; FieldSnippetResultHandler(String outputDir, String type, - List descriptors) { - super(outputDir, type + "-fields"); + Map attributes, List descriptors) { + super(outputDir, type + "-fields", attributes); this.templateName = type + "-fields"; for (FieldDescriptor descriptor : descriptors) { Assert.notNull(descriptor.getPath()); @@ -83,6 +83,7 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand } fields.add(descriptor.toModel()); } + context.putAll(getAttributes()); TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); writer.print(templateEngine.compileTemplate(this.templateName).render(context)); diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java index 2b39a1b2..e49a5972 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/PayloadDocumentation.java @@ -17,6 +17,7 @@ package org.springframework.restdocs.payload; import java.util.Arrays; +import java.util.Map; import org.springframework.restdocs.RestDocumentationResultHandler; @@ -107,14 +108,16 @@ public abstract class PayloadDocumentation { * documented. * * @param outputDir The directory to which the snippet should be written + * @param attributes Attributes made available during rendering of the links snippet * @param descriptors The descriptions of the request's fields * @return the handler * @see RestDocumentationResultHandler#withRequestFields(FieldDescriptor...) * @see #fieldWithPath(String) */ public static FieldSnippetResultHandler documentRequestFields(String outputDir, - FieldDescriptor... descriptors) { - return new RequestFieldSnippetResultHandler(outputDir, Arrays.asList(descriptors)); + Map attributes, FieldDescriptor... descriptors) { + return new RequestFieldSnippetResultHandler(outputDir, attributes, + Arrays.asList(descriptors)); } /** @@ -129,13 +132,14 @@ public abstract class PayloadDocumentation { * documented. * * @param outputDir The directory to which the snippet should be written + * @param attributes Attributes made available during rendering of the links snippet * @param descriptors The descriptions of the response's fields * @return the handler * @see RestDocumentationResultHandler#withResponseFields(FieldDescriptor...) */ public static FieldSnippetResultHandler documentResponseFields(String outputDir, - FieldDescriptor... descriptors) { - return new ResponseFieldSnippetResultHandler(outputDir, + Map attributes, FieldDescriptor... descriptors) { + return new ResponseFieldSnippetResultHandler(outputDir, attributes, Arrays.asList(descriptors)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java index b589ef8b..aad0f6e0 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/RequestFieldSnippetResultHandler.java @@ -18,6 +18,7 @@ package org.springframework.restdocs.payload; import java.io.IOException; import java.io.Reader; import java.util.List; +import java.util.Map; import org.springframework.test.web.servlet.MvcResult; @@ -28,8 +29,9 @@ import org.springframework.test.web.servlet.MvcResult; */ public class RequestFieldSnippetResultHandler extends FieldSnippetResultHandler { - RequestFieldSnippetResultHandler(String outputDir, List descriptors) { - super(outputDir, "request", descriptors); + RequestFieldSnippetResultHandler(String outputDir, Map attributes, + List descriptors) { + super(outputDir, "request", attributes, descriptors); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java index af8e3d1f..76ff2c79 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/ResponseFieldSnippetResultHandler.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.List; +import java.util.Map; import org.springframework.test.web.servlet.MvcResult; @@ -29,8 +30,9 @@ import org.springframework.test.web.servlet.MvcResult; */ public class ResponseFieldSnippetResultHandler extends FieldSnippetResultHandler { - ResponseFieldSnippetResultHandler(String outputDir, List descriptors) { - super(outputDir, "response", descriptors); + ResponseFieldSnippetResultHandler(String outputDir, Map attributes, + List descriptors) { + super(outputDir, "response", attributes, descriptors); } @Override diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java index 661e80ff..bbbb3e70 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java @@ -44,8 +44,8 @@ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHan private final Map descriptorsByName = new LinkedHashMap<>(); protected QueryParametersSnippetResultHandler(String outputDir, - ParameterDescriptor... descriptors) { - super(outputDir, "query-parameters"); + Map attributes, ParameterDescriptor... descriptors) { + super(outputDir, "query-parameters", attributes); for (ParameterDescriptor descriptor : descriptors) { Assert.hasText(descriptor.getName()); Assert.hasText(descriptor.getDescription()); @@ -98,6 +98,7 @@ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHan parameters.add(entry.getValue().toModel()); } context.put("parameters", parameters); + context.putAll(getAttributes()); writer.print(templateEngine.compileTemplate("query-parameters").render(context)); } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java index cef76d16..6fc061c7 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java @@ -16,6 +16,8 @@ package org.springframework.restdocs.request; +import java.util.Map; + import org.springframework.restdocs.RestDocumentationResultHandler; import org.springframework.restdocs.snippet.SnippetWritingResultHandler; @@ -35,13 +37,15 @@ public abstract class RequestDocumentation { * documenting a request's query parameters * * @param outputDir The directory to which the snippet should be written + * @param attributes Attributes made available during rendering of the query + * parameters snippet * @param descriptors The descriptions of the parameters in the request's query string * @return the result handler * @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...) */ public static SnippetWritingResultHandler documentQueryParameters(String outputDir, - ParameterDescriptor... descriptors) { - return new QueryParametersSnippetResultHandler(outputDir, descriptors); + Map attributes, ParameterDescriptor... descriptors) { + return new QueryParametersSnippetResultHandler(outputDir, attributes, descriptors); } /** diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java index 1a1dc022..b81594a5 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/snippet/SnippetWritingResultHandler.java @@ -23,6 +23,8 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; +import java.util.HashMap; +import java.util.Map; import org.springframework.restdocs.config.RestDocumentationContext; import org.springframework.test.web.servlet.MvcResult; @@ -35,13 +37,19 @@ import org.springframework.test.web.servlet.ResultHandler; */ public abstract class SnippetWritingResultHandler implements ResultHandler { - private String outputDir; + private final Map attributes = new HashMap<>(); - private String fileName; + private final String outputDir; - protected SnippetWritingResultHandler(String outputDir, String fileName) { + private final String fileName; + + protected SnippetWritingResultHandler(String outputDir, String fileName, + Map attributes) { this.outputDir = outputDir; this.fileName = fileName; + if (attributes != null) { + this.attributes.putAll(attributes); + } } protected abstract void handle(MvcResult result, PrintWriter writer) @@ -54,6 +62,10 @@ public abstract class SnippetWritingResultHandler implements ResultHandler { } } + protected Map getAttributes() { + return this.attributes; + } + private Writer createWriter() throws IOException { File outputFile = new OutputFileResolver().resolve(this.outputDir, this.fileName + ".adoc"); diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java index 3a359ef2..74f70e44 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/curl/CurlDocumentationTests.java @@ -16,6 +16,11 @@ package org.springframework.restdocs.curl; +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.curl.CurlDocumentation.documentCurlRequest; import static org.springframework.restdocs.test.SnippetMatchers.codeBlock; import static org.springframework.restdocs.test.StubMvcResult.result; @@ -29,9 +34,13 @@ import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.core.io.FileSystemResource; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; /** @@ -54,14 +63,14 @@ public class CurlDocumentationTests { public void getRequest() throws IOException { this.snippet.expectCurlRequest("get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i")); - documentCurlRequest("get-request").handle(result(get("/foo"))); + documentCurlRequest("get-request", null).handle(result(get("/foo"))); } @Test public void nonGetRequest() throws IOException { this.snippet.expectCurlRequest("non-get-request").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo' -i -X POST")); - documentCurlRequest("non-get-request").handle(result(post("/foo"))); + documentCurlRequest("non-get-request", null).handle(result(post("/foo"))); } @Test @@ -69,7 +78,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("request-with-content").withContents( codeBlock("bash") .content("$ curl 'http://localhost/foo' -i -d 'content'")); - documentCurlRequest("request-with-content").handle( + documentCurlRequest("request-with-content", null).handle( result(get("/foo").content("content"))); } @@ -79,7 +88,7 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?param=value' -i")); - documentCurlRequest("request-with-query-string").handle( + documentCurlRequest("request-with-query-string", null).handle( result(get("/foo?param=value"))); } @@ -87,7 +96,7 @@ public class CurlDocumentationTests { public void requestWithOneParameter() throws IOException { this.snippet.expectCurlRequest("request-with-one-parameter").withContents( codeBlock("bash").content("$ curl 'http://localhost/foo?k1=v1' -i")); - documentCurlRequest("request-with-one-parameter").handle( + documentCurlRequest("request-with-one-parameter", null).handle( result(get("/foo").param("k1", "v1"))); } @@ -96,7 +105,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("request-with-multiple-parameters").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?k1=v1&k1=v1-bis&k2=v2' -i")); - documentCurlRequest("request-with-multiple-parameters").handle( + documentCurlRequest("request-with-multiple-parameters", null).handle( result(get("/foo").param("k1", "v1").param("k2", "v2") .param("k1", "v1-bis"))); } @@ -107,7 +116,7 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo?k1=foo+bar%26' -i")); - documentCurlRequest("request-with-url-encoded-parameter").handle( + documentCurlRequest("request-with-url-encoded-parameter", null).handle( result(get("/foo").param("k1", "foo bar&"))); } @@ -116,7 +125,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); - documentCurlRequest("post-request-with-one-parameter").handle( + documentCurlRequest("post-request-with-one-parameter", null).handle( result(post("/foo").param("k1", "v1"))); } @@ -127,7 +136,7 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - documentCurlRequest("post-request-with-multiple-parameters").handle( + documentCurlRequest("post-request-with-multiple-parameters", null).handle( result(post("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); } @@ -138,7 +147,7 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); - documentCurlRequest("post-request-with-url-encoded-parameter").handle( + documentCurlRequest("post-request-with-url-encoded-parameter", null).handle( result(post("/foo").param("k1", "a&b"))); } @@ -147,7 +156,7 @@ public class CurlDocumentationTests { this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); - documentCurlRequest("put-request-with-one-parameter").handle( + documentCurlRequest("put-request-with-one-parameter", null).handle( result(put("/foo").param("k1", "v1"))); } @@ -158,7 +167,7 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); - documentCurlRequest("put-request-with-multiple-parameters").handle( + documentCurlRequest("put-request-with-multiple-parameters", null).handle( result(put("/foo").param("k1", "v1", "v1-bis").param("k2", "v2"))); } @@ -168,7 +177,7 @@ public class CurlDocumentationTests { .withContents( codeBlock("bash").content( "$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); - documentCurlRequest("put-request-with-url-encoded-parameter").handle( + documentCurlRequest("put-request-with-url-encoded-parameter", null).handle( result(put("/foo").param("k1", "a&b"))); } @@ -178,7 +187,7 @@ public class CurlDocumentationTests { codeBlock("bash").content( "$ curl 'http://localhost/foo' -i" + " -H 'Content-Type: application/json' -H 'a: alpha'")); - documentCurlRequest("request-with-headers").handle( + documentCurlRequest("request-with-headers", null).handle( result(get("/foo").contentType(MediaType.APPLICATION_JSON).header("a", "alpha"))); } @@ -189,7 +198,7 @@ public class CurlDocumentationTests { codeBlock("bash").content("$ curl 'http://localhost:8080/foo' -i")); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(8080); - documentCurlRequest("http-with-non-standard-port").handle(result(request)); + documentCurlRequest("http-with-non-standard-port", null).handle(result(request)); } @Test @@ -199,7 +208,7 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(443); request.setScheme("https"); - documentCurlRequest("https-with-standard-port").handle(result(request)); + documentCurlRequest("https-with-standard-port", null).handle(result(request)); } @Test @@ -209,7 +218,7 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerPort(8443); request.setScheme("https"); - documentCurlRequest("https-with-non-standard-port").handle(result(request)); + documentCurlRequest("https-with-non-standard-port", null).handle(result(request)); } @Test @@ -218,7 +227,7 @@ public class CurlDocumentationTests { codeBlock("bash").content("$ curl 'http://api.example.com/foo' -i")); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); - documentCurlRequest("request-with-custom-host").handle(result(request)); + documentCurlRequest("request-with-custom-host", null).handle(result(request)); } @Test @@ -230,7 +239,7 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); request.setContextPath("/v3"); - documentCurlRequest("request-with-custom-context-with-slash").handle( + documentCurlRequest("request-with-custom-context-with-slash", null).handle( result(request)); } @@ -243,7 +252,7 @@ public class CurlDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); request.setContextPath("v3"); - documentCurlRequest("request-with-custom-context-without-slash").handle( + documentCurlRequest("request-with-custom-context-without-slash", null).handle( result(request)); } @@ -256,7 +265,7 @@ public class CurlDocumentationTests { .withContents(codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("metadata", "{\"description\": \"foo\"}".getBytes()); - documentCurlRequest("multipart-post-no-original-filename").handle( + documentCurlRequest("multipart-post-no-original-filename", null).handle( result(fileUpload("/upload").file(multipartFile))); } @@ -270,7 +279,7 @@ public class CurlDocumentationTests { MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, "bytes".getBytes()); - documentCurlRequest("multipart-post-with-content-type").handle( + documentCurlRequest("multipart-post-with-content-type", null).handle( result(fileUpload("/upload").file(multipartFile))); } @@ -283,7 +292,7 @@ public class CurlDocumentationTests { codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "bytes".getBytes()); - documentCurlRequest("multipart-post").handle( + documentCurlRequest("multipart-post", null).handle( result(fileUpload("/upload").file(multipartFile))); } @@ -297,9 +306,26 @@ public class CurlDocumentationTests { codeBlock("bash").content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "bytes".getBytes()); - documentCurlRequest("multipart-post").handle( + documentCurlRequest("multipart-post", null).handle( result(fileUpload("/upload").file(multipartFile) .param("a", "apple", "avocado").param("b", "banana"))); } + @Test + public void customAttributes() throws IOException { + this.snippet.expectCurlRequest("custom-attributes").withContents( + containsString("curl request title")); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("curl-request")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/curl-request-with-title.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + documentCurlRequest("custom-attributes", + attributes(key("title").value("curl request title"))).handle( + result(request)); + } + } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java index b2486e32..c1061284 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/http/HttpDocumentationTests.java @@ -16,8 +16,13 @@ package org.springframework.restdocs.http; +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.OK; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRequest; import static org.springframework.restdocs.http.HttpDocumentation.documentHttpResponse; import static org.springframework.restdocs.test.SnippetMatchers.httpRequest; @@ -35,11 +40,15 @@ import java.io.IOException; import org.junit.Rule; import org.junit.Test; +import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; import org.springframework.restdocs.test.ExpectedSnippet; /** @@ -61,7 +70,7 @@ public class HttpDocumentationTests { httpRequest(GET, "/foo").header(HttpHeaders.HOST, "localhost").header( "Alpha", "a")); - documentHttpRequest("get-request").handle( + documentHttpRequest("get-request", null).handle( result(get("/foo").header("Alpha", "a"))); } @@ -70,7 +79,7 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-with-query-string").withContents( httpRequest(GET, "/foo?bar=baz").header(HttpHeaders.HOST, "localhost")); - documentHttpRequest("get-request-with-query-string").handle( + documentHttpRequest("get-request-with-query-string", null).handle( result(get("/foo?bar=baz"))); } @@ -79,7 +88,7 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-with-parameter").withContents( httpRequest(GET, "/foo?b%26r=baz").header(HttpHeaders.HOST, "localhost")); - documentHttpRequest("get-request-with-parameter").handle( + documentHttpRequest("get-request-with-parameter", null).handle( result(get("/foo").param("b&r", "baz"))); } @@ -89,7 +98,7 @@ public class HttpDocumentationTests { httpRequest(POST, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - documentHttpRequest("post-request-with-content").handle( + documentHttpRequest("post-request-with-content", null).handle( result(post("/foo").content("Hello, world"))); } @@ -100,7 +109,7 @@ public class HttpDocumentationTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - documentHttpRequest("post-request-with-parameter").handle( + documentHttpRequest("post-request-with-parameter", null).handle( result(post("/foo").param("b&r", "baz").param("a", "alpha"))); } @@ -110,7 +119,7 @@ public class HttpDocumentationTests { httpRequest(PUT, "/foo").header(HttpHeaders.HOST, "localhost").content( "Hello, world")); - documentHttpRequest("put-request-with-content").handle( + documentHttpRequest("put-request-with-content", null).handle( result(put("/foo").content("Hello, world"))); } @@ -121,14 +130,14 @@ public class HttpDocumentationTests { .header("Content-Type", "application/x-www-form-urlencoded") .content("b%26r=baz&a=alpha")); - documentHttpRequest("put-request-with-parameter").handle( + documentHttpRequest("put-request-with-parameter", null).handle( result(put("/foo").param("b&r", "baz").param("a", "alpha"))); } @Test public void basicResponse() throws IOException { this.snippet.expectHttpResponse("basic-response").withContents(httpResponse(OK)); - documentHttpResponse("basic-response").handle(result()); + documentHttpResponse("basic-response", null).handle(result()); } @Test @@ -138,7 +147,7 @@ public class HttpDocumentationTests { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(BAD_REQUEST.value()); - documentHttpResponse("non-ok-response").handle(result(response)); + documentHttpResponse("non-ok-response", null).handle(result(response)); } @Test @@ -151,7 +160,7 @@ public class HttpDocumentationTests { MockHttpServletResponse response = new MockHttpServletResponse(); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setHeader("a", "alpha"); - documentHttpResponse("response-with-headers").handle(result(response)); + documentHttpResponse("response-with-headers", null).handle(result(response)); } @Test @@ -160,7 +169,7 @@ public class HttpDocumentationTests { httpResponse(OK).content("content")); MockHttpServletResponse response = new MockHttpServletResponse(); response.getWriter().append("content"); - documentHttpResponse("response-with-content").handle(result(response)); + documentHttpResponse("response-with-content", null).handle(result(response)); } @Test @@ -175,7 +184,7 @@ public class HttpDocumentationTests { .content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "<< data >>".getBytes()); - documentHttpRequest("multipart-post").handle( + documentHttpRequest("multipart-post", null).handle( result(fileUpload("/upload").file(multipartFile))); } @@ -198,7 +207,7 @@ public class HttpDocumentationTests { .content(expectedContent)); MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", null, "<< data >>".getBytes()); - documentHttpRequest("multipart-post").handle( + documentHttpRequest("multipart-post", null).handle( result(fileUpload("/upload").file(multipartFile) .param("a", "apple", "avocado").param("b", "banana"))); } @@ -217,7 +226,7 @@ public class HttpDocumentationTests { MockMultipartFile multipartFile = new MockMultipartFile("image", "documents/images/example.png", MediaType.IMAGE_PNG_VALUE, "<< data >>".getBytes()); - documentHttpRequest("multipart-post-with-content-type").handle( + documentHttpRequest("multipart-post-with-content-type", null).handle( result(fileUpload("/upload").file(multipartFile))); } @@ -229,7 +238,8 @@ public class HttpDocumentationTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); request.setServerName("api.example.com"); - documentHttpRequest("get-request-custom-server-name").handle(result(request)); + documentHttpRequest("get-request-custom-server-name", null).handle( + result(request)); } @Test @@ -237,10 +247,44 @@ public class HttpDocumentationTests { this.snippet.expectHttpRequest("get-request-custom-host").withContents( httpRequest(GET, "/foo").header(HttpHeaders.HOST, "api.example.com")); - documentHttpRequest("get-request-custom-host").handle( + documentHttpRequest("get-request-custom-host", null).handle( result(get("/foo").header(HttpHeaders.HOST, "api.example.com"))); } + @Test + public void requestWithCustomSnippetAttributes() throws IOException { + this.snippet.expectHttpRequest("request-with-snippet-attributes").withContents( + containsString("Title for the request")); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo"); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("http-request")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/http-request-with-title.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + documentHttpRequest("request-with-snippet-attributes", + attributes(key("title").value("Title for the request"))).handle( + result(request)); + } + + @Test + public void responseWithCustomSnippetAttributes() throws IOException { + this.snippet.expectHttpResponse("response-with-snippet-attributes").withContents( + containsString("Title for the response")); + MockHttpServletRequest request = new MockHttpServletRequest(); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("http-response")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/http-response-with-title.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + documentHttpResponse("response-with-snippet-attributes", + attributes(key("title").value("Title for the response"))).handle( + result(request)); + } + private String createPart(String content) { return this.createPart(content, true); } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java index e282f2b9..87e3c3df 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/hypermedia/HypermediaDocumentationTests.java @@ -17,8 +17,11 @@ package org.springframework.restdocs.hypermedia; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.StubMvcResult.result; @@ -57,7 +60,7 @@ public class HypermediaDocumentationTests { this.thrown.expect(SnippetGenerationException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [foo]")); - documentLinks("undocumented-link", + documentLinks("undocumented-link", null, new StubLinkExtractor().withLinks(new Link("foo", "bar"))).handle( result()); } @@ -67,7 +70,7 @@ public class HypermediaDocumentationTests { this.thrown.expect(SnippetGenerationException.class); this.thrown.expectMessage(equalTo("Links with the following relations were not" + " found in the response: [foo]")); - documentLinks("missing-link", new StubLinkExtractor(), + documentLinks("missing-link", null, new StubLinkExtractor(), new LinkDescriptor("foo").description("bar")).handle(result()); } @@ -76,7 +79,7 @@ public class HypermediaDocumentationTests { this.snippet.expectLinks("documented-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - documentLinks("documented-optional-link", + documentLinks("documented-optional-link", null, new StubLinkExtractor().withLinks(new Link("foo", "blah")), new LinkDescriptor("foo").description("bar").optional()).handle(result()); } @@ -86,7 +89,7 @@ public class HypermediaDocumentationTests { this.snippet.expectLinks("missing-optional-link").withContents( // tableWithHeader("Relation", "Description") // .row("foo", "bar")); - documentLinks("missing-optional-link", new StubLinkExtractor(), + documentLinks("missing-optional-link", null, new StubLinkExtractor(), new LinkDescriptor("foo").description("bar").optional()).handle(result()); } @@ -96,7 +99,7 @@ public class HypermediaDocumentationTests { this.thrown.expectMessage(equalTo("Links with the following relations were not" + " documented: [a]. Links with the following relations were not" + " found in the response: [foo]")); - documentLinks("undocumented-link-and-missing-link", + documentLinks("undocumented-link-and-missing-link", null, new StubLinkExtractor().withLinks(new Link("a", "alpha")), new LinkDescriptor("foo").description("bar")).handle(result()); } @@ -109,14 +112,15 @@ public class HypermediaDocumentationTests { .row("b", "two")); documentLinks( "documented-links", + null, new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), new LinkDescriptor("a").description("one"), new LinkDescriptor("b").description("two")).handle(result()); } @Test - public void linksWithCustomAttributes() throws IOException { - this.snippet.expectLinks("documented-links").withContents( // + public void linksWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectLinks("links-with-custom-descriptor-attributes").withContents( // tableWithHeader("Relation", "Description", "Foo") // .row("a", "one", "alpha") // .row("b", "two", "bravo")); @@ -129,12 +133,34 @@ public class HypermediaDocumentationTests { request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); documentLinks( - "documented-links", + "links-with-custom-descriptor-attributes", + null, new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", "bravo")), - new LinkDescriptor("a").description("one").attribute("foo", "alpha"), - new LinkDescriptor("b").description("two").attribute("foo", "bravo")) - .handle(result(request)); + new LinkDescriptor("a").description("one").attributes( + key("foo").value("alpha")), + new LinkDescriptor("b").description("two").attributes( + key("foo").value("bravo"))).handle(result(request)); + } + + @Test + public void linksWithCustomAttribute() throws IOException { + this.snippet.expectLinks("links-with-custom-attribute").withContents( + startsWith(".Title for the links")); + MockHttpServletRequest request = new MockHttpServletRequest(); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("links")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/links-with-title.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + documentLinks( + "links-with-custom-attribute", + attributes(key("title").value("Title for the links")), + new StubLinkExtractor().withLinks(new Link("a", "alpha"), new Link("b", + "bravo")), new LinkDescriptor("a").description("one"), + new LinkDescriptor("b").description("two")).handle(result(request)); } private static class StubLinkExtractor implements LinkExtractor { diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java index b788da69..3e3939f8 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/payload/PayloadDocumentationTests.java @@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -62,7 +64,7 @@ public class PayloadDocumentationTests { .row("a.c", "String", "two") // .row("a", "Object", "three")); - documentRequestFields("map-request-with-fields", + documentRequestFields("map-request-with-fields", null, fieldWithPath("a.b").description("one"), fieldWithPath("a.c").description("two"), fieldWithPath("a").description("three")).handle( @@ -77,7 +79,7 @@ public class PayloadDocumentationTests { .row("[]a.c", "String", "two") // .row("[]a", "Object", "three")); - documentRequestFields("array-request-with-fields", + documentRequestFields("array-request-with-fields", null, fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a").description("three")).handle( @@ -100,7 +102,7 @@ public class PayloadDocumentationTests { response.getWriter().append( "{\"id\": 67,\"date\": \"2015-01-20\",\"assets\":" + " [{\"id\":356,\"name\": \"sample\"}]}"); - documentResponseFields("map-response-with-fields", + documentResponseFields("map-response-with-fields", null, fieldWithPath("id").description("one"), fieldWithPath("date").description("two"), fieldWithPath("assets").description("three"), @@ -121,7 +123,7 @@ public class PayloadDocumentationTests { MockHttpServletResponse response = new MockHttpServletResponse(); response.getWriter() .append("[{\"a\": {\"b\": 5}},{\"a\": {\"c\": \"charlie\"}}]"); - documentResponseFields("array-response-with-fields", + documentResponseFields("array-response-with-fields", null, fieldWithPath("[]a.b").description("one"), fieldWithPath("[]a.c").description("two"), fieldWithPath("[]a").description("three")).handle(result(response)); @@ -145,7 +147,7 @@ public class PayloadDocumentationTests { this.thrown .expectMessage(startsWith("The following parts of the payload were not" + " documented:")); - documentRequestFields("undocumented-request-fields").handle( + documentRequestFields("undocumented-request-fields", null).handle( result(get("/foo").content("{\"a\": 5}"))); } @@ -155,7 +157,7 @@ public class PayloadDocumentationTests { this.thrown .expectMessage(equalTo("Fields with the following paths were not found" + " in the payload: [a.b]")); - documentRequestFields("missing-request-fields", + documentRequestFields("missing-request-fields", null, fieldWithPath("a.b").description("one")).handle( result(get("/foo").content("{}"))); } @@ -163,7 +165,7 @@ public class PayloadDocumentationTests { @Test public void missingOptionalRequestFieldWithNoTypeProvided() throws IOException { this.thrown.expect(FieldTypeRequiredException.class); - documentRequestFields("missing-optional-request-field-with-no-type", + documentRequestFields("missing-optional-request-field-with-no-type", null, fieldWithPath("a.b").description("one").optional()).handle( result(get("/foo").content("{ }"))); } @@ -178,20 +180,20 @@ public class PayloadDocumentationTests { .expectMessage(endsWith("Fields with the following paths were not found" + " in the payload: [a.b]")); documentRequestFields("undocumented-request-field-and-missing-request-field", - fieldWithPath("a.b").description("one")).handle( + null, fieldWithPath("a.b").description("one")).handle( result(get("/foo").content("{ \"a\": { \"c\": 5 }}"))); } @Test - public void requestFieldsWithCustomAttributes() throws IOException { - this.snippet.expectRequestFields("request-fields-with-custom-attributes") - .withContents( // - tableWithHeader("Path", "Type", "Description", "Foo") // - .row("a.b", "Number", "one", "alpha") // - .row("a.c", "String", "two", "bravo") // - .row("a", "Object", "three", "charlie")); + public void requestFieldsWithCustomDescriptorAttributes() throws IOException { + this.snippet.expectRequestFields( + "request-fields-with-custom-descriptor-attributes").withContents( // + tableWithHeader("Path", "Type", "Description", "Foo") // + .row("a.b", "Number", "one", "alpha") // + .row("a.c", "String", "two", "bravo") // + .row("a", "Object", "three", "charlie")); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("fields")) + when(resolver.resolveTemplateResource("request-fields")) .thenReturn( new FileSystemResource( "src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet")); @@ -199,15 +201,19 @@ public class PayloadDocumentationTests { request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( resolver)); request.setContent("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}".getBytes()); - documentRequestFields("request-fields-with-custom-attributes", - fieldWithPath("a.b").description("one").attribute("foo", "alpha"), - fieldWithPath("a.c").description("two").attribute("foo", "bravo"), - fieldWithPath("a").description("three").attribute("foo", "charlie")) - .handle(result(request)); + documentRequestFields( + "request-fields-with-custom-descriptor-attributes", + null, + fieldWithPath("a.b").description("one").attributes( + key("foo").value("alpha")), + fieldWithPath("a.c").description("two").attributes( + key("foo").value("bravo")), + fieldWithPath("a").description("three").attributes( + key("foo").value("charlie"))).handle(result(request)); } @Test - public void responseFieldsWithCustomAttributes() throws IOException { + public void responseFieldsWithCustomDescriptorAttributes() throws IOException { this.snippet.expectResponseFields("response-fields-with-custom-attributes") .withContents( // tableWithHeader("Path", "Type", "Description", "Foo") // @@ -215,7 +221,7 @@ public class PayloadDocumentationTests { .row("a.c", "String", "two", "bravo") // .row("a", "Object", "three", "charlie")); TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - when(resolver.resolveTemplateResource("fields")) + when(resolver.resolveTemplateResource("response-fields")) .thenReturn( new FileSystemResource( "src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet")); @@ -224,10 +230,53 @@ public class PayloadDocumentationTests { resolver)); MockHttpServletResponse response = new MockHttpServletResponse(); response.getOutputStream().print("{\"a\": {\"b\": 5, \"c\": \"charlie\"}}"); - documentResponseFields("response-fields-with-custom-attributes", - fieldWithPath("a.b").description("one").attribute("foo", "alpha"), - fieldWithPath("a.c").description("two").attribute("foo", "bravo"), - fieldWithPath("a").description("three").attribute("foo", "charlie")) - .handle(result(request, response)); + documentResponseFields( + "response-fields-with-custom-attributes", + null, + fieldWithPath("a.b").description("one").attributes( + key("foo").value("alpha")), + fieldWithPath("a.c").description("two").attributes( + key("foo").value("bravo")), + fieldWithPath("a").description("three").attributes( + key("foo").value("charlie"))).handle(result(request, response)); } + + @Test + public void requestFieldsWithCustomAttributes() throws IOException { + this.snippet.expectRequestFields("request-fields-with-custom-attributes") + .withContents(startsWith(".Custom title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("request-fields")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/request-fields-with-title.snippet")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.setContent("{\"a\": \"foo\"}".getBytes()); + MockHttpServletResponse response = new MockHttpServletResponse(); + documentRequestFields("request-fields-with-custom-attributes", + attributes(key("title").value("Custom title")), + fieldWithPath("a").description("one")).handle(result(request, response)); + } + + @Test + public void responseFieldsWithCustomAttributes() throws IOException { + this.snippet.expectResponseFields("response-fields-with-custom-attributes") + .withContents(startsWith(".Custom title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("response-fields")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/response-fields-with-title.snippet")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.getOutputStream().print("{\"a\": \"foo\"}"); + documentResponseFields("response-fields-with-custom-attributes", + attributes(key("title").value("Custom title")), + fieldWithPath("a").description("one")).handle(result(request, response)); + } + } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java index f3a87d6b..42ab1f40 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java @@ -17,8 +17,11 @@ package org.springframework.restdocs.request; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.restdocs.Attributes.attributes; +import static org.springframework.restdocs.Attributes.key; import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; @@ -57,7 +60,7 @@ public class RequestDocumentationTests { this.thrown .expectMessage(equalTo("Query parameters with the following names were" + " not documented: [a]")); - documentQueryParameters("undocumented-parameter").handle( + documentQueryParameters("undocumented-parameter", null).handle( result(get("/").param("a", "alpha"))); } @@ -67,7 +70,7 @@ public class RequestDocumentationTests { this.thrown .expectMessage(equalTo("Query parameters with the following names were" + " not found in the request: [a]")); - documentQueryParameters("missing-parameter", + documentQueryParameters("missing-parameter", null, parameterWithName("a").description("one")).handle(result(get("/"))); } @@ -78,7 +81,7 @@ public class RequestDocumentationTests { .expectMessage(equalTo("Query parameters with the following names were" + " not documented: [b]. Query parameters with the following" + " names were not found in the request: [a]")); - documentQueryParameters("undocumented-parameter-missing-parameter", + documentQueryParameters("undocumented-parameter-missing-parameter", null, parameterWithName("a").description("one")).handle( result(get("/").param("b", "bravo"))); } @@ -89,7 +92,7 @@ public class RequestDocumentationTests { .withContents( tableWithHeader("Parameter", "Description").row("a", "one").row( "b", "two")); - documentQueryParameters("parameter-snippet-request-parameters", + documentQueryParameters("parameter-snippet-request-parameters", null, parameterWithName("a").description("one"), parameterWithName("b").description("two")).handle( result(get("/").param("a", "bravo").param("b", "bravo"))); @@ -101,15 +104,16 @@ public class RequestDocumentationTests { .withContents( tableWithHeader("Parameter", "Description").row("a", "one").row( "b", "two")); - documentQueryParameters("parameter-snippet-request-uri-query-string", + documentQueryParameters("parameter-snippet-request-uri-query-string", null, parameterWithName("a").description("one"), parameterWithName("b").description("two")).handle( result(get("/?a=alpha&b=bravo"))); } @Test - public void parametersWithCustomAttributes() throws IOException { - this.snippet.expectQueryParameters("parameters-with-custom-attributes") + public void parametersWithCustomDescriptorAttributes() throws IOException { + this.snippet + .expectQueryParameters("parameters-with-custom-descriptor-attributes") .withContents( tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); @@ -123,10 +127,36 @@ public class RequestDocumentationTests { resolver)); request.addParameter("a", "bravo"); request.addParameter("b", "bravo"); - documentQueryParameters("parameters-with-custom-attributes", - parameterWithName("a").description("one").attribute("foo", "alpha"), - parameterWithName("b").description("two").attribute("foo", "bravo")) - .handle(result(request)); + documentQueryParameters( + "parameters-with-custom-descriptor-attributes", + null, + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), + parameterWithName("b").description("two").attributes( + key("foo").value("bravo"))).handle(result(request)); + } + + @Test + public void parametersWithCustomAttributes() throws IOException { + this.snippet.expectQueryParameters("parameters-with-custom-attributes") + .withContents(startsWith(".The title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("query-parameters")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + request.addParameter("a", "bravo"); + request.addParameter("b", "bravo"); + documentQueryParameters( + "parameters-with-custom-attributes", + attributes(key("title").value("The title")), + parameterWithName("a").description("one").attributes( + key("foo").value("alpha")), + parameterWithName("b").description("two").attributes( + key("foo").value("bravo"))).handle(result(request)); } } diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/curl-request-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/curl-request-with-title.snippet new file mode 100644 index 00000000..b53c203c --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/curl-request-with-title.snippet @@ -0,0 +1,5 @@ +[source,bash] +.{{title}} +---- +$ curl {{arguments}} +---- \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/http-request-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/http-request-with-title.snippet new file mode 100644 index 00000000..812b2e3f --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/http-request-with-title.snippet @@ -0,0 +1,9 @@ +[source,http] +.{{title}} +---- +{{method}} {{path}} HTTP/1.1 +{{#headers}} +{{name}}: {{value}} +{{/headers}} +{{requestBody}} +---- \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/http-response-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/http-response-with-title.snippet new file mode 100644 index 00000000..10931202 --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/http-response-with-title.snippet @@ -0,0 +1,9 @@ +[source,http] +.{{title}} +---- +HTTP/1.1 {{statusCode}} {{statusReason}} +{{#headers}} +{{name}}: {{value}} +{{/headers}} +{{responseBody}} +---- \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-title.snippet new file mode 100644 index 00000000..ea4aa06d --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-title.snippet @@ -0,0 +1,10 @@ +.{{title}} +|=== +|Relation|Description + +{{#links}} +|{{rel}} +|{{description}} + +{{/links}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet new file mode 100644 index 00000000..611254aa --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-title.snippet @@ -0,0 +1,10 @@ +.{{title}} +|=== +|Parameter|Description + +{{#parameters}} +|{{name}} +|{{description}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-title.snippet new file mode 100644 index 00000000..a51b74ae --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-title.snippet @@ -0,0 +1,11 @@ +.{{title}} +|=== +|Path|Type|Description + +{{#fields}} +|{{path}} +|{{type}} +|{{description}} + +{{/fields}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-title.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-title.snippet new file mode 100644 index 00000000..a51b74ae --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-title.snippet @@ -0,0 +1,11 @@ +.{{title}} +|=== +|Path|Type|Description + +{{#fields}} +|{{path}} +|{{type}} +|{{description}} + +{{/fields}} +|=== \ No newline at end of file