diff --git a/docs/src/docs/asciidoc/customizing-snippets.adoc b/docs/src/docs/asciidoc/customizing-snippets.adoc deleted file mode 100644 index 94cca79d..00000000 --- a/docs/src/docs/asciidoc/customizing-snippets.adoc +++ /dev/null @@ -1,12 +0,0 @@ -[[customizing-snippets]] -== Customizing the generated snippets - -Spring REST Docs uses https://mustache.github.io[Mustache] templates to produce the -generated snippets. You can customize the generated snippets by overriding the -{source}spring-restdocs/src/main/resources/org/springframework/restdocs/templates[default -templates]. - -Templates are loaded from the classpath in the `org.springframework.restdocs.templates` -package and each template is named after the snippet that it will produce. For example, to -override the template for the `curl-request.adoc` snippet, create a template named -`curl-request.snippet` in `src/test/resources/org/springframework/restdocs/templates`. \ No newline at end of file diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index a7652ed4..edac1e2d 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -257,4 +257,65 @@ include::{examples-dir}/com/example/AlwaysDo.java[tags=always-do] With this configuration in place, every call to `MockMvc.perform` will produce the <> without any further configuration. Take a look at the `GettingStartedDocumentation` classes in each of the -sample applications to see this functionality in action. \ No newline at end of file +sample applications to see this functionality in action. + + + +[[documenting-your-api-customizing]] +=== Customizing the output + + + +[[documenting-your-api-customizing-snippets]] +==== Customizing the generated snippets +Spring REST Docs uses https://mustache.github.io[Mustache] templates to produce the +generated snippets. +{source}spring-restdocs/src/main/resources/org/springframework/restdocs/templates[Default +templates] are provided for each of the snippets that Spring REST Docs can produce. To +customize a snippet's content, you can provide your own template. + +Templates are loaded from the classpath in the `org.springframework.restdocs.templates` +package and each template is named after the snippet that it will produce. For example, to +override the template for the `curl-request.adoc` snippet, create a template named +`curl-request.snippet` in `src/test/resources/org/springframework/restdocs/templates`. + + + +[[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: + +[source,java,indent=0] +---- +include::{examples-dir}/com/example/Payload.java[tags=constraints] +---- + +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: + +[source,indent=0] +---- + |=== + |Path|Type|Description|Constraints <1> + + {{#fields}} + |{{path}} + |{{type}} + |{{description}} + |{{constraints}} <2> + + {{/fields}} + |=== +---- +<1> Add a new column named "Constraints" +<2> Include the descriptors' `constraints` attribute in each row of the table + + diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index f6afcf0e..2fb04b36 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -24,7 +24,6 @@ include::introduction.adoc[] include::getting-started.adoc[] include::documenting-your-api.adoc[] include::customizing-responses.adoc[] -include::customizing-snippets.adoc[] include::configuration.adoc[] include::working-with-asciidoctor.adoc[] include::contributing.adoc[] \ No newline at end of file diff --git a/docs/src/test/java/com/example/Payload.java b/docs/src/test/java/com/example/Payload.java index bec0f361..7fcd725a 100644 --- a/docs/src/test/java/com/example/Payload.java +++ b/docs/src/test/java/com/example/Payload.java @@ -19,6 +19,7 @@ package com.example; import static org.springframework.restdocs.RestDocumentation.document; 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 org.springframework.http.MediaType; @@ -51,4 +52,18 @@ private MockMvc mockMvc; // end::explicit-type[] } + public void constraints() throws Exception { + this.mockMvc.perform(post("/users/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + // tag::constraints[] + .andDo(document("create-user").withRequestFields( + fieldWithPath("name") + .description("The user's name") + .attribute("constraints", "Must not be null. Must not be empty"), + fieldWithPath("email") + .description("The user's email address") + .attribute("constrains", "Must be a valid email address"))); + // 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 new file mode 100644 index 00000000..c87637ff --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/AbstractDescriptor.java @@ -0,0 +1,39 @@ +package org.springframework.restdocs; + +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for descriptors. Provides the ability to associate arbitrary attributes with + * a descriptor. + * + * @author Andy Wilkinson + * + * @param the type of the descriptor + */ +public abstract class AbstractDescriptor> { + + private final Map attributes = new HashMap<>(); + + /** + * Sets an attribute with the given {@code name} and {@code value}. + * + * @param name The name of the attribute + * @param value The value of the attribute + */ + @SuppressWarnings("unchecked") + public T attribute(String name, Object value) { + this.attributes.put(name, value); + return (T) this; + } + + /** + * Returns the descriptor's attributes + * + * @return the attributes + */ + protected Map getAttributes() { + return this.attributes; + } + +} diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java index dca8adcb..2c1451dc 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/hypermedia/LinkDescriptor.java @@ -16,6 +16,11 @@ package org.springframework.restdocs.hypermedia; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.restdocs.AbstractDescriptor; + /** * A description of a link found in a hypermedia API * @@ -23,7 +28,7 @@ package org.springframework.restdocs.hypermedia; * * @author Andy Wilkinson */ -public class LinkDescriptor { +public class LinkDescriptor extends AbstractDescriptor { private final String rel; @@ -67,4 +72,13 @@ public class LinkDescriptor { boolean isOptional() { return this.optional; } + + Map toModel() { + Map model = new HashMap<>(); + model.put("rel", this.rel); + model.put("description", this.description); + model.put("optional", this.optional); + model.putAll(getAttributes()); + return model; + } } 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 6d18c625..06b239dd 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 @@ -18,11 +18,13 @@ package org.springframework.restdocs.hypermedia; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.springframework.restdocs.snippet.SnippetGenerationException; @@ -34,7 +36,7 @@ import org.springframework.util.Assert; /** * A {@link SnippetWritingResultHandler} that produces a snippet documenting a RESTful * resource's links. - * + * * @author Andy Wilkinson */ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { @@ -114,8 +116,16 @@ public class LinkSnippetResultHandler extends SnippetWritingResultHandler { TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); Map context = new HashMap<>(); - context.put("links", this.descriptorsByRel.values()); + context.put("links", createLinksModel()); writer.print(templateEngine.compileTemplate("links").render(context)); } + private List> createLinksModel() { + List> model = new ArrayList<>(); + for (Entry entry : this.descriptorsByRel.entrySet()) { + model.add(entry.getValue().toModel()); + } + return model; + } + } \ No newline at end of file diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java index 2c81b9ed..be485d62 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/payload/FieldDescriptor.java @@ -16,6 +16,11 @@ package org.springframework.restdocs.payload; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.restdocs.AbstractDescriptor; + /** * A description of a field found in a request or response payload * @@ -24,7 +29,7 @@ package org.springframework.restdocs.payload; * @author Andreas Evers * @author Andy Wilkinson */ -public class FieldDescriptor { +public class FieldDescriptor extends AbstractDescriptor { private final String path; @@ -86,4 +91,14 @@ public class FieldDescriptor { String getDescription() { return this.description; } + + Map toModel() { + Map model = new HashMap(); + model.put("path", this.path); + model.put("type", this.type.toString()); + model.put("description", this.description); + model.put("optional", this.optional); + model.putAll(this.getAttributes()); + return model; + } } 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 1e87a551..3ad66c08 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 @@ -73,39 +73,32 @@ public abstract class FieldSnippetResultHandler extends SnippetWritingResultHand Object payload = extractPayload(result); - TemplateEngine templateEngine = (TemplateEngine) result.getRequest() - .getAttribute(TemplateEngine.class.getName()); Map context = new HashMap<>(); - List> fields = new ArrayList<>(); + List> fields = new ArrayList<>(); context.put("fields", fields); for (Entry entry : this.descriptorsByPath.entrySet()) { FieldDescriptor descriptor = entry.getValue(); - FieldType type = getFieldType(descriptor, payload); - Map fieldModel = new HashMap<>(); - fieldModel.put("path", entry.getKey()); - fieldModel.put("type", type.toString()); - fieldModel.put("description", descriptor.getDescription()); - fields.add(fieldModel); + if (descriptor.getType() == null) { + descriptor.type(getFieldType(descriptor, payload)); + } + fields.add(descriptor.toModel()); } + TemplateEngine templateEngine = (TemplateEngine) result.getRequest() + .getAttribute(TemplateEngine.class.getName()); writer.print(templateEngine.compileTemplate(this.templateName).render(context)); } private FieldType getFieldType(FieldDescriptor descriptor, Object payload) { - if (descriptor.getType() != null) { - return descriptor.getType(); + try { + return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType( + descriptor.getPath(), payload); } - else { - try { - return FieldSnippetResultHandler.this.fieldTypeResolver.resolveFieldType( - descriptor.getPath(), payload); - } - catch (FieldDoesNotExistException ex) { - String message = "Cannot determine the type of the field '" - + descriptor.getPath() + "' as it is not present in the" - + " payload. Please provide a type using" - + " FieldDescriptor.type(FieldType)."; - throw new FieldTypeRequiredException(message); - } + catch (FieldDoesNotExistException ex) { + String message = "Cannot determine the type of the field '" + + descriptor.getPath() + "' as it is not present in the" + + " payload. Please provide a type using" + + " FieldDescriptor.type(FieldType)."; + throw new FieldTypeRequiredException(message); } } diff --git a/spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java b/spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java index 63a1be58..abc4ee0b 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java @@ -16,6 +16,11 @@ package org.springframework.restdocs.request; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.restdocs.AbstractDescriptor; + /** * A descriptor of a parameter in a query string * @@ -23,7 +28,7 @@ package org.springframework.restdocs.request; * @see RequestDocumentation#parameterWithName * */ -public class ParameterDescriptor { +public class ParameterDescriptor extends AbstractDescriptor { private final String name; @@ -52,4 +57,12 @@ public class ParameterDescriptor { return this.description; } + Map toModel() { + Map model = new HashMap<>(); + model.put("name", this.name); + model.put("description", this.description); + model.putAll(getAttributes()); + return model; + } + } 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 8813d0c8..661e80ff 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 @@ -18,10 +18,13 @@ package org.springframework.restdocs.request; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.springframework.restdocs.snippet.SnippetGenerationException; @@ -33,7 +36,7 @@ import org.springframework.util.Assert; /** * A {@link SnippetWritingResultHandler} that produces a snippet documenting the query * parameters supported by a RESTful resource. - * + * * @author Andy Wilkinson */ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHandler { @@ -90,7 +93,11 @@ public class QueryParametersSnippetResultHandler extends SnippetWritingResultHan TemplateEngine templateEngine = (TemplateEngine) result.getRequest() .getAttribute(TemplateEngine.class.getName()); Map context = new HashMap<>(); - context.put("parameters", this.descriptorsByName.values()); + List> parameters = new ArrayList<>(); + for (Entry entry : this.descriptorsByName.entrySet()) { + parameters.add(entry.getValue().toModel()); + } + context.put("parameters", parameters); writer.print(templateEngine.compileTemplate("query-parameters").render(context)); } 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 56588a06..e282f2b9 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,6 +17,8 @@ package org.springframework.restdocs.hypermedia; import static org.hamcrest.CoreMatchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; import static org.springframework.restdocs.test.StubMvcResult.result; @@ -26,8 +28,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.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.restdocs.snippet.SnippetGenerationException; +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; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -107,6 +114,29 @@ public class HypermediaDocumentationTests { new LinkDescriptor("b").description("two")).handle(result()); } + @Test + public void linksWithCustomAttributes() throws IOException { + this.snippet.expectLinks("documented-links").withContents( // + tableWithHeader("Relation", "Description", "Foo") // + .row("a", "one", "alpha") // + .row("b", "two", "bravo")); + MockHttpServletRequest request = new MockHttpServletRequest(); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("links")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/links-with-extra-column.snippet")); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + resolver)); + documentLinks( + "documented-links", + 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)); + } + private static class StubLinkExtractor implements LinkExtractor { private MultiValueMap linksByRel = new LinkedMultiValueMap(); 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 c96afe0d..b788da69 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 @@ -18,6 +18,8 @@ package org.springframework.restdocs.payload; import static org.hamcrest.CoreMatchers.endsWith; 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.payload.PayloadDocumentation.documentRequestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -30,8 +32,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.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.restdocs.snippet.SnippetGenerationException; +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; /** @@ -174,4 +181,53 @@ public class PayloadDocumentationTests { 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")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("fields")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet")); + MockHttpServletRequest request = new MockHttpServletRequest(); + 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)); + } + + @Test + public void responseFieldsWithCustomAttributes() throws IOException { + this.snippet.expectResponseFields("response-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")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("fields")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet")); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(TemplateEngine.class.getName(), new MustacheTemplateEngine( + 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)); + } } 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 32c794b8..f3a87d6b 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,6 +17,8 @@ package org.springframework.restdocs.request; import static org.hamcrest.CoreMatchers.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; @@ -28,7 +30,12 @@ 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.mock.web.MockHttpServletRequest; import org.springframework.restdocs.snippet.SnippetGenerationException; +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; /** @@ -100,4 +107,26 @@ public class RequestDocumentationTests { result(get("/?a=alpha&b=bravo"))); } + @Test + public void parametersWithCustomAttributes() throws IOException { + this.snippet.expectQueryParameters("parameters-with-custom-attributes") + .withContents( + tableWithHeader("Parameter", "Description", "Foo").row("a", + "one", "alpha").row("b", "two", "bravo")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + when(resolver.resolveTemplateResource("query-parameters")) + .thenReturn( + new FileSystemResource( + "src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.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", + parameterWithName("a").description("one").attribute("foo", "alpha"), + parameterWithName("b").description("two").attribute("foo", "bravo")) + .handle(result(request)); + + } } diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java index 39ef530d..7b1d3150 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/StubMvcResult.java @@ -61,6 +61,11 @@ public class StubMvcResult implements MvcResult { return new StubMvcResult(response); } + public static StubMvcResult result(MockHttpServletRequest request, + MockHttpServletResponse response) { + return new StubMvcResult(request, response); + } + private StubMvcResult() { this(new MockHttpServletRequest(), new MockHttpServletResponse()); } @@ -79,8 +84,10 @@ public class StubMvcResult implements MvcResult { private StubMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) { this.request = request; - this.request.setAttribute(TemplateEngine.class.getName(), - new MustacheTemplateEngine(new StandardTemplateResourceResolver())); + if (this.request.getAttribute(TemplateEngine.class.getName()) == null) { + this.request.setAttribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine(new StandardTemplateResourceResolver())); + } this.response = response; } diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-extra-column.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-extra-column.snippet new file mode 100644 index 00000000..81c8232c --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/links-with-extra-column.snippet @@ -0,0 +1,10 @@ +|=== +|Relation|Description|Foo + +{{#links}} +|{{rel}} +|{{description}} +|{{foo}} + +{{/links}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.snippet new file mode 100644 index 00000000..fb97f89b --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/query-parameters-with-extra-column.snippet @@ -0,0 +1,10 @@ +|=== +|Parameter|Description|Foo + +{{#parameters}} +|{{name}} +|{{description}} +|{{foo}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet new file mode 100644 index 00000000..4830d895 --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/request-fields-with-extra-column.snippet @@ -0,0 +1,11 @@ +|=== +|Path|Type|Description|Foo + +{{#fields}} +|{{path}} +|{{type}} +|{{description}} +|{{foo}} + +{{/fields}} +|=== \ No newline at end of file diff --git a/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet b/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet new file mode 100644 index 00000000..4830d895 --- /dev/null +++ b/spring-restdocs/src/test/resources/custom-snippet-templates/response-fields-with-extra-column.snippet @@ -0,0 +1,11 @@ +|=== +|Path|Type|Description|Foo + +{{#fields}} +|{{path}} +|{{type}} +|{{description}} +|{{foo}} + +{{/fields}} +|=== \ No newline at end of file