From b3eee08b5d6d3cc7a3ca04b4b7c00917b7d5dff9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 29 Apr 2015 09:22:33 +0100 Subject: [PATCH] =?UTF-8?q?Add=20support=20for=20documenting=20a=20request?= =?UTF-8?q?=E2=80=99s=20query=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for documenting a request's query parameters. For example: mockMvc.perform( get("/issues").param("page", "2").param("limit", "20")) .andDo(documentQueryParameters("list-issues", parameterWithName("page") .description("Page number to return"), parameterWithName("limit") .description("Maximum page size"))); Closes gh-39 --- .../RestDocumentationResultHandler.java | 21 ++++ .../restdocs/request/ParameterDescriptor.java | 55 +++++++++ .../QueryParametersSnippetResultHandler.java | 105 ++++++++++++++++++ .../request/RequestDocumentation.java | 59 ++++++++++ .../request/RequestDocumentationTests.java | 103 +++++++++++++++++ .../restdocs/test/ExpectedSnippet.java | 5 + 6 files changed, 348 insertions(+) create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.java create mode 100644 spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java create mode 100644 spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java 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 11b55534..912d4650 100644 --- a/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/RestDocumentationResultHandler.java @@ -22,6 +22,7 @@ import static org.springframework.restdocs.http.HttpDocumentation.documentHttpRe import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.documentLinks; import static org.springframework.restdocs.payload.PayloadDocumentation.documentRequestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.documentResponseFields; +import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; import java.util.ArrayList; import java.util.List; @@ -32,6 +33,8 @@ import org.springframework.restdocs.hypermedia.LinkExtractor; import org.springframework.restdocs.hypermedia.LinkExtractors; 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.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultHandler; @@ -138,4 +141,22 @@ public class RestDocumentationResultHandler implements ResultHandler { return this; } + /** + * Documents the parameters in the request's query string using the given + * {@code descriptors}. + *

+ * 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 + * @return {@code this} + * @see RequestDocumentation#parameterWithName(String) + */ + public RestDocumentationResultHandler withQueryParameters( + ParameterDescriptor... descriptors) { + this.delegates.add(documentQueryParameters(this.outputDir, descriptors)); + return this; + } } 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 new file mode 100644 index 00000000..63a1be58 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/ParameterDescriptor.java @@ -0,0 +1,55 @@ +/* + * 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.request; + +/** + * A descriptor of a parameter in a query string + * + * @author Andy Wilkinson + * @see RequestDocumentation#parameterWithName + * + */ +public class ParameterDescriptor { + + private final String name; + + private String description; + + ParameterDescriptor(String name) { + this.name = name; + } + + /** + * Specifies the description of the parameter + * + * @param description The parameter's description + * @return {@code this} + */ + public ParameterDescriptor description(String description) { + this.description = description; + return this; + } + + String getName() { + return this.name; + } + + String getDescription() { + return this.description; + } + +} 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 new file mode 100644 index 00000000..1058b0a8 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/QueryParametersSnippetResultHandler.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.request; + +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.springframework.restdocs.snippet.DocumentationWriter; +import org.springframework.restdocs.snippet.DocumentationWriter.TableAction; +import org.springframework.restdocs.snippet.DocumentationWriter.TableWriter; +import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.snippet.SnippetWritingResultHandler; +import org.springframework.test.web.servlet.MvcResult; +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 { + + private final Map descriptorsByName = new LinkedHashMap<>(); + + protected QueryParametersSnippetResultHandler(String outputDir, + ParameterDescriptor... descriptors) { + super(outputDir, "query-parameters"); + for (ParameterDescriptor descriptor : descriptors) { + Assert.hasText(descriptor.getName()); + Assert.hasText(descriptor.getDescription()); + this.descriptorsByName.put(descriptor.getName(), descriptor); + } + } + + @Override + protected void handle(MvcResult result, DocumentationWriter writer) + throws IOException { + verifyParameterDescriptors(result); + documentParameters(writer); + } + + private void verifyParameterDescriptors(MvcResult result) { + Set actualParameters = result.getRequest().getParameterMap().keySet(); + Set expectedParameters = this.descriptorsByName.keySet(); + + Set undocumentedParameters = new HashSet(actualParameters); + undocumentedParameters.removeAll(expectedParameters); + + Set missingParameters = new HashSet(expectedParameters); + missingParameters.removeAll(actualParameters); + + if (!undocumentedParameters.isEmpty() || !missingParameters.isEmpty()) { + String message = ""; + if (!undocumentedParameters.isEmpty()) { + message += "Query parameters with the following names were not documented: " + + undocumentedParameters; + } + if (!missingParameters.isEmpty()) { + if (message.length() > 0) { + message += ". "; + } + message += "Query parameters with the following names were not found in the request: " + + missingParameters; + } + throw new SnippetGenerationException(message); + } + + Assert.isTrue(actualParameters.equals(expectedParameters)); + } + + private void documentParameters(DocumentationWriter writer) throws IOException { + writer.table(new TableAction() { + + @Override + public void perform(TableWriter tableWriter) throws IOException { + tableWriter.headers("Parameter", "Description"); + for (Entry entry : QueryParametersSnippetResultHandler.this.descriptorsByName + .entrySet()) { + tableWriter.row(entry.getKey(), entry.getValue().getDescription()); + } + } + + }); + } + +} 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 new file mode 100644 index 00000000..cef76d16 --- /dev/null +++ b/spring-restdocs/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java @@ -0,0 +1,59 @@ +/* + * 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.request; + +import org.springframework.restdocs.RestDocumentationResultHandler; +import org.springframework.restdocs.snippet.SnippetWritingResultHandler; + +/** + * Static factory methods for documenting aspects of a request sent to a RESTful API. + * + * @author Andy Wilkinson + */ +public abstract class RequestDocumentation { + + private RequestDocumentation() { + + } + + /** + * Creates a {@link SnippetWritingResultHandler} that will produce a snippet + * documenting a request's query parameters + * + * @param outputDir The directory to which the snippet should be written + * @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); + } + + /** + * Creates a {@link ParameterDescriptor} that describes a query string parameter with + * the given {@code name}. + * + * @param name The name of the parameter + * @return a {@link ParameterDescriptor} ready for further configuration + * @see RestDocumentationResultHandler#withQueryParameters(ParameterDescriptor...) + */ + public static ParameterDescriptor parameterWithName(String name) { + return new ParameterDescriptor(name); + } + +} 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 new file mode 100644 index 00000000..32c794b8 --- /dev/null +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/request/RequestDocumentationTests.java @@ -0,0 +1,103 @@ +/* + * 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.request; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.restdocs.request.RequestDocumentation.documentQueryParameters; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.test.SnippetMatchers.tableWithHeader; +import static org.springframework.restdocs.test.StubMvcResult.result; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +import java.io.IOException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.restdocs.snippet.SnippetGenerationException; +import org.springframework.restdocs.test.ExpectedSnippet; + +/** + * Requests for {@link RequestDocumentation} + * + * @author Andy Wilkinson + */ +public class RequestDocumentationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public ExpectedSnippet snippet = new ExpectedSnippet(); + + @Test + public void undocumentedParameter() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown + .expectMessage(equalTo("Query parameters with the following names were" + + " not documented: [a]")); + documentQueryParameters("undocumented-parameter").handle( + result(get("/").param("a", "alpha"))); + } + + @Test + public void missingParameter() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown + .expectMessage(equalTo("Query parameters with the following names were" + + " not found in the request: [a]")); + documentQueryParameters("missing-parameter", + parameterWithName("a").description("one")).handle(result(get("/"))); + } + + @Test + public void undocumentedParameterAndMissingParameter() throws IOException { + this.thrown.expect(SnippetGenerationException.class); + this.thrown + .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", + parameterWithName("a").description("one")).handle( + result(get("/").param("b", "bravo"))); + } + + @Test + public void parameterSnippetFromRequestParameters() throws IOException { + this.snippet.expectQueryParameters("parameter-snippet-request-parameters") + .withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row( + "b", "two")); + documentQueryParameters("parameter-snippet-request-parameters", + parameterWithName("a").description("one"), + parameterWithName("b").description("two")).handle( + result(get("/").param("a", "bravo").param("b", "bravo"))); + } + + @Test + public void parameterSnippetFromRequestUriQueryString() throws IOException { + this.snippet.expectQueryParameters("parameter-snippet-request-uri-query-string") + .withContents( + tableWithHeader("Parameter", "Description").row("a", "one").row( + "b", "two")); + documentQueryParameters("parameter-snippet-request-uri-query-string", + parameterWithName("a").description("one"), + parameterWithName("b").description("two")).handle( + result(get("/?a=alpha&b=bravo"))); + } + +} diff --git a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java index 5c7f9b52..d7ec0d71 100644 --- a/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java +++ b/spring-restdocs/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java @@ -142,6 +142,11 @@ public class ExpectedSnippet implements TestRule { return this; } + public ExpectedSnippet expectQueryParameters(String name) { + expect(name, "query-parameters"); + return this; + } + private ExpectedSnippet expect(String name, String type) { this.expectedName = name; this.expectedType = type;