Handle form and query parameters separately

Previously, form and query parameters were handled together as
request parameters. Howeer, request parameters are a server-side
construct that's specific to the servlet specification. As such
they're not appropriate for the client-side documentation that
Spring REST Docs aims to produce.

This commit replaces support for documenting request parameters
with support for documenting query paramters found in the query
string of the request's URI and for documenting form parameters
found in the form URL encoded body of the request.

Closes gh-832
This commit is contained in:
Andy Wilkinson
2022-10-10 15:28:30 +01:00
parent b4be34bf8e
commit f5a629af34
63 changed files with 1730 additions and 1356 deletions

View File

@@ -30,9 +30,7 @@ import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.codec.FormHttpMessageReader;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.FilePart;
@@ -42,14 +40,11 @@ import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationRequestPartFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.QueryStringParser;
import org.springframework.restdocs.operation.RequestConverter;
import org.springframework.restdocs.operation.RequestCookie;
import org.springframework.test.web.reactive.server.ExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* A {@link RequestConverter} for creating an {@link OperationRequest} derived from an
@@ -59,18 +54,11 @@ import org.springframework.util.MultiValueMap;
*/
class WebTestClientRequestConverter implements RequestConverter<ExchangeResult> {
private static final ResolvableType FORM_DATA_TYPE = ResolvableType.forClassWithGenerics(MultiValueMap.class,
String.class, String.class);
private final QueryStringParser queryStringParser = new QueryStringParser();
private final FormHttpMessageReader formDataReader = new FormHttpMessageReader();
@Override
public OperationRequest convert(ExchangeResult result) {
HttpHeaders headers = extractRequestHeaders(result);
return new OperationRequestFactory().create(result.getUrl(), result.getMethod(), result.getRequestBodyContent(),
headers, extractParameters(result), extractRequestParts(result), extractCookies(headers));
headers, extractRequestParts(result), extractCookies(headers));
}
private HttpHeaders extractRequestHeaders(ExchangeResult result) {
@@ -80,16 +68,6 @@ class WebTestClientRequestConverter implements RequestConverter<ExchangeResult>
return extracted;
}
private Parameters extractParameters(ExchangeResult result) {
Parameters parameters = new Parameters();
parameters.addAll(this.queryStringParser.parse(result.getUrl()));
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(result.getRequestHeaders().getContentType())) {
parameters.addAll(this.formDataReader
.readMono(FORM_DATA_TYPE, new ExchangeResultReactiveHttpInputMessage(result), null).block());
}
return parameters;
}
private List<OperationRequestPart> extractRequestParts(ExchangeResult result) {
HttpMessageReader<Part> partHttpMessageReader = new DefaultPartHttpMessageReader();
return new MultipartHttpMessageReader(partHttpMessageReader)

View File

@@ -17,6 +17,7 @@
package org.springframework.restdocs.webtestclient;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.junit.Test;
@@ -103,15 +104,12 @@ public class WebTestClientRequestConverterTests {
}
@Test
public void getRequestWithQueryStringPopulatesParameters() {
public void getRequestWithQueryString() {
ExchangeResult result = WebTestClient.bindToRouterFunction(RouterFunctions.route(GET("/foo"), (req) -> null))
.configureClient().baseUrl("http://localhost").build().get().uri("/foo?a=alpha&b=bravo").exchange()
.expectBody().returnResult();
OperationRequest request = this.converter.convert(result);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo"));
assertThat(request.getParameters()).hasSize(2);
assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha"));
assertThat(request.getParameters()).containsEntry("b", Arrays.asList("bravo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.GET);
}
@@ -128,9 +126,9 @@ public class WebTestClientRequestConverterTests {
OperationRequest request = this.converter.convert(result);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getParameters()).hasSize(2);
assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha", "apple"));
assertThat(request.getParameters()).containsEntry("b", Arrays.asList("br&vo"));
assertThat(request.getContentAsString()).isEqualTo("a=alpha&a=apple&b=br%26vo");
assertThat(request.getHeaders().getContentType())
.isEqualTo(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8));
}
@Test
@@ -144,9 +142,6 @@ public class WebTestClientRequestConverterTests {
OperationRequest request = this.converter.convert(result);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getParameters()).hasSize(2);
assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha", "apple"));
assertThat(request.getParameters()).containsEntry("b", Arrays.asList("br&vo"));
}
@Test
@@ -162,9 +157,9 @@ public class WebTestClientRequestConverterTests {
OperationRequest request = this.converter.convert(result);
assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=br%26vo"));
assertThat(request.getMethod()).isEqualTo(HttpMethod.POST);
assertThat(request.getParameters()).hasSize(2);
assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha", "apple"));
assertThat(request.getParameters()).containsEntry("b", Arrays.asList("br&vo"));
assertThat(request.getContentAsString()).isEqualTo("a=apple");
assertThat(request.getHeaders().getContentType())
.isEqualTo(new MediaType(MediaType.APPLICATION_FORM_URLENCODED, StandardCharsets.UTF_8));
}
@Test

View File

@@ -64,7 +64,7 @@ import static org.assertj.core.api.Assertions.fail;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.partWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.restdocs.request.RequestDocumentation.requestParts;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;
import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration;
@@ -122,12 +122,11 @@ public class WebTestClientRestDocumentationIntegrationTests {
}
@Test
public void requestParametersSnippet() {
this.webTestClient.get().uri("/?a=alpha&b=bravo").exchange().expectStatus().isOk().expectBody()
.consumeWith(document("request-parameters",
requestParameters(parameterWithName("a").description("Alpha description"),
parameterWithName("b").description("Bravo description"))));
assertThat(new File("build/generated-snippets/request-parameters/request-parameters.adoc"))
public void queryParametersSnippet() {
this.webTestClient.get().uri("/?a=alpha&b=bravo").exchange().expectStatus().isOk().expectBody().consumeWith(
document("query-parameters", queryParameters(parameterWithName("a").description("Alpha description"),
parameterWithName("b").description("Bravo description"))));
assertThat(new File("build/generated-snippets/query-parameters/query-parameters.adoc"))
.has(content(tableWithHeader(TemplateFormats.asciidoctor(), "Parameter", "Description")
.row("`a`", "Alpha description").row("`b`", "Bravo description")));
}
@@ -204,7 +203,7 @@ public class WebTestClientRestDocumentationIntegrationTests {
}
private Condition<File> content(final Condition<String> delegate) {
return new Condition<File>() {
return new Condition<>() {
@Override
public boolean matches(File value) {