diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index 92cb7bd1..15d3a520 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -649,21 +649,20 @@ For example, the preceding code results in a snippet named `response-fields-bene -[[documenting-your-api-request-parameters]] -=== Request Parameters +[[documenting-your-api-query-parameters]] +=== Query Parameters -You can document a request's parameters by using `requestParameters`. -You can include request parameters in a `GET` request's query string. +You can document a request's query parameters by using `queryParameters`. The following examples show how to do so: [source,java,indent=0,role="primary"] .MockMvc ---- -include::{examples-dir}/com/example/mockmvc/RequestParameters.java[tags=request-parameters-query-string] +include::{examples-dir}/com/example/mockmvc/QueryParameters.java[tags=query-parameters] ---- <1> Perform a `GET` request with two parameters, `page` and `per_page`, in the query string. -<2> Configure Spring REST Docs to produce a snippet describing the request's parameters. -Uses the static `requestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<2> Configure Spring REST Docs to produce a snippet describing the request's query parameters. +Uses the static `queryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document the `page` parameter. Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. <4> Document the `per_page` parameter. @@ -671,11 +670,11 @@ Uses the static `parameterWithName` method on `org.springframework.restdocs.requ [source,java,indent=0,role="secondary"] .WebTestClient ---- -include::{examples-dir}/com/example/webtestclient/RequestParameters.java[tags=request-parameters-query-string] +include::{examples-dir}/com/example/webtestclient/QueryParameters.java[tags=query-parameters] ---- <1> Perform a `GET` request with two parameters, `page` and `per_page`, in the query string. -<2> Configure Spring REST Docs to produce a snippet describing the request's parameters. -Uses the static `requestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<2> Configure Spring REST Docs to produce a snippet describing the request's query parameters. +Uses the static `queryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document the `page` parameter. Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. <4> Document the `per_page` parameter. @@ -683,51 +682,77 @@ Uses the static `parameterWithName` method on `org.springframework.restdocs.requ [source,java,indent=0,role="secondary"] .REST Assured ---- -include::{examples-dir}/com/example/restassured/RequestParameters.java[tags=request-parameters-query-string] +include::{examples-dir}/com/example/restassured/QueryParameters.java[tags=query-parameters] ---- -<1> Configure Spring REST Docs to produce a snippet describing the request's parameters. -Uses the static `requestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<1> Configure Spring REST Docs to produce a snippet describing the request's query parameters. +Uses the static `queryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. <2> Document the `page` parameter. Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. <3> Document the `per_page` parameter. <4> Perform a `GET` request with two parameters, `page` and `per_page`, in the query string. -You can also include request parameters as form data in the body of a POST request. +When documenting query parameters, the test fails if an undocumented query parameter is used in the request's query string. +Similarly, the test also fails if a documented query parameter is not found in the request's query string and the parameter has not been marked as optional. + +If you do not want to document a query parameter, you can mark it as ignored. +This prevents it from appearing in the generated snippet while avoiding the failure described above. + +You can also document query parameters in a relaxed mode where any undocumented parameters do not cause a test failure. +To do so, use the `relaxedQueryParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +This can be useful when documenting a particular scenario where you only want to focus on a subset of the query parameters. + + + +[[documenting-your-api-form-parameters]] +=== Form Parameters + +You can document a request's form parameters by using `formParameters`. The following examples show how to do so: [source,java,indent=0,role="primary"] .MockMvc ---- -include::{examples-dir}/com/example/mockmvc/RequestParameters.java[tags=request-parameters-form-data] +include::{examples-dir}/com/example/mockmvc/FormParameters.java[tags=form-parameters] ---- -<1> Perform a `POST` request with a single parameter, `username`. +<1> Perform a `POST` request with a single form parameter, `username`. +<2> Configure Spring REST Docs to produce a snippet describing the request's form parameters. +Uses the static `formParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<3> Document the `username` parameter. +Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. [source,java,indent=0,role="secondary"] .WebTestClient ---- -include::{examples-dir}/com/example/webtestclient/RequestParameters.java[tags=request-parameters-form-data] +include::{examples-dir}/com/example/webtestclient/FormParameters.java[tags=form-parameters] ---- -<1> Perform a `POST` request with a single parameter, `username`. +<1> Perform a `POST` request with a single form parameter, `username`. +<2> Configure Spring REST Docs to produce a snippet describing the request's form parameters. +Uses the static `formParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<3> Document the `username` parameter. +Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. [source,java,indent=0,role="secondary"] .REST Assured ---- -include::{examples-dir}/com/example/restassured/RequestParameters.java[tags=request-parameters-form-data] +include::{examples-dir}/com/example/restassured/FormParameters.java[tags=form-parameters] ---- -<1> Configure the `username` parameter. -<2> Perform the `POST` request. +<1> Configure Spring REST Docs to produce a snippet describing the request's form parameters. +Uses the static `formParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +<2> Document the `username` parameter. +Uses the static `parameterWithName` method on `org.springframework.restdocs.request.RequestDocumentation`. +<3> Perform a `POST` request with a single form parameter, `username`. -In all cases, the result is a snippet named `request-parameters.adoc` that contains a table describing the parameters that are supported by the resource. +In all cases, the result is a snippet named `form-parameters.adoc` that contains a table describing the form parameters that are supported by the resource. -When documenting request parameters, the test fails if an undocumented request parameter is used in the request. -Similarly, the test also fails if a documented request parameter is not found in the request and the request parameter has not been marked as optional. +When documenting form parameters, the test fails if an undocumented form parameter is used in the request body. +Similarly, the test also fails if a documented form parameter is not found in the request body and the form parameter has not been marked as optional. -If you do not want to document a request parameter, you can mark it as ignored. +If you do not want to document a form parameter, you can mark it as ignored. This prevents it from appearing in the generated snippet while avoiding the failure described above. -You can also document request parameters in a relaxed mode where any undocumented parameters do not cause a test failure. -To do so, use the `relaxedRequestParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. -This can be useful when documenting a particular scenario where you only want to focus on a subset of the request parameters. +You can also document form parameters in a relaxed mode where any undocumented parameters do not cause a test failure. +To do so, use the `relaxedFormParameters` method on `org.springframework.restdocs.request.RequestDocumentation`. +This can be useful when documenting a particular scenario where you only want to focus on a subset of the form parameters. diff --git a/docs/src/test/java/com/example/mockmvc/FormParameters.java b/docs/src/test/java/com/example/mockmvc/FormParameters.java new file mode 100644 index 00000000..8000286f --- /dev/null +++ b/docs/src/test/java/com/example/mockmvc/FormParameters.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2021 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 + * + * https://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 com.example.mockmvc; + +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.request.RequestDocumentation.formParameters; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class FormParameters { + + private MockMvc mockMvc; + + public void postFormDataSnippet() throws Exception { + // tag::form-parameters[] + this.mockMvc.perform(post("/users").param("username", "Tester")) // <1> + .andExpect(status().isCreated()).andDo(document("create-user", formParameters(// <2> + parameterWithName("username").description("The user's username") // <3> + ))); + // end::form-parameters[] + } + +} diff --git a/docs/src/test/java/com/example/mockmvc/RequestParameters.java b/docs/src/test/java/com/example/mockmvc/QueryParameters.java similarity index 67% rename from docs/src/test/java/com/example/mockmvc/RequestParameters.java rename to docs/src/test/java/com/example/mockmvc/QueryParameters.java index 1f206a94..bd9d6b50 100644 --- a/docs/src/test/java/com/example/mockmvc/RequestParameters.java +++ b/docs/src/test/java/com/example/mockmvc/QueryParameters.java @@ -20,31 +20,22 @@ import org.springframework.test.web.servlet.MockMvc; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class RequestParameters { +public class QueryParameters { private MockMvc mockMvc; public void getQueryStringSnippet() throws Exception { - // tag::request-parameters-query-string[] + // tag::query-parameters[] this.mockMvc.perform(get("/users?page=2&per_page=100")) // <1> - .andExpect(status().isOk()).andDo(document("users", requestParameters(// <2> + .andExpect(status().isOk()).andDo(document("users", queryParameters(// <2> parameterWithName("page").description("The page to retrieve"), // <3> parameterWithName("per_page").description("Entries per page") // <4> ))); - // end::request-parameters-query-string[] - } - - public void postFormDataSnippet() throws Exception { - // tag::request-parameters-form-data[] - this.mockMvc.perform(post("/users").param("username", "Tester")) // <1> - .andExpect(status().isCreated()).andDo(document("create-user", - requestParameters(parameterWithName("username").description("The user's username")))); - // end::request-parameters-form-data[] + // end::query-parameters[] } } diff --git a/docs/src/test/java/com/example/restassured/FormParameters.java b/docs/src/test/java/com/example/restassured/FormParameters.java new file mode 100644 index 00000000..e2310f05 --- /dev/null +++ b/docs/src/test/java/com/example/restassured/FormParameters.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2022 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 + * + * https://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 com.example.restassured; + +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; + +import static org.hamcrest.CoreMatchers.is; +import static org.springframework.restdocs.request.RequestDocumentation.formParameters; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; + +public class FormParameters { + + private RequestSpecification spec; + + public void postFormDataSnippet() { + // tag::form-parameters[] + RestAssured.given(this.spec).filter(document("create-user", formParameters(// <1> + parameterWithName("username").description("The user's username")))) // <2> + .formParam("username", "Tester").when().post("/users") // <3> + .then().assertThat().statusCode(is(200)); + // end::form-parameters[] + } + +} diff --git a/docs/src/test/java/com/example/restassured/RequestParameters.java b/docs/src/test/java/com/example/restassured/QueryParameters.java similarity index 68% rename from docs/src/test/java/com/example/restassured/RequestParameters.java rename to docs/src/test/java/com/example/restassured/QueryParameters.java index a6189771..7656e4e7 100644 --- a/docs/src/test/java/com/example/restassured/RequestParameters.java +++ b/docs/src/test/java/com/example/restassured/QueryParameters.java @@ -21,32 +21,21 @@ import io.restassured.specification.RequestSpecification; import static org.hamcrest.CoreMatchers.is; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; -public class RequestParameters { +public class QueryParameters { private RequestSpecification spec; public void getQueryStringSnippet() { - // tag::request-parameters-query-string[] - RestAssured.given(this.spec).filter(document("users", requestParameters(// <1> + // tag::query-parameters[] + RestAssured.given(this.spec).filter(document("users", queryParameters(// <1> parameterWithName("page").description("The page to retrieve"), // <2> parameterWithName("per_page").description("Entries per page")))) // <3> .when().get("/users?page=2&per_page=100") // <4> .then().assertThat().statusCode(is(200)); - // end::request-parameters-query-string[] - } - - public void postFormDataSnippet() { - // tag::request-parameters-form-data[] - RestAssured.given(this.spec) - .filter(document("create-user", - requestParameters(parameterWithName("username").description("The user's username")))) - .formParam("username", "Tester") // <1> - .when().post("/users") // <2> - .then().assertThat().statusCode(is(200)); - // end::request-parameters-form-data[] + // end::query-parameters[] } } diff --git a/docs/src/test/java/com/example/webtestclient/RequestParameters.java b/docs/src/test/java/com/example/webtestclient/FormParameters.java similarity index 64% rename from docs/src/test/java/com/example/webtestclient/RequestParameters.java rename to docs/src/test/java/com/example/webtestclient/FormParameters.java index de0cb239..0bfdbc0c 100644 --- a/docs/src/test/java/com/example/webtestclient/RequestParameters.java +++ b/docs/src/test/java/com/example/webtestclient/FormParameters.java @@ -21,37 +21,26 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserters; +import static org.springframework.restdocs.request.RequestDocumentation.formParameters; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; -public class RequestParameters { +public class FormParameters { // @formatter:off private WebTestClient webTestClient; - public void getQueryStringSnippet() { - // tag::request-parameters-query-string[] - this.webTestClient.get().uri("/users?page=2&per_page=100") // <1> - .exchange().expectStatus().isOk().expectBody() - .consumeWith(document("users", requestParameters(// <2> - parameterWithName("page").description("The page to retrieve"), // <3> - parameterWithName("per_page").description("Entries per page") // <4> - ))); - // end::request-parameters-query-string[] - } - public void postFormDataSnippet() { - // tag::request-parameters-form-data[] + // tag::form-parameters[] MultiValueMap formData = new LinkedMultiValueMap<>(); formData.add("username", "Tester"); this.webTestClient.post().uri("/users").body(BodyInserters.fromFormData(formData)) // <1> - .exchange().expectStatus().isCreated().expectBody() - .consumeWith(document("create-user", requestParameters( - parameterWithName("username").description("The user's username") - ))); - // end::request-parameters-form-data[] + .exchange().expectStatus().isCreated().expectBody() + .consumeWith(document("create-user", formParameters(// <2> + parameterWithName("username").description("The user's username") // <3> + ))); + // end::form-parameters[] } } diff --git a/docs/src/test/java/com/example/webtestclient/QueryParameters.java b/docs/src/test/java/com/example/webtestclient/QueryParameters.java new file mode 100644 index 00000000..347c700b --- /dev/null +++ b/docs/src/test/java/com/example/webtestclient/QueryParameters.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014-2022 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 + * + * https://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 com.example.webtestclient; + +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; + +public class QueryParameters { + + // @formatter:off + + private WebTestClient webTestClient; + + public void getQueryStringSnippet() { + // tag::query-parameters[] + this.webTestClient.get().uri("/users?page=2&per_page=100") // <1> + .exchange().expectStatus().isOk().expectBody() + .consumeWith(document("users", queryParameters(// <2> + parameterWithName("page").description("The page to retrieve"), // <3> + parameterWithName("per_page").description("Entries per page") // <4> + ))); + // end::query-parameters[] + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java index 74f5979b..d3b2f9f2 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java @@ -24,13 +24,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.util.Base64Utils; @@ -65,15 +63,6 @@ final class CliOperationRequest implements OperationRequest { return null; } - Parameters getNonPartParameters() { - Parameters parameters = getParameters(); - Parameters nonPartParameters = new Parameters(); - nonPartParameters.putAll(parameters); - Set partNames = getParts().stream().map(OperationRequestPart::getName).collect(Collectors.toSet()); - nonPartParameters.keySet().removeAll(partNames); - return nonPartParameters; - } - @Override public byte[] getContent() { return this.delegate.getContent(); @@ -115,11 +104,6 @@ final class CliOperationRequest implements OperationRequest { return this.delegate.getMethod(); } - @Override - public Parameters getParameters() { - return this.delegate.getParameters(); - } - @Override public Collection getParts() { return this.delegate.getParts(); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java index 82b0e36f..358db25a 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java @@ -22,12 +22,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.springframework.http.HttpMethod; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; @@ -82,21 +81,9 @@ public class CurlRequestSnippet extends TemplatedSnippet { private String getUrl(Operation operation) { OperationRequest request = operation.getRequest(); - Parameters uniqueParameters = request.getParameters().getUniqueParameters(operation.getRequest().getUri()); - if (!uniqueParameters.isEmpty() && includeParametersInUri(request)) { - return String.format("'%s%s%s'", request.getUri(), - StringUtils.hasText(request.getUri().getRawQuery()) ? "&" : "?", uniqueParameters.toQueryString()); - } return String.format("'%s'", request.getUri()); } - private boolean includeParametersInUri(OperationRequest request) { - HttpMethod method = request.getMethod(); - return (method != HttpMethod.PUT && method != HttpMethod.POST && method != HttpMethod.PATCH) - || (request.getContent().length > 0 && !MediaType.APPLICATION_FORM_URLENCODED - .isCompatibleWith(request.getHeaders().getContentType())); - } - private String getOptions(Operation operation) { StringBuilder builder = new StringBuilder(); writeIncludeHeadersInOutputOption(builder); @@ -147,6 +134,10 @@ public class CurlRequestSnippet extends TemplatedSnippet { private void writeHeaders(CliOperationRequest request, List lines) { for (Entry> entry : request.getHeaders().entrySet()) { for (String header : entry.getValue()) { + if (StringUtils.hasText(request.getContentAsString()) && HttpHeaders.CONTENT_TYPE.equals(entry.getKey()) + && MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType())) { + continue; + } lines.add(String.format("-H '%s: %s'", entry.getKey(), header)); } } @@ -176,24 +167,6 @@ public class CurlRequestSnippet extends TemplatedSnippet { if (StringUtils.hasText(content)) { lines.add(String.format("-d '%s'", content)); } - else if (!request.getParts().isEmpty()) { - for (Entry> entry : request.getNonPartParameters().entrySet()) { - for (String value : entry.getValue()) { - lines.add(String.format("-F '%s=%s'", entry.getKey(), value)); - } - } - } - else if (request.isPutOrPost()) { - writeContentUsingParameters(request, lines); - } - } - - private void writeContentUsingParameters(OperationRequest request, List lines) { - Parameters uniqueParameters = request.getParameters().getUniqueParameters(request.getUri()); - String queryString = uniqueParameters.toQueryString(); - if (StringUtils.hasText(queryString)) { - lines.add(String.format("-d '%s'", queryString)); - } } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java index 314a8997..30912429 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java @@ -25,12 +25,11 @@ import java.util.Map; import java.util.Map.Entry; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.FormParameters; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; @@ -85,6 +84,9 @@ public class HttpieRequestSnippet extends TemplatedSnippet { } private Object getContentStandardIn(CliOperationRequest request) { + if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(request.getHeaders().getContentType())) { + return ""; + } String content = request.getContentAsString(); if (StringUtils.hasText(content)) { return String.format("echo '%s' | ", content); @@ -102,21 +104,15 @@ public class HttpieRequestSnippet extends TemplatedSnippet { } private String getUrl(OperationRequest request) { - Parameters uniqueParameters = request.getParameters().getUniqueParameters(request.getUri()); - if (!uniqueParameters.isEmpty() && includeParametersInUri(request)) { - return String.format("'%s%s%s'", request.getUri(), - StringUtils.hasText(request.getUri().getRawQuery()) ? "&" : "?", uniqueParameters.toQueryString()); - } return String.format("'%s'", request.getUri()); } private String getRequestItems(CliOperationRequest request) { List lines = new ArrayList<>(); - writeFormDataIfNecessary(request, lines); writeHeaders(request, lines); writeCookies(request, lines); - writeParametersIfNecessary(request, lines); + writeFormDataIfNecessary(request, lines); return this.commandFormatter.format(lines); } @@ -125,24 +121,11 @@ public class HttpieRequestSnippet extends TemplatedSnippet { if (!request.getParts().isEmpty()) { writer.print("--multipart "); } - else if (!request.getParameters().getUniqueParameters(request.getUri()).isEmpty() - && !includeParametersInUri(request) && includeParametersAsFormOptions(request)) { + else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(request.getHeaders().getContentType())) { writer.print("--form "); } } - private boolean includeParametersInUri(OperationRequest request) { - HttpMethod method = request.getMethod(); - return (method != HttpMethod.PUT && method != HttpMethod.POST && method != HttpMethod.PATCH) - || (request.getContent().length > 0 && !MediaType.APPLICATION_FORM_URLENCODED - .isCompatibleWith(request.getHeaders().getContentType())); - } - - private boolean includeParametersAsFormOptions(OperationRequest request) { - return request.getMethod() != HttpMethod.GET && (request.getContent().length == 0 - || !MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(request.getHeaders().getContentType())); - } - private void writeUserOptionIfNecessary(CliOperationRequest request, PrintWriter writer) { String credentials = request.getBasicAuthCredentials(); if (credentials != null) { @@ -155,23 +138,33 @@ public class HttpieRequestSnippet extends TemplatedSnippet { } private void writeFormDataIfNecessary(OperationRequest request, List lines) { - for (OperationRequestPart part : request.getParts()) { - StringBuilder oneLine = new StringBuilder(); - oneLine.append(String.format("'%s'", part.getName())); - if (!StringUtils.hasText(part.getSubmittedFileName())) { - oneLine.append(String.format("='%s'", part.getContentAsString())); - } - else { - oneLine.append(String.format("@'%s'", part.getSubmittedFileName())); - } + if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(request.getHeaders().getContentType())) { + FormParameters.from(request).forEach( + (key, values) -> values.forEach((value) -> lines.add(String.format("'%s=%s'", key, value)))); + } + else { + for (OperationRequestPart part : request.getParts()) { + StringBuilder oneLine = new StringBuilder(); + oneLine.append(String.format("'%s'", part.getName())); + if (!StringUtils.hasText(part.getSubmittedFileName())) { + oneLine.append(String.format("='%s'", part.getContentAsString())); + } + else { + oneLine.append(String.format("@'%s'", part.getSubmittedFileName())); + } - lines.add(oneLine.toString()); + lines.add(oneLine.toString()); + } } } private void writeHeaders(OperationRequest request, List lines) { HttpHeaders headers = request.getHeaders(); for (Entry> entry : headers.entrySet()) { + if (entry.getKey().equals(HttpHeaders.CONTENT_TYPE) + && headers.getContentType().isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) { + continue; + } for (String header : entry.getValue()) { // HTTPie adds Content-Type automatically with --form if (!request.getParts().isEmpty() && entry.getKey().equals(HttpHeaders.CONTENT_TYPE) @@ -189,29 +182,4 @@ public class HttpieRequestSnippet extends TemplatedSnippet { } } - private void writeParametersIfNecessary(CliOperationRequest request, List lines) { - if (StringUtils.hasText(request.getContentAsString())) { - return; - } - if (!request.getParts().isEmpty()) { - writeContentUsingParameters(request.getNonPartParameters(), lines); - } - else if (request.isPutOrPost()) { - writeContentUsingParameters(request.getParameters().getUniqueParameters(request.getUri()), lines); - } - } - - private void writeContentUsingParameters(Parameters parameters, List lines) { - for (Map.Entry> entry : parameters.entrySet()) { - if (entry.getValue().isEmpty()) { - lines.add(String.format("'%s='", entry.getKey())); - } - else { - for (String value : entry.getValue()) { - lines.add(String.format("'%s=%s'", entry.getKey(), value)); - } - } - } - } - } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java index 59d75af1..b375b626 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java @@ -23,8 +23,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -32,7 +30,6 @@ import org.springframework.http.MediaType; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.TemplatedSnippet; @@ -78,15 +75,6 @@ public class HttpRequestSnippet extends TemplatedSnippet { private String getPath(OperationRequest request) { String path = request.getUri().getRawPath(); String queryString = request.getUri().getRawQuery(); - Parameters uniqueParameters = request.getParameters().getUniqueParameters(request.getUri()); - if (!uniqueParameters.isEmpty() && includeParametersInUri(request)) { - if (StringUtils.hasText(queryString)) { - queryString = queryString + "&" + uniqueParameters.toQueryString(); - } - else { - queryString = uniqueParameters.toQueryString(); - } - } if (StringUtils.hasText(queryString)) { path = path + "?" + queryString; } @@ -133,14 +121,7 @@ public class HttpRequestSnippet extends TemplatedSnippet { writer.printf("%n%s", content); } else if (isPutOrPost(request)) { - if (request.getParts().isEmpty()) { - String queryString = request.getParameters().getUniqueParameters(request.getUri()).toQueryString(); - if (StringUtils.hasText(queryString)) { - writer.println(); - writer.print(queryString); - } - } - else { + if (!request.getParts().isEmpty()) { writeParts(request, writer); } } @@ -153,23 +134,6 @@ public class HttpRequestSnippet extends TemplatedSnippet { private void writeParts(OperationRequest request, PrintWriter writer) { writer.println(); - Set partNames = request.getParts().stream().map(OperationRequestPart::getName) - .collect(Collectors.toSet()); - for (Entry> parameter : request.getParameters().entrySet()) { - if (!partNames.contains(parameter.getKey())) { - if (parameter.getValue().isEmpty()) { - writePartBoundary(writer); - writePart(parameter.getKey(), "", null, null, writer); - } - else { - for (String value : parameter.getValue()) { - writePartBoundary(writer); - writePart(parameter.getKey(), value, null, null, writer); - writer.println(); - } - } - } - } for (OperationRequestPart part : request.getParts()) { writePartBoundary(writer); writePart(part, writer); @@ -206,7 +170,6 @@ public class HttpRequestSnippet extends TemplatedSnippet { private boolean requiresFormEncodingContentTypeHeader(OperationRequest request) { return request.getHeaders().get(HttpHeaders.CONTENT_TYPE) == null && isPutOrPost(request) - && !request.getParameters().getUniqueParameters(request.getUri()).isEmpty() && !includeParametersInUri(request); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryStringParser.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/FormParameters.java similarity index 55% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryStringParser.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/FormParameters.java index 49d637dd..5cabb84e 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryStringParser.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/FormParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -17,36 +17,46 @@ package org.springframework.restdocs.operation; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.net.URLDecoder; import java.util.LinkedList; import java.util.List; import java.util.Scanner; +import org.springframework.util.LinkedMultiValueMap; + /** - * A parser for the query string of a URI. + * A request's form parameters, derived from its form URL encoded body content. * * @author Andy Wilkinson + * @since 3.0.0 */ -public class QueryStringParser { +public final class FormParameters extends LinkedMultiValueMap { + + private FormParameters() { - /** - * Parses the query string of the given {@code uri} and returns the resulting - * {@link Parameters}. - * @param uri the uri to parse - * @return the parameters parsed from the query string - */ - public Parameters parse(URI uri) { - String query = uri.getRawQuery(); - if (query != null) { - return parse(query); - } - return new Parameters(); } - private Parameters parse(String query) { - Parameters parameters = new Parameters(); - try (Scanner scanner = new Scanner(query)) { + /** + * Extracts the form parameters from the body of the given {@code request}. If the + * request has no body content, an empty {@code FormParameters} is returned, rather + * than {@code null}. + * @param request the request + * @return the form parameters extracted from the body content + */ + public static FormParameters from(OperationRequest request) { + return of(request.getContentAsString()); + } + + private static FormParameters of(String bodyContent) { + if (bodyContent == null || bodyContent.length() == 0) { + return new FormParameters(); + } + return parse(bodyContent); + } + + private static FormParameters parse(String bodyContent) { + FormParameters parameters = new FormParameters(); + try (Scanner scanner = new Scanner(bodyContent)) { scanner.useDelimiter("&"); while (scanner.hasNext()) { processParameter(scanner.next(), parameters); @@ -55,7 +65,7 @@ public class QueryStringParser { return parameters; } - private void processParameter(String parameter, Parameters parameters) { + private static void processParameter(String parameter, FormParameters parameters) { String[] components = parameter.split("="); if (components.length > 0 && components.length < 3) { if (components.length == 2) { @@ -64,7 +74,7 @@ public class QueryStringParser { parameters.add(decode(name), decode(value)); } else { - List values = parameters.computeIfAbsent(components[0], (p) -> new LinkedList()); + List values = parameters.computeIfAbsent(components[0], (p) -> new LinkedList<>()); values.add(""); } } @@ -73,12 +83,12 @@ public class QueryStringParser { } } - private String decode(String encoded) { + private static String decode(String encoded) { try { return URLDecoder.decode(encoded, "UTF-8"); } catch (UnsupportedEncodingException ex) { - throw new IllegalStateException("Unable to URL encode " + encoded + " using UTF-8", ex); + throw new IllegalStateException("Unable to URL decode " + encoded + " using UTF-8", ex); } } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java index c8b6fb35..7e36c2f3 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2014-2022 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. @@ -58,14 +58,6 @@ public interface OperationRequest { */ HttpMethod getMethod(); - /** - * Returns the request's parameters. For a {@code GET} request, the parameters are - * derived from the query string. For a {@code POST} request, the parameters are - * derived form the request's body. - * @return the parameters - */ - Parameters getParameters(); - /** * Returns the request's parts, provided that it is a multipart request. If not, then * an empty {@link Collection} is returned. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java index 69eb7c2a..8d91c356 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/OperationRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. @@ -17,7 +17,6 @@ package org.springframework.restdocs.operation; import java.net.URI; -import java.net.URISyntaxException; import java.util.Collection; import java.util.Collections; @@ -39,15 +38,15 @@ public class OperationRequestFactory { * @param method the request method * @param content the content of the request * @param headers the request's headers - * @param parameters the request's parameters * @param parts the request's parts * @param cookies the request's cookies * @return the {@code OperationRequest} + * @since 3.0.0 */ public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, - Parameters parameters, Collection parts, Collection cookies) { - return new StandardOperationRequest(uri, method, content, augmentHeaders(headers, uri, content), parameters, - parts, cookies); + Collection parts, Collection cookies) { + return new StandardOperationRequest(uri, method, content, augmentHeaders(headers, uri, content), + (parts != null) ? parts : Collections.emptyList(), cookies); } /** @@ -58,13 +57,13 @@ public class OperationRequestFactory { * @param method the request method * @param content the content of the request * @param headers the request's headers - * @param parameters the request's parameters * @param parts the request's parts * @return the {@code OperationRequest} + * @since 3.0.0 */ public OperationRequest create(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, - Parameters parameters, Collection parts) { - return create(uri, method, content, headers, parameters, parts, Collections.emptyList()); + Collection parts) { + return create(uri, method, content, headers, parts, Collections.emptyList()); } /** @@ -77,8 +76,7 @@ public class OperationRequestFactory { */ public OperationRequest createFrom(OperationRequest original, byte[] newContent) { return new StandardOperationRequest(original.getUri(), original.getMethod(), newContent, - getUpdatedHeaders(original.getHeaders(), newContent), original.getParameters(), original.getParts(), - original.getCookies()); + getUpdatedHeaders(original.getHeaders(), newContent), original.getParts(), original.getCookies()); } /** @@ -90,33 +88,7 @@ public class OperationRequestFactory { */ public OperationRequest createFrom(OperationRequest original, HttpHeaders newHeaders) { return new StandardOperationRequest(original.getUri(), original.getMethod(), original.getContent(), newHeaders, - original.getParameters(), original.getParts(), original.getCookies()); - } - - /** - * Creates a new {@code OperationRequest} based on the given {@code original} but with - * the given {@code newParameters} applied. The query string of a {@code GET} request - * will be updated to reflect the new parameters. - * @param original the original request - * @param newParameters the new parameters - * @return the new request with the parameters applied - */ - public OperationRequest createFrom(OperationRequest original, Parameters newParameters) { - URI uri = (original.getMethod() == HttpMethod.GET) ? updateQueryString(original.getUri(), newParameters) - : original.getUri(); - return new StandardOperationRequest(uri, original.getMethod(), original.getContent(), original.getHeaders(), - newParameters, original.getParts(), original.getCookies()); - } - - private URI updateQueryString(URI originalUri, Parameters parameters) { - try { - return new URI(originalUri.getScheme(), originalUri.getUserInfo(), originalUri.getHost(), - originalUri.getPort(), originalUri.getPath(), - parameters.isEmpty() ? null : parameters.toQueryString(), originalUri.getFragment()); - } - catch (URISyntaxException ex) { - throw new RuntimeException(ex); - } + original.getParts(), original.getCookies()); } private HttpHeaders augmentHeaders(HttpHeaders originalHeaders, URI uri, byte[] content) { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java deleted file mode 100644 index 0e3558e5..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2014-2019 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 - * - * https://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.operation; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.util.List; -import java.util.Map; - -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.StringUtils; - -/** - * The parameters received in a request. - * - * @author Andy Wilkinson - */ -@SuppressWarnings("serial") -public class Parameters extends LinkedMultiValueMap { - - /** - * Converts the parameters to a query string suitable for use in a URI or the body of - * a form-encoded request. - * @return the query string - */ - public String toQueryString() { - StringBuilder sb = new StringBuilder(); - for (Map.Entry> entry : entrySet()) { - if (entry.getValue().isEmpty()) { - append(sb, entry.getKey()); - } - else { - for (String value : entry.getValue()) { - append(sb, entry.getKey(), value); - } - } - } - return sb.toString(); - } - - /** - * Returns a new {@code Parameters} containing only the parameters that do no appear - * in the query string of the given {@code uri}. - * @param uri the uri - * @return the unique parameters - */ - public Parameters getUniqueParameters(URI uri) { - Parameters queryStringParameters = new QueryStringParser().parse(uri); - Parameters uniqueParameters = new Parameters(); - - for (Map.Entry> parameter : entrySet()) { - addIfUnique(parameter, queryStringParameters, uniqueParameters); - } - return uniqueParameters; - } - - private void addIfUnique(Map.Entry> parameter, Parameters queryStringParameters, - Parameters uniqueParameters) { - if (!queryStringParameters.containsKey(parameter.getKey())) { - uniqueParameters.put(parameter.getKey(), parameter.getValue()); - } - else { - List candidates = parameter.getValue(); - List existing = queryStringParameters.get(parameter.getKey()); - for (String candidate : candidates) { - if (!existing.contains(candidate)) { - uniqueParameters.add(parameter.getKey(), candidate); - } - } - } - } - - private static void append(StringBuilder sb, String key) { - append(sb, key, ""); - } - - private static void append(StringBuilder sb, String key, String value) { - doAppend(sb, urlEncodeUTF8(key) + "=" + urlEncodeUTF8(value)); - } - - private static void doAppend(StringBuilder sb, String toAppend) { - if (sb.length() > 0) { - sb.append("&"); - } - sb.append(toAppend); - } - - private static String urlEncodeUTF8(String s) { - if (!StringUtils.hasLength(s)) { - return ""; - } - try { - return URLEncoder.encode(s, "UTF-8"); - } - catch (UnsupportedEncodingException ex) { - throw new IllegalStateException("Unable to URL encode " + s + " using UTF-8", ex); - } - } - -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryParameters.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryParameters.java new file mode 100644 index 00000000..17f7fc1c --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/QueryParameters.java @@ -0,0 +1,90 @@ +/* + * Copyright 2014-2022 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 + * + * https://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.operation; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.Scanner; + +import org.springframework.util.LinkedMultiValueMap; + +/** + * A request's query parameters, derived from its URI's query string. + * + * @author Andy Wilkinson + * @since 3.0.0 + */ +public final class QueryParameters extends LinkedMultiValueMap { + + private QueryParameters() { + + } + + /** + * Extracts the query parameters from the query string of the given {@code request}. + * If the request has no query string, an empty {@code QueryParameters} is returned, + * rather than {@code null}. + * @param request the request + * @return the query parameters extracted from the request's query string + */ + public static QueryParameters from(OperationRequest request) { + return from(request.getUri().getRawQuery()); + } + + private static QueryParameters from(String queryString) { + if (queryString == null || queryString.length() == 0) { + return new QueryParameters(); + } + return parse(queryString); + } + + private static QueryParameters parse(String query) { + QueryParameters parameters = new QueryParameters(); + try (Scanner scanner = new Scanner(query)) { + scanner.useDelimiter("&"); + while (scanner.hasNext()) { + processParameter(scanner.next(), parameters); + } + } + return parameters; + } + + private static void processParameter(String parameter, QueryParameters parameters) { + String[] components = parameter.split("="); + if (components.length > 0 && components.length < 3) { + if (components.length == 2) { + String name = components[0]; + String value = components[1]; + parameters.add(decode(name), decode(value)); + } + else { + List values = parameters.computeIfAbsent(components[0], (p) -> new LinkedList<>()); + values.add(""); + } + } + else { + throw new IllegalArgumentException("The parameter '" + parameter + "' is malformed"); + } + } + + private static String decode(String encoded) { + return URLDecoder.decode(encoded, StandardCharsets.UTF_8); + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java index 204ab28f..bdf65934 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/StandardOperationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -32,8 +32,6 @@ class StandardOperationRequest extends AbstractOperationMessage implements Opera private HttpMethod method; - private Parameters parameters; - private Collection parts; private URI uri; @@ -48,16 +46,14 @@ class StandardOperationRequest extends AbstractOperationMessage implements Opera * @param method the method * @param content the content * @param headers the headers - * @param parameters the parameters * @param parts the parts * @param cookies the cookies */ - StandardOperationRequest(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, Parameters parameters, + StandardOperationRequest(URI uri, HttpMethod method, byte[] content, HttpHeaders headers, Collection parts, Collection cookies) { super(content, headers); this.uri = uri; this.method = method; - this.parameters = parameters; this.parts = parts; this.cookies = cookies; } @@ -67,11 +63,6 @@ class StandardOperationRequest extends AbstractOperationMessage implements Opera return this.method; } - @Override - public Parameters getParameters() { - return this.parameters; - } - @Override public Collection getParts() { return Collections.unmodifiableCollection(this.parts); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java index 231f4e2c..ef2f2223 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessor.java @@ -134,9 +134,9 @@ public class UriModifyingOperationPreprocessor implements OperationPreprocessor HttpHeaders modifiedHeaders = modify(request.getHeaders()); modifiedHeaders.set(HttpHeaders.HOST, modifiedUri.getHost() + ((modifiedUri.getPort() != -1) ? ":" + modifiedUri.getPort() : "")); - return this.contentModifyingDelegate.preprocess(new OperationRequestFactory().create( - uriBuilder.build(true).toUri(), request.getMethod(), request.getContent(), modifiedHeaders, - request.getParameters(), modify(request.getParts()), request.getCookies())); + return this.contentModifyingDelegate + .preprocess(new OperationRequestFactory().create(uriBuilder.build(true).toUri(), request.getMethod(), + request.getContent(), modifiedHeaders, modify(request.getParts()), request.getCookies())); } @Override diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java similarity index 62% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java index b1a1afc1..8d5401d5 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestParametersSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/FormParametersSnippet.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. @@ -22,36 +22,33 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.restdocs.operation.FormParameters; import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.snippet.Snippet; import org.springframework.restdocs.snippet.SnippetException; /** - * A {@link Snippet} that documents the request parameters supported by a RESTful - * resource. - *

- * Request parameters are sent as part of the query string or as POSTed form data. + * A {@link Snippet} that documents the form parameters supported by a RESTful resource. * * @author Andy Wilkinson - * @see OperationRequest#getParameters() - * @see RequestDocumentation#requestParameters(ParameterDescriptor...) - * @see RequestDocumentation#requestParameters(Map, ParameterDescriptor...) + * @since 3.0.0 + * @see RequestDocumentation#formParameters(ParameterDescriptor...) + * @see RequestDocumentation#formParameters(Map, ParameterDescriptor...) */ -public class RequestParametersSnippet extends AbstractParametersSnippet { +public class FormParametersSnippet extends AbstractParametersSnippet { /** - * Creates a new {@code RequestParametersSnippet} that will document the request's + * Creates a new {@code FormParametersSnippet} that will document the request's form * parameters using the given {@code descriptors}. Undocumented parameters will * trigger a failure. * @param descriptors the parameter descriptors */ - protected RequestParametersSnippet(List descriptors) { + protected FormParametersSnippet(List descriptors) { this(descriptors, null, false); } /** - * Creates a new {@code RequestParametersSnippet} that will document the request's + * Creates a new {@code FormParametersSnippet} that will document the request's form * parameters using the given {@code descriptors}. If * {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will * be ignored and will not trigger a failure. @@ -59,24 +56,24 @@ public class RequestParametersSnippet extends AbstractParametersSnippet { * @param ignoreUndocumentedParameters whether undocumented parameters should be * ignored */ - protected RequestParametersSnippet(List descriptors, boolean ignoreUndocumentedParameters) { + protected FormParametersSnippet(List descriptors, boolean ignoreUndocumentedParameters) { this(descriptors, null, ignoreUndocumentedParameters); } /** - * Creates a new {@code RequestParametersSnippet} that will document the request's + * Creates a new {@code FormParametersSnippet} that will document the request's form * parameters using the given {@code descriptors}. The given {@code attributes} will * be included in the model during template rendering. Undocumented parameters will * trigger a failure. * @param descriptors the parameter descriptors * @param attributes the additional attributes */ - protected RequestParametersSnippet(List descriptors, Map attributes) { + protected FormParametersSnippet(List descriptors, Map attributes) { this(descriptors, attributes, false); } /** - * Creates a new {@code RequestParametersSnippet} that will document the request's + * Creates a new {@code FormParametersSnippet} that will document the request's form * parameters using the given {@code descriptors}. The given {@code attributes} will * be included in the model during template rendering. If * {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will @@ -86,54 +83,53 @@ public class RequestParametersSnippet extends AbstractParametersSnippet { * @param ignoreUndocumentedParameters whether undocumented parameters should be * ignored */ - protected RequestParametersSnippet(List descriptors, Map attributes, + protected FormParametersSnippet(List descriptors, Map attributes, boolean ignoreUndocumentedParameters) { - super("request-parameters", descriptors, attributes, ignoreUndocumentedParameters); + super("form-parameters", descriptors, attributes, ignoreUndocumentedParameters); } @Override protected void verificationFailed(Set undocumentedParameters, Set missingParameters) { String message = ""; if (!undocumentedParameters.isEmpty()) { - message += "Request parameters with the following names were not documented: " + undocumentedParameters; + message += "Form parameters with the following names were not documented: " + undocumentedParameters; } if (!missingParameters.isEmpty()) { if (message.length() > 0) { message += ". "; } - message += "Request parameters with the following names were not found in the request: " - + missingParameters; + message += "Form parameters with the following names were not found in the request: " + missingParameters; } throw new SnippetException(message); } @Override protected Set extractActualParameters(Operation operation) { - return operation.getRequest().getParameters().keySet(); + return FormParameters.from(operation.getRequest()).keySet(); } /** - * Returns a new {@code RequestParametersSnippet} configured with this snippet's + * Returns a new {@code FormParametersSnippet} configured with this snippet's * attributes and its descriptors combined with the given * {@code additionalDescriptors}. * @param additionalDescriptors the additional descriptors * @return the new snippet */ - public RequestParametersSnippet and(ParameterDescriptor... additionalDescriptors) { + public FormParametersSnippet and(ParameterDescriptor... additionalDescriptors) { return and(Arrays.asList(additionalDescriptors)); } /** - * Returns a new {@code RequestParametersSnippet} configured with this snippet's + * Returns a new {@code FormParametersSnippet} configured with this snippet's * attributes and its descriptors combined with the given * {@code additionalDescriptors}. * @param additionalDescriptors the additional descriptors * @return the new snippet */ - public RequestParametersSnippet and(List additionalDescriptors) { + public FormParametersSnippet and(List additionalDescriptors) { List combinedDescriptors = new ArrayList<>(getParameterDescriptors().values()); combinedDescriptors.addAll(additionalDescriptors); - return new RequestParametersSnippet(combinedDescriptors, this.getAttributes(), + return new FormParametersSnippet(combinedDescriptors, this.getAttributes(), this.isIgnoreUndocumentedParameters()); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java new file mode 100644 index 00000000..2485c2f3 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/QueryParametersSnippet.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014-2022 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 + * + * https://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.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.QueryParameters; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.SnippetException; + +/** + * A {@link Snippet} that documents the query parameters supported by a RESTful resource. + * + * @author Andy Wilkinson + * @since 3.0.0 + * @see RequestDocumentation#queryParameters(ParameterDescriptor...) + * @see RequestDocumentation#queryParameters(Map, ParameterDescriptor...) + */ +public class QueryParametersSnippet extends AbstractParametersSnippet { + + /** + * Creates a new {@code QueryParametersSnippet} that will document the request's query + * parameters using the given {@code descriptors}. Undocumented parameters will + * trigger a failure. + * @param descriptors the parameter descriptors + */ + protected QueryParametersSnippet(List descriptors) { + this(descriptors, null, false); + } + + /** + * Creates a new {@code QueryParametersSnippet} that will document the request's query + * parameters using the given {@code descriptors}. If + * {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will + * be ignored and will not trigger a failure. + * @param descriptors the parameter descriptors + * @param ignoreUndocumentedParameters whether undocumented parameters should be + * ignored + */ + protected QueryParametersSnippet(List descriptors, boolean ignoreUndocumentedParameters) { + this(descriptors, null, ignoreUndocumentedParameters); + } + + /** + * Creates a new {@code QueryParametersSnippet} that will document the request's query + * parameters using the given {@code descriptors}. The given {@code attributes} will + * be included in the model during template rendering. Undocumented parameters will + * trigger a failure. + * @param descriptors the parameter descriptors + * @param attributes the additional attributes + */ + protected QueryParametersSnippet(List descriptors, Map attributes) { + this(descriptors, attributes, false); + } + + /** + * Creates a new {@code QueryParametersSnippet} that will document the request's query + * parameters using the given {@code descriptors}. The given {@code attributes} will + * be included in the model during template rendering. If + * {@code ignoreUndocumentedParameters} is {@code true}, undocumented parameters will + * be ignored and will not trigger a failure. + * @param descriptors the parameter descriptors + * @param attributes the additional attributes + * @param ignoreUndocumentedParameters whether undocumented parameters should be + * ignored + */ + protected QueryParametersSnippet(List descriptors, Map attributes, + boolean ignoreUndocumentedParameters) { + super("query-parameters", descriptors, attributes, ignoreUndocumentedParameters); + } + + @Override + protected void verificationFailed(Set undocumentedParameters, Set missingParameters) { + 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 SnippetException(message); + } + + @Override + protected Set extractActualParameters(Operation operation) { + return QueryParameters.from(operation.getRequest()).keySet(); + } + + /** + * Returns a new {@code QueryParametersSnippet} configured with this snippet's + * attributes and its descriptors combined with the given + * {@code additionalDescriptors}. + * @param additionalDescriptors the additional descriptors + * @return the new snippet + */ + public QueryParametersSnippet and(ParameterDescriptor... additionalDescriptors) { + return and(Arrays.asList(additionalDescriptors)); + } + + /** + * Returns a new {@code QueryParametersSnippet} configured with this snippet's + * attributes and its descriptors combined with the given + * {@code additionalDescriptors}. + * @param additionalDescriptors the additional descriptors + * @return the new snippet + */ + public QueryParametersSnippet and(List additionalDescriptors) { + List combinedDescriptors = new ArrayList<>(getParameterDescriptors().values()); + combinedDescriptors.addAll(additionalDescriptors); + return new QueryParametersSnippet(combinedDescriptors, this.getAttributes(), + this.isIgnoreUndocumentedParameters()); + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java index 3c051fbc..221006b8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/request/RequestDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -203,159 +203,321 @@ public abstract class RequestDocumentation { } /** - * Returns a {@code Snippet} that will document the parameters from the API - * operation's request. The parameters will be documented using the given + * Returns a {@code Snippet} that will document the query parameters from the API + * operation's request. The query parameters will be documented using the given * {@code descriptors}. *

- * If a parameter is present in the request, but is not documented by one of the + * If a query parameter is present in the request, but is not documented by one of the * descriptors, a failure will occur when the snippet is invoked. Similarly, if a - * parameter is documented, is not marked as optional, and is not present in the + * query parameter is documented, is not marked as optional, and is not present in the * request, a failure will also occur. *

- * If you do not want to document a request parameter, a parameter descriptor can be + * If you do not want to document a query parameter, a parameter descriptor can be * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from * appearing in the generated snippet while avoiding the failure described above. - * @param descriptors the descriptions of the request's parameters + * @param descriptors the descriptions of the request's query parameters * @return the snippet - * @see OperationRequest#getParameters() + * @since 3.0.0 */ - public static RequestParametersSnippet requestParameters(ParameterDescriptor... descriptors) { - return requestParameters(Arrays.asList(descriptors)); + public static QueryParametersSnippet queryParameters(ParameterDescriptor... descriptors) { + return queryParameters(Arrays.asList(descriptors)); } /** - * Returns a {@code Snippet} that will document the parameters from the API - * operation's request. The parameters will be documented using the given + * Returns a {@code Snippet} that will document the query parameters from the API + * operation's request. The query parameters will be documented using the given * {@code descriptors}. *

- * If a parameter is present in the request, but is not documented by one of the + * If a query parameter is present in the request, but is not documented by one of the * descriptors, a failure will occur when the snippet is invoked. Similarly, if a - * parameter is documented, is not marked as optional, and is not present in the + * query parameter is documented, is not marked as optional, and is not present in the * request, a failure will also occur. *

- * If you do not want to document a request parameter, a parameter descriptor can be + * If you do not want to document a query parameter, a parameter descriptor can be * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from * appearing in the generated snippet while avoiding the failure described above. - * @param descriptors the descriptions of the request's parameters + * @param descriptors the descriptions of the request's query parameters * @return the snippet - * @see OperationRequest#getParameters() + * @since 3.0.0 */ - public static RequestParametersSnippet requestParameters(List descriptors) { - return new RequestParametersSnippet(descriptors); + public static QueryParametersSnippet queryParameters(List descriptors) { + return new QueryParametersSnippet(descriptors); } /** - * Returns a {@code Snippet} that will document the parameters from the API - * operation's request. The parameters will be documented using the given + * Returns a {@code Snippet} that will document the query parameters from the API + * operation's request. The query parameters will be documented using the given + * {@code descriptors}. + *

+ * If a query parameter is documented, is not marked as optional, and is not present + * in the response, a failure will occur. Any undocumented query parameters will be + * ignored. + * @param descriptors the descriptions of the request's query parameters + * @return the snippet + * @since 3.0.0 + */ + public static QueryParametersSnippet relaxedQueryParameters(ParameterDescriptor... descriptors) { + return relaxedQueryParameters(Arrays.asList(descriptors)); + } + + /** + * Returns a {@code Snippet} that will document the query parameters from the API + * operation's request. The query parameters will be documented using the given * {@code descriptors}. *

* If a parameter is documented, is not marked as optional, and is not present in the - * response, a failure will occur. Any undocumented parameters will be ignored. - * @param descriptors the descriptions of the request's parameters + * response, a failure will occur. Any undocumented query parameters will be ignored. + * @param descriptors the descriptions of the request's query parameters * @return the snippet - * @see OperationRequest#getParameters() + * @since 3.0.0 */ - public static RequestParametersSnippet relaxedRequestParameters(ParameterDescriptor... descriptors) { - return relaxedRequestParameters(Arrays.asList(descriptors)); + public static QueryParametersSnippet relaxedQueryParameters(List descriptors) { + return new QueryParametersSnippet(descriptors, true); } /** - * Returns a {@code Snippet} that will document the parameters from the API - * operation's request. The parameters will be documented using the given - * {@code descriptors}. - *

- * If a parameter is documented, is not marked as optional, and is not present in the - * response, a failure will occur. Any undocumented parameters will be ignored. - * @param descriptors the descriptions of the request's parameters - * @return the snippet - * @see OperationRequest#getParameters() - */ - public static RequestParametersSnippet relaxedRequestParameters(List descriptors) { - return new RequestParametersSnippet(descriptors, true); - } - - /** - * Returns a {@code Snippet} that will document the parameters from the API + * Returns a {@code Snippet} that will document the query parameters from the API * operation's request. The given {@code attributes} will be available during snippet - * rendering and the parameters will be documented using the given {@code descriptors} - * . + * rendering and the query parameters will be documented using the given + * {@code descriptors} . *

- * If a parameter is present in the request, but is not documented by one of the + * If a query parameter is present in the request, but is not documented by one of the * descriptors, a failure will occur when the snippet is invoked. Similarly, if a - * parameter is documented, is not marked as optional, and is not present in the + * query parameter is documented, is not marked as optional, and is not present in the * request, a failure will also occur. *

- * If you do not want to document a request parameter, a parameter descriptor can be + * If you do not want to document a query parameter, a parameter descriptor can be * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from * appearing in the generated snippet while avoiding the failure described above. * @param attributes the attributes - * @param descriptors the descriptions of the request's parameters - * @return the snippet that will document the parameters - * @see OperationRequest#getParameters() + * @param descriptors the descriptions of the request's query parameters + * @return the snippet that will document the query parameters + * @since 3.0.0 */ - public static RequestParametersSnippet requestParameters(Map attributes, + public static QueryParametersSnippet queryParameters(Map attributes, ParameterDescriptor... descriptors) { - return requestParameters(attributes, Arrays.asList(descriptors)); + return queryParameters(attributes, Arrays.asList(descriptors)); } /** - * Returns a {@code Snippet} that will document the parameters from the API + * Returns a {@code Snippet} that will document the query parameters from the API * operation's request. The given {@code attributes} will be available during snippet - * rendering and the parameters will be documented using the given {@code descriptors} - * . + * rendering and the query parameters will be documented using the given + * {@code descriptors} . *

- * If a parameter is present in the request, but is not documented by one of the + * If a query parameter is present in the request, but is not documented by one of the * descriptors, a failure will occur when the snippet is invoked. Similarly, if a - * parameter is documented, is not marked as optional, and is not present in the + * query parameter is documented, is not marked as optional, and is not present in the * request, a failure will also occur. *

- * If you do not want to document a request parameter, a parameter descriptor can be + * If you do not want to document a query parameter, a parameter descriptor can be * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from * appearing in the generated snippet while avoiding the failure described above. * @param attributes the attributes - * @param descriptors the descriptions of the request's parameters - * @return the snippet that will document the parameters - * @see OperationRequest#getParameters() + * @param descriptors the descriptions of the request's query parameters + * @return the snippet that will document the query parameters + * @since 3.0.0 */ - public static RequestParametersSnippet requestParameters(Map attributes, + public static QueryParametersSnippet queryParameters(Map attributes, List descriptors) { - return new RequestParametersSnippet(descriptors, attributes); + return new QueryParametersSnippet(descriptors, attributes); } /** - * Returns a {@code Snippet} that will document the parameters from the API + * Returns a {@code Snippet} that will document the query parameters from the API * operation's request. The given {@code attributes} will be available during snippet - * rendering and the parameters will be documented using the given {@code descriptors} - * . + * rendering and the query parameters will be documented using the given + * {@code descriptors} . *

- * If a parameter is documented, is not marked as optional, and is not present in the - * response, a failure will occur. Any undocumented parameters will be ignored. + * If a query parameter is documented, is not marked as optional, and is not present + * in the response, a failure will occur. Any undocumented query parameters will be + * ignored. * @param attributes the attributes - * @param descriptors the descriptions of the request's parameters - * @return the snippet that will document the parameters - * @see OperationRequest#getParameters() + * @param descriptors the descriptions of the request's query parameters + * @return the snippet that will document the query parameters + * @since 3.0.0 */ - public static RequestParametersSnippet relaxedRequestParameters(Map attributes, + public static QueryParametersSnippet relaxedQueryParameters(Map attributes, ParameterDescriptor... descriptors) { - return relaxedRequestParameters(attributes, Arrays.asList(descriptors)); + return relaxedQueryParameters(attributes, Arrays.asList(descriptors)); } /** - * Returns a {@code Snippet} that will document the parameters from the API + * Returns a {@code Snippet} that will document the query parameters from the API * operation's request. The given {@code attributes} will be available during snippet - * rendering and the parameters will be documented using the given {@code descriptors} - * . + * rendering and the query parameters will be documented using the given + * {@code descriptors} . + *

+ * If a query parameter is documented, is not marked as optional, and is not present + * in the response, a failure will occur. Any undocumented query parameters will be + * ignored. + * @param attributes the attributes + * @param descriptors the descriptions of the request's query parameters + * @return the snippet that will document the query parameters + * @since 3.0.0 + */ + public static QueryParametersSnippet relaxedQueryParameters(Map attributes, + List descriptors) { + return new QueryParametersSnippet(descriptors, attributes, true); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The form parameters will be documented using the given + * {@code descriptors}. + *

+ * If a form parameter is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a form + * parameter is documented, is not marked as optional, and is not present in the + * request, a failure will also occur. + *

+ * If you do not want to document a form parameter, a parameter descriptor can be + * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from + * appearing in the generated snippet while avoiding the failure described above. + * @param descriptors the descriptions of the request's form parameters + * @return the snippet + * @since 3.0.0 + */ + public static FormParametersSnippet formParameters(ParameterDescriptor... descriptors) { + return formParameters(Arrays.asList(descriptors)); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The form parameters will be documented using the given + * {@code descriptors}. + *

+ * If a form parameter is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a form + * parameter is documented, is not marked as optional, and is not present in the + * request, a failure will also occur. + *

+ * If you do not want to document a form parameter, a parameter descriptor can be + * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from + * appearing in the generated snippet while avoiding the failure described above. + * @param descriptors the descriptions of the request's form parameters + * @return the snippet + * @since 3.0.0 + */ + public static FormParametersSnippet formParameters(List descriptors) { + return new FormParametersSnippet(descriptors); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The form parameters will be documented using the given + * {@code descriptors}. + *

+ * If a form parameter is documented, is not marked as optional, and is not present in + * the response, a failure will occur. Any undocumented form parameters will be + * ignored. + * @param descriptors the descriptions of the request's form parameters + * @return the snippet + * @since 3.0.0 + */ + public static FormParametersSnippet relaxedFormParameters(ParameterDescriptor... descriptors) { + return relaxedFormParameters(Arrays.asList(descriptors)); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The form parameters will be documented using the given + * {@code descriptors}. *

* If a parameter is documented, is not marked as optional, and is not present in the - * response, a failure will occur. Any undocumented parameters will be ignored. - * @param attributes the attributes - * @param descriptors the descriptions of the request's parameters - * @return the snippet that will document the parameters - * @see OperationRequest#getParameters() + * response, a failure will occur. Any undocumented form parameters will be ignored. + * @param descriptors the descriptions of the request's form parameters + * @return the snippet + * @since 3.0.0 */ - public static RequestParametersSnippet relaxedRequestParameters(Map attributes, + public static FormParametersSnippet relaxedFormParameters(List descriptors) { + return new FormParametersSnippet(descriptors, true); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The given {@code attributes} will be available during snippet + * rendering and the form parameters will be documented using the given + * {@code descriptors} . + *

+ * If a form parameter is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a form + * parameter is documented, is not marked as optional, and is not present in the + * request, a failure will also occur. + *

+ * If you do not want to document a form parameter, a parameter descriptor can be + * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from + * appearing in the generated snippet while avoiding the failure described above. + * @param attributes the attributes + * @param descriptors the descriptions of the request's form parameters + * @return the snippet that will document the form parameters + * @since 3.0.0 + */ + public static FormParametersSnippet formParameters(Map attributes, + ParameterDescriptor... descriptors) { + return formParameters(attributes, Arrays.asList(descriptors)); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The given {@code attributes} will be available during snippet + * rendering and the form parameters will be documented using the given + * {@code descriptors} . + *

+ * If a form parameter is present in the request, but is not documented by one of the + * descriptors, a failure will occur when the snippet is invoked. Similarly, if a form + * parameter is documented, is not marked as optional, and is not present in the + * request, a failure will also occur. + *

+ * If you do not want to document a form parameter, a parameter descriptor can be + * marked as {@link ParameterDescriptor#ignored()}. This will prevent it from + * appearing in the generated snippet while avoiding the failure described above. + * @param attributes the attributes + * @param descriptors the descriptions of the request's form parameters + * @return the snippet that will document the form parameters + * @since 3.0.0 + */ + public static FormParametersSnippet formParameters(Map attributes, List descriptors) { - return new RequestParametersSnippet(descriptors, attributes, true); + return new FormParametersSnippet(descriptors, attributes); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The given {@code attributes} will be available during snippet + * rendering and the form parameters will be documented using the given + * {@code descriptors} . + *

+ * If a form parameter is documented, is not marked as optional, and is not present in + * the response, a failure will occur. Any undocumented form parameters will be + * ignored. + * @param attributes the attributes + * @param descriptors the descriptions of the request's form parameters + * @return the snippet that will document the form parameters + * @since 3.0.0 + */ + public static FormParametersSnippet relaxedFormParameters(Map attributes, + ParameterDescriptor... descriptors) { + return relaxedFormParameters(attributes, Arrays.asList(descriptors)); + } + + /** + * Returns a {@code Snippet} that will document the form parameters from the API + * operation's request. The given {@code attributes} will be available during snippet + * rendering and the form parameters will be documented using the given + * {@code descriptors} . + *

+ * If a form parameter is documented, is not marked as optional, and is not present in + * the response, a failure will occur. Any undocumented form parameters will be + * ignored. + * @param attributes the attributes + * @param descriptors the descriptions of the request's form parameters + * @return the snippet that will document the form parameters + * @since 3.0.0 + */ + public static FormParametersSnippet relaxedFormParameters(Map attributes, + List descriptors) { + return new FormParametersSnippet(descriptors, attributes, true); } /** @@ -482,7 +644,6 @@ public abstract class RequestDocumentation { * @param attributes the attributes * @param descriptors the descriptions of the request's parts * @return the snippet - * @see OperationRequest#getParameters() */ public static RequestPartsSnippet relaxedRequestParts(Map attributes, RequestPartDescriptor... descriptors) { @@ -499,7 +660,6 @@ public abstract class RequestDocumentation { * @param attributes the attributes * @param descriptors the descriptions of the request's parts * @return the snippet - * @see OperationRequest#getParameters() */ public static RequestPartsSnippet relaxedRequestParts(Map attributes, List descriptors) { diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-form-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-form-parameters.snippet new file mode 100644 index 00000000..9e6f6888 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-form-parameters.snippet @@ -0,0 +1,9 @@ +|=== +|Parameter|Description + +{{#parameters}} +|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-query-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-query-parameters.snippet new file mode 100644 index 00000000..9e6f6888 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-query-parameters.snippet @@ -0,0 +1,9 @@ +|=== +|Parameter|Description + +{{#parameters}} +|{{#tableCellContent}}`+{{name}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-form-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-form-parameters.snippet new file mode 100644 index 00000000..681daaa8 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-form-parameters.snippet @@ -0,0 +1,5 @@ +Parameter | Description +--------- | ----------- +{{#parameters}} +`{{name}}` | {{description}} +{{/parameters}} \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-query-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-query-parameters.snippet new file mode 100644 index 00000000..681daaa8 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-query-parameters.snippet @@ -0,0 +1,5 @@ +Parameter | Description +--------- | ----------- +{{#parameters}} +`{{name}}` | {{description}} +{{/parameters}} \ No newline at end of file diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java index 856be874..a661c897 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java @@ -57,14 +57,6 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X GET")); } - @Test - public void getRequestWithParameter() throws IOException { - new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").param("a", "alpha").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha' -i -X GET")); - } - @Test public void nonGetRequest() throws IOException { new CurlRequestSnippet(this.commandFormatter) @@ -89,30 +81,6 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X GET")); } - @Test - public void getRequestWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo?param=value").param("param", "value").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?param=value' -i -X GET")); - } - - @Test - public void getRequestWithPartiallyOverlappingQueryStringAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder - .request("http://localhost/foo?a=alpha").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i -X GET")); - } - - @Test - public void getRequestWithDisjointQueryStringAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i -X GET")); - } - @Test public void getRequestWithQueryStringWithNoValue() throws IOException { new CurlRequestSnippet(this.commandFormatter) @@ -140,7 +108,16 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithOneParameter() throws IOException { new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").param("k1", "v1").build()); + this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=v1").build()); + assertThat(this.generatedSnippets.curlRequest()) + .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); + } + + @Test + public void postRequestWithOneParameterAndExplicitContentType() throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).method("POST") + .content("k1=v1").build()); assertThat(this.generatedSnippets.curlRequest()) .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=v1'")); } @@ -148,7 +125,7 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithOneParameterWithNoValue() throws IOException { new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").param("k1").build()); + .document(this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=").build()); assertThat(this.generatedSnippets.curlRequest()) .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1='")); } @@ -156,7 +133,7 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithMultipleParameters() throws IOException { new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("POST").param("k1", "v1", "v1-bis").param("k2", "v2").build()); + .method("POST").content("k1=v1&k1=v1-bis&k2=v2").build()); assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") .withContent("$ curl 'http://localhost/foo' -i -X POST" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); } @@ -164,52 +141,24 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithUrlEncodedParameter() throws IOException { new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").param("k1", "a&b").build()); + this.operationBuilder.request("http://localhost/foo").method("POST").content("k1=a%26b").build()); assertThat(this.generatedSnippets.curlRequest()) .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST -d 'k1=a%26b'")); } @Test - public void postRequestWithDisjointQueryStringAndParameter() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder - .request("http://localhost/foo?a=alpha").method("POST").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'")); - } - - @Test - public void postRequestWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("POST") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i -X POST")); - } - - @Test - public void postRequestWithPartiallyOverlappingQueryStringAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'")); - } - - @Test - public void postRequestWithOverlappingParametersAndFormUrlEncodedBody() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").content("a=alpha&b=bravo") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X POST " - + "-H 'Content-Type: application/x-www-form-urlencoded' " + "-d 'a=alpha&b=bravo'")); + public void postRequestWithJsonData() throws IOException { + new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .content("{\"a\":\"alpha\"}").build()); + assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent( + "$ curl 'http://localhost/foo' -i -X POST -H 'Content-Type: application/json' -d '{\"a\":\"alpha\"}'")); } @Test public void putRequestWithOneParameter() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("PUT").param("k1", "v1").build()); + new CurlRequestSnippet(this.commandFormatter) + .document(this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=v1").build()); assertThat(this.generatedSnippets.curlRequest()) .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=v1'")); } @@ -217,7 +166,7 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void putRequestWithMultipleParameters() throws IOException { new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("PUT").param("k1", "v1").param("k1", "v1-bis").param("k2", "v2").build()); + .method("PUT").content("k1=v1&k1=v1-bis&k2=v2").build()); assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") .withContent("$ curl 'http://localhost/foo' -i -X PUT" + " -d 'k1=v1&k1=v1-bis&k2=v2'")); } @@ -225,7 +174,7 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { @Test public void putRequestWithUrlEncodedParameter() throws IOException { new CurlRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("PUT").param("k1", "a&b").build()); + this.operationBuilder.request("http://localhost/foo").method("PUT").content("k1=a%26b").build()); assertThat(this.generatedSnippets.curlRequest()) .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo' -i -X PUT -d 'k1=a%26b'")); } @@ -287,29 +236,6 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); } - @Test - public void multipartPostWithParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") - .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]).submittedFileName("documents/images/example.png").and() - .param("a", "apple", "avocado").param("b", "banana").build()); - String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " - + "'Content-Type: multipart/form-data' -F " - + "'image=@documents/images/example.png' -F 'a=apple' -F 'a=avocado' " + "-F 'b=banana'"; - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); - } - - @Test - public void multipartPostWithOverlappingPartsAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/upload") - .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]).submittedFileName("documents/images/example.png").and() - .part("a", "apple".getBytes()).and().param("a", "apple").build()); - String expectedContent = "$ curl 'http://localhost/upload' -i -X POST -H " - + "'Content-Type: multipart/form-data' -F 'image=@documents/images/example.png' -F 'a=apple'"; - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash").withContent(expectedContent)); - } - @Test public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") @@ -329,22 +255,6 @@ public class CurlRequestSnippetTests extends AbstractSnippetTests { + " -H 'Content-Type: application/json' -H 'a: alpha'")); } - @Test - public void postWithContentAndParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .param("a", "alpha").method("POST").param("b", "bravo").content("Some content").build()); - assertThat(this.generatedSnippets.curlRequest()).is(codeBlock("bash") - .withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X POST -d 'Some content'")); - } - - @Test - public void deleteWithParameters() throws IOException { - new CurlRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("DELETE").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.curlRequest()) - .is(codeBlock("bash").withContent("$ curl 'http://localhost/foo?a=alpha&b=bravo' -i " + "-X DELETE")); - } - @Test public void deleteWithQueryString() throws IOException { new CurlRequestSnippet(this.commandFormatter).document( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java index 7be6272c..88835917 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java @@ -58,14 +58,6 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo'")); } - @Test - public void getRequestWithParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").param("a", "alpha").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?a=alpha'")); - } - @Test public void nonGetRequest() throws IOException { new HttpieRequestSnippet(this.commandFormatter) @@ -90,30 +82,6 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param=value'")); } - @Test - public void getRequestWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo?param=value").param("param", "value").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?param=value'")); - } - - @Test - public void getRequestWithPartiallyOverlappingQueryStringAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder - .request("http://localhost/foo?a=alpha").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?a=alpha&b=bravo'")); - } - - @Test - public void getRequestWithDisjointQueryStringAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http GET 'http://localhost/foo?a=alpha&b=bravo'")); - } - @Test public void getRequestWithQueryStringWithNoValue() throws IOException { new HttpieRequestSnippet(this.commandFormatter) @@ -140,16 +108,18 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithOneParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").param("k1", "v1").build()); + new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=v1").build()); assertThat(this.generatedSnippets.httpieRequest()) .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1'")); } @Test public void postRequestWithOneParameterWithNoValue() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo").method("POST").param("k1").build()); + new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1").build()); assertThat(this.generatedSnippets.httpieRequest()) .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1='")); } @@ -157,60 +127,26 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithMultipleParameters() throws IOException { new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("POST").param("k1", "v1", "v1-bis").param("k2", "v2").build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") - .withContent("$ http --form POST 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); + .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=v1&k1=v1-bis&k2=v2").build()); + assertThat(this.generatedSnippets.httpieRequest()).is( + codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=v1' 'k1=v1-bis' 'k2=v2'")); } @Test public void postRequestWithUrlEncodedParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").param("k1", "a&b").build()); + new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("POST").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=a%26b").build()); assertThat(this.generatedSnippets.httpieRequest()) .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo' 'k1=a&b'")); } - @Test - public void postRequestWithDisjointQueryStringAndParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder - .request("http://localhost/foo?a=alpha").method("POST").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'")); - } - - @Test - public void postRequestWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo").method("POST") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http POST 'http://localhost/foo?a=alpha&b=bravo'")); - } - - @Test - public void postRequestWithPartiallyOverlappingQueryStringAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'")); - } - - @Test - public void postRequestWithOverlappingParametersAndFormUrlEncodedBody() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("POST").content("a=alpha&b=bravo") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ echo 'a=alpha&b=bravo' | http POST 'http://localhost/foo' " - + "'Content-Type:application/x-www-form-urlencoded'")); - } - @Test public void putRequestWithOneParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("PUT").param("k1", "v1").build()); + new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("PUT").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=v1").build()); assertThat(this.generatedSnippets.httpieRequest()) .is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=v1'")); } @@ -218,15 +154,17 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void putRequestWithMultipleParameters() throws IOException { new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("PUT").param("k1", "v1").param("k1", "v1-bis").param("k2", "v2").build()); + .method("PUT").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=v1&k1=v1-bis&k2=v2").build()); assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") .withContent("$ http --form PUT 'http://localhost/foo'" + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); } @Test public void putRequestWithUrlEncodedParameter() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document( - this.operationBuilder.request("http://localhost/foo").method("PUT").param("k1", "a&b").build()); + new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") + .method("PUT").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .content("k1=a%26b").build()); assertThat(this.generatedSnippets.httpieRequest()) .is(codeBlock("bash").withContent("$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); } @@ -291,30 +229,6 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); } - @Test - public void multipartPostWithParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/upload").method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]).submittedFileName("documents/images/example.png").and() - .param("a", "apple", "avocado").param("b", "banana").build()); - String expectedContent = "$ http --multipart POST 'http://localhost/upload'" - + " 'image'@'documents/images/example.png' 'a=apple' 'a=avocado'" + " 'b=banana'"; - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); - } - - @Test - public void multipartPostWithOverlappingPartsAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter) - .document(this.operationBuilder.request("http://localhost/upload").method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE) - .part("image", new byte[0]).submittedFileName("documents/images/example.png").and() - .part("a", "apple".getBytes()).and().param("a", "apple").build()); - String expectedContent = "$ http --multipart POST 'http://localhost/upload'" - + " 'image'@'documents/images/example.png' 'a'='apple'"; - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash").withContent(expectedContent)); - } - @Test public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException { new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") @@ -334,22 +248,6 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { + " 'Content-Type:application/json' 'a:alpha'")); } - @Test - public void postWithContentAndParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("POST").param("a", "alpha").param("b", "bravo").content("Some content").build()); - assertThat(this.generatedSnippets.httpieRequest()).is(codeBlock("bash") - .withContent("$ echo 'Some content' | http POST " + "'http://localhost/foo?a=alpha&b=bravo'")); - } - - @Test - public void deleteWithParameters() throws IOException { - new HttpieRequestSnippet(this.commandFormatter).document(this.operationBuilder.request("http://localhost/foo") - .method("DELETE").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpieRequest()) - .is(codeBlock("bash").withContent("$ http DELETE 'http://localhost/foo?a=alpha&b=bravo'")); - } - @Test public void deleteWithQueryString() throws IOException { new HttpieRequestSnippet(this.commandFormatter).document( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java index 4bdb908d..62055b08 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java @@ -58,9 +58,9 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { } @Test - public void getRequestWithParameters() throws IOException { - new HttpRequestSnippet().document( - this.operationBuilder.request("http://localhost/foo").header("Alpha", "a").param("b", "bravo").build()); + public void getRequestWithQueryParameters() throws IOException { + new HttpRequestSnippet() + .document(this.operationBuilder.request("http://localhost/foo?b=bravo").header("Alpha", "a").build()); assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.GET, "/foo?b=bravo") .header("Alpha", "a").header(HttpHeaders.HOST, "localhost")); } @@ -96,22 +96,6 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { .is(httpRequest(RequestMethod.GET, "/foo?bar").header(HttpHeaders.HOST, "localhost")); } - @Test - public void getWithPartiallyOverlappingQueryStringAndParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?a=alpha") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo?a=alpha&b=bravo").header(HttpHeaders.HOST, "localhost")); - } - - @Test - public void getWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.GET, "/foo?a=alpha&b=bravo").header(HttpHeaders.HOST, "localhost")); - } - @Test public void postRequestWithContent() throws IOException { String content = "Hello, world"; @@ -123,59 +107,16 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { } @Test - public void postRequestWithContentAndParameters() throws IOException { + public void postRequestWithContentAndQueryParameters() throws IOException { String content = "Hello, world"; - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo").method("POST") - .param("a", "alpha").content(content).build()); + new HttpRequestSnippet().document( + this.operationBuilder.request("http://localhost/foo?a=alpha").method("POST").content(content).build()); assertThat(this.generatedSnippets.httpRequest()) .is(httpRequest(RequestMethod.POST, "/foo?a=alpha").header(HttpHeaders.HOST, "localhost") .content(content).header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void postRequestWithContentAndDisjointQueryStringAndParameters() throws IOException { - String content = "Hello, world"; - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?b=bravo").method("POST") - .param("a", "alpha").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo?b=bravo&a=alpha").header(HttpHeaders.HOST, "localhost") - .content(content).header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); - } - - @Test - public void postRequestWithContentAndPartiallyOverlappingQueryStringAndParameters() throws IOException { - String content = "Hello, world"; - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?b=bravo").method("POST") - .param("a", "alpha").param("b", "bravo").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo?b=bravo&a=alpha").header(HttpHeaders.HOST, "localhost") - .content(content).header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); - } - - @Test - public void postRequestWithContentAndTotallyOverlappingQueryStringAndParameters() throws IOException { - String content = "Hello, world"; - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?b=bravo&a=alpha") - .method("POST").param("a", "alpha").param("b", "bravo").content(content).build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo?b=bravo&a=alpha").header(HttpHeaders.HOST, "localhost") - .content(content).header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); - } - - @Test - public void postRequestWithOverlappingParametersAndFormUrlEncodedBody() throws IOException { - String content = "a=alpha&b=bravo"; - new HttpRequestSnippet().document( - this.operationBuilder.request("http://localhost/foo").method("POST").content("a=alpha&b=bravo") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/foo") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .header(HttpHeaders.HOST, "localhost").content(content) - .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); - } - @Test public void postRequestWithCharset() throws IOException { String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4"; @@ -187,24 +128,6 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { .header(HttpHeaders.CONTENT_LENGTH, contentBytes.length).content(japaneseContent)); } - @Test - public void postRequestWithParameter() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo").method("POST") - .param("b&r", "baz").param("a", "alpha").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST, "localhost") - .header("Content-Type", "application/x-www-form-urlencoded").content("b%26r=baz&a=alpha")); - } - - @Test - public void postRequestWithParameterWithNoValue() throws IOException { - new HttpRequestSnippet() - .document(this.operationBuilder.request("http://localhost/foo").method("POST").param("bar").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST, "localhost") - .header("Content-Type", "application/x-www-form-urlencoded").content("bar=")); - } - @Test public void putRequestWithContent() throws IOException { String content = "Hello, world"; @@ -215,23 +138,6 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { .header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length)); } - @Test - public void putRequestWithParameter() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo").method("PUT") - .param("b&r", "baz").param("a", "alpha").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.PUT, "/foo").header(HttpHeaders.HOST, "localhost") - .header("Content-Type", "application/x-www-form-urlencoded").content("b%26r=baz&a=alpha")); - } - - @Test - public void putRequestWithTotallyOverlappingQueryStringAndParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo?a=alpha&b=bravo") - .method("PUT").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.PUT, "/foo?a=alpha&b=bravo").header(HttpHeaders.HOST, "localhost")); - } - @Test public void multipartPost() throws IOException { new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload").method("POST") @@ -256,47 +162,6 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { .header(HttpHeaders.HOST, "localhost").content(expectedContent)); } - @Test - public void multipartPostWithParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload").method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE).param("a", "apple", "avocado") - .param("b", "banana").part("image", "<< data >>".getBytes()).build()); - String param1Part = createPart(String.format("Content-Disposition: form-data; " + "name=a%n%napple"), false); - String param2Part = createPart(String.format("Content-Disposition: form-data; " + "name=a%n%navocado"), false); - String param3Part = createPart(String.format("Content-Disposition: form-data; " + "name=b%n%nbanana"), false); - String filePart = createPart(String.format("Content-Disposition: form-data; " + "name=image%n%n<< data >>")); - String expectedContent = param1Part + param2Part + param3Part + filePart; - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") - .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) - .header(HttpHeaders.HOST, "localhost").content(expectedContent)); - } - - @Test - public void multipartPostWithOverlappingPartsAndParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload").method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE).param("a", "apple") - .part("a", "apple".getBytes()).and().part("image", "<< data >>".getBytes()).build()); - String paramPart = createPart(String.format("Content-Disposition: form-data; " + "name=a%n%napple"), false); - String filePart = createPart(String.format("Content-Disposition: form-data; " + "name=image%n%n<< data >>")); - String expectedContent = paramPart + filePart; - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") - .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) - .header(HttpHeaders.HOST, "localhost").content(expectedContent)); - } - - @Test - public void multipartPostWithParameterWithNoValue() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload").method("POST") - .header(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE).param("a") - .part("image", "<< data >>".getBytes()).build()); - String paramPart = createPart(String.format("Content-Disposition: form-data; " + "name=a%n"), false); - String filePart = createPart(String.format("Content-Disposition: form-data; " + "name=image%n%n<< data >>")); - String expectedContent = paramPart + filePart; - assertThat(this.generatedSnippets.httpRequest()).is(httpRequest(RequestMethod.POST, "/upload") - .header("Content-Type", "multipart/form-data; boundary=" + BOUNDARY) - .header(HttpHeaders.HOST, "localhost").content(expectedContent)); - } - @Test public void multipartPostWithContentType() throws IOException { new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/upload").method("POST") @@ -328,14 +193,6 @@ public class HttpRequestSnippetTests extends AbstractSnippetTests { assertThat(this.generatedSnippets.httpRequest()).contains("Title for the request"); } - @Test - public void deleteWithParameters() throws IOException { - new HttpRequestSnippet().document(this.operationBuilder.request("http://localhost/foo").method("DELETE") - .param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.httpRequest()) - .is(httpRequest(RequestMethod.DELETE, "/foo?a=alpha&b=bravo").header("Host", "localhost")); - } - @Test public void deleteWithQueryString() throws IOException { new HttpRequestSnippet().document( diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/ParametersTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/ParametersTests.java deleted file mode 100644 index a6fefcd8..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/ParametersTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2014-2018 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 - * - * https://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.operation; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link Parameters}. - * - * @author Andy Wilkinson - */ -public class ParametersTests { - - private final Parameters parameters = new Parameters(); - - @Test - public void queryStringForNoParameters() { - assertThat(this.parameters.toQueryString()).isEqualTo(""); - } - - @Test - public void queryStringForSingleParameter() { - this.parameters.add("a", "b"); - assertThat(this.parameters.toQueryString()).isEqualTo("a=b"); - } - - @Test - public void queryStringForSingleParameterWithMultipleValues() { - this.parameters.add("a", "b"); - this.parameters.add("a", "c"); - assertThat(this.parameters.toQueryString()).isEqualTo("a=b&a=c"); - } - - @Test - public void queryStringForMutipleParameters() { - this.parameters.add("a", "alpha"); - this.parameters.add("b", "bravo"); - assertThat(this.parameters.toQueryString()).isEqualTo("a=alpha&b=bravo"); - } - - @Test - public void queryStringForParameterWithEmptyValue() { - this.parameters.add("a", ""); - assertThat(this.parameters.toQueryString()).isEqualTo("a="); - } - - @Test - public void queryStringForParameterWithNullValue() { - this.parameters.add("a", null); - assertThat(this.parameters.toQueryString()).isEqualTo("a="); - } - - @Test - public void queryStringForParameterThatRequiresEncoding() { - this.parameters.add("a", "alpha&bravo"); - assertThat(this.parameters.toQueryString()).isEqualTo("a=alpha%26bravo"); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java deleted file mode 100644 index 0cb99f12..00000000 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/QueryStringParserTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2014-2019 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 - * - * https://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.operation; - -import java.net.URI; -import java.util.Arrays; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link QueryStringParser}. - * - * @author Andy Wilkinson - */ -public class QueryStringParserTests { - - private final QueryStringParser queryStringParser = new QueryStringParser(); - - @Test - public void noParameters() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost")); - assertThat(parameters.size()).isEqualTo(0); - } - - @Test - public void singleParameter() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=alpha")); - assertThat(parameters.size()).isEqualTo(1); - assertThat(parameters).containsEntry("a", Arrays.asList("alpha")); - } - - @Test - public void multipleParameters() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=alpha&b=bravo&c=charlie")); - assertThat(parameters.size()).isEqualTo(3); - assertThat(parameters).containsEntry("a", Arrays.asList("alpha")); - assertThat(parameters).containsEntry("b", Arrays.asList("bravo")); - assertThat(parameters).containsEntry("c", Arrays.asList("charlie")); - } - - @Test - public void multipleParametersWithSameKey() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=apple&a=avocado")); - assertThat(parameters.size()).isEqualTo(1); - assertThat(parameters).containsEntry("a", Arrays.asList("apple", "avocado")); - } - - @Test - public void encoded() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=al%26%3Dpha")); - assertThat(parameters.size()).isEqualTo(1); - assertThat(parameters).containsEntry("a", Arrays.asList("al&=pha")); - } - - @Test - public void malformedParameter() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado"))) - .withMessage("The parameter 'a=apple=avocado' is malformed"); - } - - @Test - public void emptyParameter() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=")); - assertThat(parameters.size()).isEqualTo(1); - assertThat(parameters).containsEntry("a", Arrays.asList("")); - } - - @Test - public void emptyAndNotEmptyParameter() { - Parameters parameters = this.queryStringParser.parse(URI.create("http://localhost?a=&a=alpha")); - assertThat(parameters.size()).isEqualTo(1); - assertThat(parameters).containsEntry("a", Arrays.asList("", "alpha")); - } - -} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java index 57b54c98..7e26b6da 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/ContentModifyingOperationPreprocessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2022 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. @@ -30,7 +30,6 @@ import org.springframework.restdocs.operation.OperationRequestFactory; import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; -import org.springframework.restdocs.operation.Parameters; import static org.assertj.core.api.Assertions.assertThat; @@ -59,8 +58,7 @@ public class ContentModifyingOperationPreprocessorTests { @Test public void modifyRequestContent() { OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, - "content".getBytes(), new HttpHeaders(), new Parameters(), - Collections.emptyList()); + "content".getBytes(), new HttpHeaders(), Collections.emptyList()); OperationRequest preprocessed = this.preprocessor.preprocess(request); assertThat(preprocessed.getContent()).isEqualTo("modified".getBytes()); } @@ -78,7 +76,7 @@ public class ContentModifyingOperationPreprocessorTests { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentLength(7); OperationRequest request = this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, - "content".getBytes(), httpHeaders, new Parameters(), Collections.emptyList()); + "content".getBytes(), httpHeaders, Collections.emptyList()); OperationRequest preprocessed = this.preprocessor.preprocess(request); assertThat(preprocessed.getHeaders().getContentLength()).isEqualTo(8L); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java index 9028c564..c33153f4 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/HeadersModifyingOperationPreprocessorTests.java @@ -31,7 +31,6 @@ import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestFactory; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; -import org.springframework.restdocs.operation.Parameters; import static org.assertj.core.api.Assertions.assertThat; @@ -149,7 +148,7 @@ public class HeadersModifyingOperationPreprocessorTests { headersCustomizer.accept(headers); } return new OperationRequestFactory().create(URI.create("http://localhost:8080"), HttpMethod.GET, new byte[0], - headers, new Parameters(), Collections.emptyList()); + headers, Collections.emptyList()); } private OperationResponse createResponse() { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java index f20d1507..d0353540 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/operation/preprocess/UriModifyingOperationPreprocessorTests.java @@ -32,7 +32,6 @@ import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.OperationRequestPartFactory; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import static org.assertj.core.api.Assertions.assertThat; @@ -304,41 +303,40 @@ public class UriModifyingOperationPreprocessorTests { public void resultingRequestHasCookiesFromOriginalRequst() { List cookies = Arrays.asList(new RequestCookie("a", "alpha")); OperationRequest request = this.requestFactory.create(URI.create("http://localhost:12345"), HttpMethod.GET, - new byte[0], new HttpHeaders(), new Parameters(), Collections.emptyList(), - cookies); + new byte[0], new HttpHeaders(), Collections.emptyList(), cookies); OperationRequest processed = this.preprocessor.preprocess(request); assertThat(processed.getCookies().size()).isEqualTo(1); } private OperationRequest createRequestWithUri(String uri) { return this.requestFactory.create(URI.create(uri), HttpMethod.GET, new byte[0], new HttpHeaders(), - new Parameters(), Collections.emptyList()); + Collections.emptyList()); } private OperationRequest createRequestWithContent(String content) { return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, content.getBytes(), - new HttpHeaders(), new Parameters(), Collections.emptyList()); + new HttpHeaders(), Collections.emptyList()); } private OperationRequest createRequestWithHeader(String name, String value) { HttpHeaders headers = new HttpHeaders(); headers.add(name, value); return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, new byte[0], headers, - new Parameters(), Collections.emptyList()); + Collections.emptyList()); } private OperationRequest createRequestWithPartWithHeader(String name, String value) { HttpHeaders headers = new HttpHeaders(); headers.add(name, value); return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, new byte[0], - new HttpHeaders(), new Parameters(), + new HttpHeaders(), Arrays.asList(new OperationRequestPartFactory().create("part", "fileName", new byte[0], headers))); } private OperationRequest createRequestWithPartWithContent(String content) { return this.requestFactory.create(URI.create("http://localhost"), HttpMethod.GET, new byte[0], - new HttpHeaders(), new Parameters(), Arrays.asList(new OperationRequestPartFactory().create("part", - "fileName", content.getBytes(), new HttpHeaders()))); + new HttpHeaders(), Arrays.asList(new OperationRequestPartFactory().create("part", "fileName", + content.getBytes(), new HttpHeaders()))); } private OperationResponse createResponseWithContent(String content) { diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java similarity index 68% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java index b6921c44..b6e495d0 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetFailureTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetFailureTests.java @@ -30,12 +30,12 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; /** - * Tests for failures when rendering {@link RequestParametersSnippet} due to missing or - * undocumented request parameters. + * Tests for failures when rendering {@link FormParametersSnippet} due to missing or + * undocumented form parameters. * * @author Andy Wilkinson */ -public class RequestParametersSnippetFailureTests { +public class FormParametersSnippetFailureTests { @Rule public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); @@ -43,25 +43,25 @@ public class RequestParametersSnippetFailureTests { @Test public void undocumentedParameter() { assertThatExceptionOfType(SnippetException.class) - .isThrownBy(() -> new RequestParametersSnippet(Collections.emptyList()) - .document(this.operationBuilder.request("http://localhost").param("a", "alpha").build())) - .withMessage("Request parameters with the following names were not documented: [a]"); + .isThrownBy(() -> new FormParametersSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost").content("a=alpha").build())) + .withMessage("Form parameters with the following names were not documented: [a]"); } @Test public void missingParameter() { assertThatExceptionOfType(SnippetException.class) - .isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) .document(this.operationBuilder.request("http://localhost").build())) - .withMessage("Request parameters with the following names were not found in the request: [a]"); + .withMessage("Form parameters with the following names were not found in the request: [a]"); } @Test public void undocumentedAndMissingParameters() { assertThatExceptionOfType(SnippetException.class) - .isThrownBy(() -> new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").param("b", "bravo").build())) - .withMessage("Request parameters with the following names were not documented: [b]. Request parameters" + .isThrownBy(() -> new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("b=bravo").build())) + .withMessage("Form parameters with the following names were not documented: [b]. Form parameters" + " with the following names were not found in the request: [a]"); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java similarity index 54% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java index 92857a27..1bf258a8 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/FormParametersSnippetTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2020 the original author or authors. + * Copyright 2014-2022 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. @@ -36,138 +36,134 @@ import static org.springframework.restdocs.snippet.Attributes.attributes; import static org.springframework.restdocs.snippet.Attributes.key; /** - * Tests for {@link RequestParametersSnippet}. + * Tests for {@link FormParametersSnippet}. * * @author Andy Wilkinson */ -public class RequestParametersSnippetTests extends AbstractSnippetTests { +public class FormParametersSnippetTests extends AbstractSnippetTests { - public RequestParametersSnippetTests(String name, TemplateFormat templateFormat) { + public FormParametersSnippetTests(String name, TemplateFormat templateFormat) { super(name, templateFormat); } @Test - public void requestParameters() throws IOException { - new RequestParametersSnippet( + public void formParameters() throws IOException { + new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").param("a", "bravo") - .param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) + .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } @Test - public void requestParameterWithNoValue() throws IOException { - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) - .document(this.operationBuilder.request("http://localhost").param("a").build()); - assertThat(this.generatedSnippets.requestParameters()) + public void formParameterWithNoValue() throws IOException { + new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").content("a=").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); } @Test - public void ignoredRequestParameter() throws IOException { - new RequestParametersSnippet( + public void ignoredFormParameter() throws IOException { + new FormParametersSnippet( Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").param("a", "bravo") - .param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) + .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); } @Test - public void allUndocumentedRequestParametersCanBeIgnored() throws IOException { - new RequestParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true).document( - this.operationBuilder.request("http://localhost").param("a", "bravo").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) + public void allUndocumentedFormParametersCanBeIgnored() throws IOException { + new FormParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) + .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); } @Test - public void missingOptionalRequestParameter() throws IOException { - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), + public void missingOptionalFormParameter() throws IOException { + new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) - .document(this.operationBuilder.request("http://localhost").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) + .document(this.operationBuilder.request("http://localhost").content("b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } @Test - public void presentOptionalRequestParameter() throws IOException { - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) - .document(this.operationBuilder.request("http://localhost").param("a", "one").build()); - assertThat(this.generatedSnippets.requestParameters()) + public void presentOptionalFormParameter() throws IOException { + new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) + .document(this.operationBuilder.request("http://localhost").content("a=alpha").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); } @Test - public void requestParametersWithCustomAttributes() throws IOException { + public void formParametersWithCustomAttributes() throws IOException { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parameters")) - .willReturn(snippetResource("request-parameters-with-title")); - new RequestParametersSnippet( + given(resolver.resolveTemplateResource("form-parameters")) + .willReturn(snippetResource("form-parameters-with-title")); + new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), attributes(key("title").value("The title"))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()).contains("The title"); + .request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()).contains("The title"); } @Test - public void requestParametersWithCustomDescriptorAttributes() throws IOException { + public void formParametersWithCustomDescriptorAttributes() throws IOException { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parameters")) - .willReturn(snippetResource("request-parameters-with-extra-column")); - new RequestParametersSnippet( + given(resolver.resolveTemplateResource("form-parameters")) + .willReturn(snippetResource("form-parameters-with-extra-column")); + new FormParametersSnippet( Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()).is( + .request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()).is( tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); } @Test - public void requestParametersWithOptionalColumn() throws IOException { + public void formParametersWithOptionalColumn() throws IOException { TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); - given(resolver.resolveTemplateResource("request-parameters")) - .willReturn(snippetResource("request-parameters-with-optional-column")); - new RequestParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), + given(resolver.resolveTemplateResource("form-parameters")) + .willReturn(snippetResource("form-parameters-with-optional-column")); + new FormParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), parameterWithName("b").description("two"))) .document(this.operationBuilder .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) - .request("http://localhost").param("a", "alpha").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) - .is(tableWithHeader("Parameter", "Optional", "Description").row("a", "true", "one").row("b", "false", - "two")); + .request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()).is(tableWithHeader("Parameter", "Optional", "Description") + .row("a", "true", "one").row("b", "false", "two")); } @Test public void additionalDescriptors() throws IOException { - RequestDocumentation.requestParameters(parameterWithName("a").description("one")) - .and(parameterWithName("b").description("two")).document(this.operationBuilder - .request("http://localhost").param("a", "bravo").param("b", "bravo").build()); - assertThat(this.generatedSnippets.requestParameters()) - .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); - } - - @Test - public void additionalDescriptorsWithRelaxedRequestParameters() throws IOException { - RequestDocumentation.relaxedRequestParameters(parameterWithName("a").description("one")) + RequestDocumentation.formParameters(parameterWithName("a").description("one")) .and(parameterWithName("b").description("two")) - .document(this.operationBuilder.request("http://localhost").param("a", "bravo").param("b", "bravo") - .param("c", "undocumented").build()); - assertThat(this.generatedSnippets.requestParameters()) + .document(this.operationBuilder.request("http://localhost").content("a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.formParameters()) .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); } @Test - public void requestParametersWithEscapedContent() throws IOException { - RequestDocumentation.requestParameters(parameterWithName("Foo|Bar").description("one|two")) - .document(this.operationBuilder.request("http://localhost").param("Foo|Bar", "baz").build()); - assertThat(this.generatedSnippets.requestParameters()).is(tableWithHeader("Parameter", "Description") + public void additionalDescriptorsWithRelaxedFormParameters() throws IOException { + RequestDocumentation.relaxedFormParameters(parameterWithName("a").description("one")) + .and(parameterWithName("b").description("two")).document(this.operationBuilder + .request("http://localhost").content("a=alpha&b=bravo&c=undocumented").build()); + assertThat(this.generatedSnippets.formParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + } + + @Test + public void formParametersWithEscapedContent() throws IOException { + RequestDocumentation.formParameters(parameterWithName("Foo|Bar").description("one|two")) + .document(this.operationBuilder.request("http://localhost").content("Foo%7CBar=baz").build()); + assertThat(this.generatedSnippets.formParameters()).is(tableWithHeader("Parameter", "Description") .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); } diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java new file mode 100644 index 00000000..567bab8c --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetFailureTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014-2022 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 + * + * https://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.util.Arrays; +import java.util.Collections; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.restdocs.snippet.SnippetException; +import org.springframework.restdocs.templates.TemplateFormats; +import org.springframework.restdocs.testfixtures.OperationBuilder; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; + +/** + * Tests for failures when rendering {@link QueryParametersSnippet} due to missing or + * undocumented query parameters. + * + * @author Andy Wilkinson + */ +public class QueryParametersSnippetFailureTests { + + @Rule + public OperationBuilder operationBuilder = new OperationBuilder(TemplateFormats.asciidoctor()); + + @Test + public void undocumentedParameter() { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new QueryParametersSnippet(Collections.emptyList()) + .document(this.operationBuilder.request("http://localhost?a=alpha").build())) + .withMessage("Query parameters with the following names were not documented: [a]"); + } + + @Test + public void missingParameter() { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost").build())) + .withMessage("Query parameters with the following names were not found in the request: [a]"); + } + + @Test + public void undocumentedAndMissingParameters() { + assertThatExceptionOfType(SnippetException.class) + .isThrownBy(() -> new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost?b=bravo").build())) + .withMessage("Query parameters with the following names were not documented: [b]. Query parameters" + + " with the following names were not found in the request: [a]"); + } + +} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java new file mode 100644 index 00000000..0d7be6df --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/request/QueryParametersSnippetTests.java @@ -0,0 +1,177 @@ +/* + * Copyright 2014-2022 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 + * + * https://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.Arrays; + +import org.junit.Test; + +import org.springframework.restdocs.AbstractSnippetTests; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateFormat; +import org.springframework.restdocs.templates.TemplateFormats; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; + +/** + * Tests for {@link QueryParametersSnippet}. + * + * @author Andy Wilkinson + */ +public class QueryParametersSnippetTests extends AbstractSnippetTests { + + public QueryParametersSnippetTests(String name, TemplateFormat templateFormat) { + super(name, templateFormat); + } + + @Test + public void queryParameters() throws IOException { + new QueryParametersSnippet( + Arrays.asList(parameterWithName("a").description("one"), parameterWithName("b").description("two"))) + .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + } + + @Test + public void queryParameterWithNoValue() throws IOException { + new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one"))) + .document(this.operationBuilder.request("http://localhost?a").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + } + + @Test + public void ignoredQueryParameter() throws IOException { + new QueryParametersSnippet( + Arrays.asList(parameterWithName("a").ignored(), parameterWithName("b").description("two"))) + .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + } + + @Test + public void allUndocumentedQueryParametersCanBeIgnored() throws IOException { + new QueryParametersSnippet(Arrays.asList(parameterWithName("b").description("two")), true) + .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`b`", "two")); + } + + @Test + public void missingOptionalQueryParameter() throws IOException { + new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), + parameterWithName("b").description("two"))) + .document(this.operationBuilder.request("http://localhost?b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + } + + @Test + public void presentOptionalQueryParameter() throws IOException { + new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional())) + .document(this.operationBuilder.request("http://localhost?a=alpha").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one")); + } + + @Test + public void queryParametersWithCustomAttributes() throws IOException { + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + given(resolver.resolveTemplateResource("query-parameters")) + .willReturn(snippetResource("query-parameters-with-title")); + new QueryParametersSnippet( + Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), + parameterWithName("b").description("two").attributes(key("foo").value("bravo"))), + attributes(key("title").value("The title"))) + .document(this.operationBuilder + .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) + .request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()).contains("The title"); + } + + @Test + public void queryParametersWithCustomDescriptorAttributes() throws IOException { + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + given(resolver.resolveTemplateResource("query-parameters")) + .willReturn(snippetResource("query-parameters-with-extra-column")); + new QueryParametersSnippet( + Arrays.asList(parameterWithName("a").description("one").attributes(key("foo").value("alpha")), + parameterWithName("b").description("two").attributes(key("foo").value("bravo")))) + .document(this.operationBuilder + .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) + .request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()).is( + tableWithHeader("Parameter", "Description", "Foo").row("a", "one", "alpha").row("b", "two", "bravo")); + } + + @Test + public void queryParametersWithOptionalColumn() throws IOException { + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + given(resolver.resolveTemplateResource("query-parameters")) + .willReturn(snippetResource("query-parameters-with-optional-column")); + new QueryParametersSnippet(Arrays.asList(parameterWithName("a").description("one").optional(), + parameterWithName("b").description("two"))) + .document(this.operationBuilder + .attribute(TemplateEngine.class.getName(), new MustacheTemplateEngine(resolver)) + .request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()).is(tableWithHeader("Parameter", "Optional", "Description") + .row("a", "true", "one").row("b", "false", "two")); + } + + @Test + public void additionalDescriptors() throws IOException { + RequestDocumentation.queryParameters(parameterWithName("a").description("one")) + .and(parameterWithName("b").description("two")) + .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + } + + @Test + public void additionalDescriptorsWithRelaxedQueryParameters() throws IOException { + RequestDocumentation.relaxedQueryParameters(parameterWithName("a").description("one")) + .and(parameterWithName("b").description("two")) + .document(this.operationBuilder.request("http://localhost?a=alpha&b=bravo&c=undocumented").build()); + assertThat(this.generatedSnippets.queryParameters()) + .is(tableWithHeader("Parameter", "Description").row("`a`", "one").row("`b`", "two")); + } + + @Test + public void queryParametersWithEscapedContent() throws IOException { + RequestDocumentation.queryParameters(parameterWithName("Foo|Bar").description("one|two")) + .document(this.operationBuilder.request("http://localhost?Foo%7CBar=baz").build()); + assertThat(this.generatedSnippets.queryParameters()).is(tableWithHeader("Parameter", "Description") + .row(escapeIfNecessary("`Foo|Bar`"), escapeIfNecessary("one|two"))); + } + + private String escapeIfNecessary(String input) { + if (this.templateFormat.getId().equals(TemplateFormats.markdown().getId())) { + return input; + } + return input.replace("|", "\\|"); + } + +} diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-extra-column.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-extra-column.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-extra-column.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-optional-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-optional-column.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-optional-column.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-optional-column.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-title.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/request-parameters-with-title.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/form-parameters-with-title.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-extra-column.snippet new file mode 100644 index 00000000..fb97f89b --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/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-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-optional-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-optional-column.snippet new file mode 100644 index 00000000..70847ba1 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-optional-column.snippet @@ -0,0 +1,10 @@ +|=== +|Parameter|Optional|Description + +{{#parameters}} +|{{name}} +|{{optional}} +|{{description}} + +{{/parameters}} +|=== \ No newline at end of file diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/query-parameters-with-title.snippet new file mode 100644 index 00000000..611254aa --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/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-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-extra-column.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-extra-column.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-extra-column.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-optional-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-optional-column.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-optional-column.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-optional-column.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-title.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/request-parameters-with-title.snippet rename to spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/form-parameters-with-title.snippet diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-extra-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-extra-column.snippet new file mode 100644 index 00000000..260502b6 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-extra-column.snippet @@ -0,0 +1,5 @@ +Parameter | Description | Foo +--------- | ----------- | --- +{{#parameters}} +{{name}} | {{description}} | {{foo}} +{{/parameters}} \ No newline at end of file diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-optional-column.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-optional-column.snippet new file mode 100644 index 00000000..2f9cd077 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-optional-column.snippet @@ -0,0 +1,5 @@ +Parameter | Optional | Description +--------- | -------- | ----------- +{{#parameters}} +{{name}} | {{optional}} | {{description}} +{{/parameters}} \ No newline at end of file diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-title.snippet new file mode 100644 index 00000000..b63b6235 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/query-parameters-with-title.snippet @@ -0,0 +1,6 @@ +{{title}} +Parameter | Description +--------- | ----------- +{{#parameters}} +{{name}} | {{description}} +{{/parameters}} \ No newline at end of file diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java index e619e02b..a131cfc3 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/GeneratedSnippets.java @@ -110,8 +110,12 @@ public class GeneratedSnippets extends OperationTestRule { return snippet("path-parameters"); } - public String requestParameters() { - return snippet("request-parameters"); + public String queryParameters() { + return snippet("query-parameters"); + } + + public String formParameters() { + return snippet("form-parameters"); } public String snippet(String name) { diff --git a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java index a9299048..a546cbd7 100644 --- a/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java +++ b/spring-restdocs-core/src/testFixtures/java/org/springframework/restdocs/testfixtures/OperationBuilder.java @@ -20,7 +20,6 @@ import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -42,7 +41,6 @@ import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.OperationRequestPartFactory; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.OperationResponseFactory; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.restdocs.operation.ResponseCookie; import org.springframework.restdocs.operation.StandardOperation; @@ -147,8 +145,6 @@ public class OperationBuilder extends OperationTestRule { private HttpHeaders headers = new HttpHeaders(); - private Parameters parameters = new Parameters(); - private List partBuilders = new ArrayList<>(); private Collection cookies = new ArrayList<>(); @@ -162,8 +158,8 @@ public class OperationBuilder extends OperationTestRule { for (OperationRequestPartBuilder builder : this.partBuilders) { parts.add(builder.buildPart()); } - return new OperationRequestFactory().create(this.requestUri, this.method, this.content, this.headers, - this.parameters, parts, this.cookies); + return new OperationRequestFactory().create(this.requestUri, this.method, this.content, this.headers, parts, + this.cookies); } public Operation build() { @@ -185,18 +181,6 @@ public class OperationBuilder extends OperationTestRule { return this; } - public OperationRequestBuilder param(String name, String... values) { - if (values.length > 0) { - for (String value : values) { - this.parameters.add(name, value); - } - } - else { - this.parameters.put(name, Collections.emptyList()); - } - return this; - } - public OperationRequestBuilder header(String name, String value) { this.headers.add(name, value); return this; diff --git a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java index 053df890..580bc763 100644 --- a/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java +++ b/spring-restdocs-mockmvc/src/main/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverter.java @@ -17,14 +17,18 @@ package org.springframework.restdocs.mockmvc; import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Scanner; import jakarta.servlet.ServletException; import jakarta.servlet.http.Part; @@ -39,10 +43,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.RequestConverter; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.util.FileCopyUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -55,36 +60,88 @@ import org.springframework.web.multipart.MultipartFile; */ class MockMvcRequestConverter implements RequestConverter { - private static final String SCHEME_HTTP = "http"; - - private static final String SCHEME_HTTPS = "https"; - - private static final int STANDARD_PORT_HTTP = 80; - - private static final int STANDARD_PORT_HTTPS = 443; - @Override public OperationRequest convert(MockHttpServletRequest mockRequest) { try { HttpHeaders headers = extractHeaders(mockRequest); - Parameters parameters = extractParameters(mockRequest); List parts = extractParts(mockRequest); Collection cookies = extractCookies(mockRequest, headers); - String queryString = mockRequest.getQueryString(); - if (!StringUtils.hasText(queryString) && "GET".equals(mockRequest.getMethod())) { - queryString = parameters.toQueryString(); - } - return new OperationRequestFactory().create( - URI.create( - getRequestUri(mockRequest) + (StringUtils.hasText(queryString) ? "?" + queryString : "")), - HttpMethod.valueOf(mockRequest.getMethod()), mockRequest.getContentAsByteArray(), headers, - parameters, parts, cookies); + return new OperationRequestFactory().create(getRequestUri(mockRequest), + HttpMethod.valueOf(mockRequest.getMethod()), getRequestContent(mockRequest, headers), headers, + parts, cookies); } catch (Exception ex) { throw new ConversionException(ex); } } + private URI getRequestUri(MockHttpServletRequest mockRequest) { + String queryString = ""; + if (mockRequest.getQueryString() != null) { + queryString = mockRequest.getQueryString(); + } + else if ("GET".equals(mockRequest.getMethod()) || mockRequest.getContentLengthLong() > 0) { + queryString = urlEncodedParameters(mockRequest); + } + StringBuffer requestUrlBuffer = mockRequest.getRequestURL(); + if (queryString.length() > 0) { + requestUrlBuffer.append("?").append(queryString.toString()); + } + return URI.create(requestUrlBuffer.toString()); + } + + private String urlEncodedParameters(MockHttpServletRequest mockRequest) { + StringBuilder parameters = new StringBuilder(); + MultiValueMap queryParameters = parse(mockRequest.getQueryString()); + for (String name : IterableEnumeration.of(mockRequest.getParameterNames())) { + if (!queryParameters.containsKey(name)) { + String[] values = mockRequest.getParameterValues(name); + if (values.length == 0) { + append(parameters, name); + } + else { + for (String value : values) { + append(parameters, name, value); + } + } + } + } + return parameters.toString(); + } + + private byte[] getRequestContent(MockHttpServletRequest mockRequest, HttpHeaders headers) { + byte[] content = mockRequest.getContentAsByteArray(); + if ("GET".equals(mockRequest.getMethod())) { + return content; + } + MediaType contentType = headers.getContentType(); + if (contentType == null || MediaType.APPLICATION_FORM_URLENCODED.includes(contentType)) { + Map parameters = mockRequest.getParameterMap(); + if (!parameters.isEmpty() && (content == null || content.length == 0)) { + StringBuilder contentBuilder = new StringBuilder(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap queryParameters = parse(mockRequest.getQueryString()); + mockRequest.getParameterMap().forEach((name, values) -> { + List queryParameterValues = queryParameters.get(name); + if (values.length == 0) { + if (queryParameterValues == null) { + append(contentBuilder, name); + } + } + else { + for (String value : values) { + if (queryParameterValues == null || !queryParameterValues.contains(value)) { + append(contentBuilder, name, value); + } + } + } + }); + return contentBuilder.toString().getBytes(StandardCharsets.UTF_8); + } + } + return content; + } + private Collection extractCookies(MockHttpServletRequest mockRequest, HttpHeaders headers) { if (mockRequest.getCookies() == null || mockRequest.getCookies().length == 0) { return Collections.emptyList(); @@ -158,16 +215,6 @@ class MockMvcRequestConverter implements RequestConverter 0) { + sb.append("&"); } - printer.print(request.getRequestURI()); - return uriWriter.toString(); + sb.append(toAppend); + } + + private static String urlEncode(String s) { + if (!StringUtils.hasLength(s)) { + return ""; + } + return URLEncoder.encode(s, StandardCharsets.UTF_8); + } + + private static MultiValueMap parse(String query) { + MultiValueMap parameters = new LinkedMultiValueMap<>(); + if (!StringUtils.hasLength(query)) { + return parameters; + } + try (Scanner scanner = new Scanner(query)) { + scanner.useDelimiter("&"); + while (scanner.hasNext()) { + processParameter(scanner.next(), parameters); + } + } + return parameters; + } + + private static void processParameter(String parameter, MultiValueMap parameters) { + String[] components = parameter.split("="); + if (components.length > 0 && components.length < 3) { + if (components.length == 2) { + String name = components[0]; + String value = components[1]; + parameters.add(decode(name), decode(value)); + } + else { + List values = parameters.computeIfAbsent(components[0], (p) -> new LinkedList<>()); + values.add(""); + } + } + else { + throw new IllegalArgumentException("The parameter '" + parameter + "' is malformed"); + } + } + + private static String decode(String encoded) { + return URLDecoder.decode(encoded, StandardCharsets.US_ASCII); } } diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java index a963f936..05264f58 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRequestConverterTests.java @@ -127,31 +127,35 @@ public class MockMvcRequestConverterTests { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.get("/foo").param("a", "alpha", "apple").param("b", "br&vo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&a=apple&b=br%26vo")); - assertThat(request.getParameters().size()).isEqualTo(2); - assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha", "apple")); - assertThat(request.getParameters()).containsEntry("b", Arrays.asList("br&vo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); } @Test - public void getRequestWithQueryStringPopulatesParameters() { - OperationRequest request = createOperationRequest(MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo")); + public void getRequestWithQueryString() { + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/foo?a=alpha&b=bravo"); + OperationRequest request = createOperationRequest(builder); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha&b=bravo")); - assertThat(request.getParameters().size()).isEqualTo(2); - assertThat(request.getParameters()).containsEntry("a", Arrays.asList("alpha")); - assertThat(request.getParameters()).containsEntry("b", Arrays.asList("bravo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.GET); } @Test - public void postRequestWithParameters() { + public void postRequestWithParametersCreatesFormUrlEncodedContent() { OperationRequest request = createOperationRequest( MockMvcRequestBuilders.post("/foo").param("a", "alpha", "apple").param("b", "br&vo")); assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo")); assertThat(request.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(request.getParameters().size()).isEqualTo(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(MediaType.APPLICATION_FORM_URLENCODED); + } + + @Test + public void postRequestWithParametersAndQueryStringCreatesFormUrlEncodedContentWithoutDuplication() { + OperationRequest request = createOperationRequest( + MockMvcRequestBuilders.post("/foo?a=alpha").param("a", "apple").param("b", "br&vo")); + assertThat(request.getUri()).isEqualTo(URI.create("http://localhost/foo?a=alpha")); + assertThat(request.getMethod()).isEqualTo(HttpMethod.POST); + assertThat(request.getContentAsString()).isEqualTo("a=apple&b=br%26vo"); + assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_FORM_URLENCODED); } @Test diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index fdd858bf..55007b96 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -92,7 +92,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.subsecti 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.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -178,7 +178,7 @@ public class MockMvcRestDocumentationIntegrationTests { public void curlSnippetWithQueryStringOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)).build(); - mockMvc.perform(post("/?foo=bar").param("foo", "bar").param("a", "alpha").accept(MediaType.APPLICATION_JSON)) + mockMvc.perform(post("/?foo=bar").param("a", "alpha").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andDo(document("curl-snippet-with-query-string")); assertThat(new File("build/generated-snippets/curl-snippet-with-query-string/curl-request.adoc")) .has(content(codeBlock(TemplateFormats.asciidoctor(), "bash") @@ -238,11 +238,11 @@ public class MockMvcRestDocumentationIntegrationTests { public void httpieSnippetWithQueryStringOnPost() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)).build(); - mockMvc.perform(post("/?foo=bar").param("foo", "bar").param("a", "alpha").accept(MediaType.APPLICATION_JSON)) + mockMvc.perform(post("/?foo=bar").param("a", "alpha").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andDo(document("httpie-snippet-with-query-string")); assertThat(new File("build/generated-snippets/httpie-snippet-with-query-string/httpie-request.adoc")) .has(content(codeBlock(TemplateFormats.asciidoctor(), "bash") - .withContent(String.format("$ http " + "--form POST 'http://localhost:8080/?foo=bar' \\%n" + .withContent(String.format("$ http --form POST 'http://localhost:8080/?foo=bar' \\%n" + " 'Accept:application/json' \\%n 'a=alpha'")))); } @@ -281,13 +281,13 @@ public class MockMvcRestDocumentationIntegrationTests { } @Test - public void requestParametersSnippet() throws Exception { + public void queryParametersSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .apply(documentationConfiguration(this.restDocumentation)).build(); mockMvc.perform(get("/").param("foo", "bar").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) - .andDo(document("links", requestParameters(parameterWithName("foo").description("The description")))); + .andDo(document("links", queryParameters(parameterWithName("foo").description("The description")))); assertExpectedSnippetFilesExist(new File("build/generated-snippets/links"), "http-request.adoc", - "http-response.adoc", "curl-request.adoc", "request-parameters.adoc"); + "http-response.adoc", "curl-request.adoc", "query-parameters.adoc"); } @Test diff --git a/spring-restdocs-restassured/build.gradle b/spring-restdocs-restassured/build.gradle index dbcd51ba..41df373b 100644 --- a/spring-restdocs-restassured/build.gradle +++ b/spring-restdocs-restassured/build.gradle @@ -21,4 +21,5 @@ dependencies { testImplementation("org.assertj:assertj-core") testImplementation("org.hamcrest:hamcrest-library") testImplementation("org.mockito:mockito-core") + testImplementation("ch.qos.logback:logback-classic:1.4.1") } diff --git a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java index f4e00b2e..091d51ec 100644 --- a/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java +++ b/spring-restdocs-restassured/src/main/java/org/springframework/restdocs/restassured/RestAssuredRequestConverter.java @@ -20,9 +20,12 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import io.restassured.http.Cookie; @@ -37,11 +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.RequestConverter; import org.springframework.restdocs.operation.RequestCookie; import org.springframework.util.FileCopyUtils; import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * A converter for creating an {@link OperationRequest} from a REST Assured @@ -56,7 +59,7 @@ class RestAssuredRequestConverter implements RequestConverter extractCookies(FilterableRequestSpecification requestSpec) { @@ -68,7 +71,36 @@ class RestAssuredRequestConverter implements RequestConverter parameters) { + for (Entry entry : parameters.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Iterable) { + for (Object v : (Iterable) value) { + append(content, name, v.toString()); + } + } + else if (value != null) { + append(content, name, value.toString()); + } + else { + append(content, name); + } + } } private byte[] convertContent(Object content) { @@ -131,28 +163,6 @@ class RestAssuredRequestConverter implements RequestConverter entry : requestSpec.getQueryParams().entrySet()) { - if (entry.getValue() instanceof Collection) { - Collection queryParams = ((Collection) entry.getValue()); - for (Object queryParam : queryParams) { - parameters.add(entry.getKey(), queryParam.toString()); - } - } - else { - parameters.add(entry.getKey(), entry.getValue().toString()); - } - } - for (Entry entry : requestSpec.getRequestParams().entrySet()) { - parameters.add(entry.getKey(), entry.getValue().toString()); - } - for (Entry entry : requestSpec.getFormParams().entrySet()) { - parameters.add(entry.getKey(), entry.getValue().toString()); - } - return parameters; - } - private Collection extractParts(FilterableRequestSpecification requestSpec) { List parts = new ArrayList<>(); for (MultiPartSpecification multiPartSpec : requestSpec.getMultiPartParams()) { @@ -165,4 +175,26 @@ class RestAssuredRequestConverter implements RequestConverter { + this.request = this.factory.convert(request); + return context.next(request, response); + }); + + @Test + public void queryParameterOnGet() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").get("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET); + } + + @Test + public void queryParameterOnHead() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").head("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD); + } + + @Test + public void queryParameterOnPost() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").post("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.POST); + } + + @Test + public void queryParameterOnPut() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").put("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT); + } + + @Test + public void queryParameterOnPatch() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").patch("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH); + } + + @Test + public void queryParameterOnDelete() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").delete("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE); + } + + @Test + public void queryParameterOnOptions() { + this.spec.queryParam("a", "alpha", "apple").queryParam("b", "bravo").options("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS); + } + + @Test + public void paramOnGet() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").get("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET); + } + + @Test + public void paramOnHead() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").head("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.HEAD); + } + + @Test + public void paramOnPost() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").post("/form-url-encoded").then().statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST); + } + + @Test + public void paramOnPut() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").put("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PUT); + } + + @Test + public void paramOnPatch() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").patch("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.PATCH); + } + + @Test + public void paramOnDelete() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").delete("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.DELETE); + } + + @Test + public void paramOnOptions() { + this.spec.param("a", "alpha", "apple").param("b", "bravo").options("/query-parameter").then().statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.OPTIONS); + } + + @Test + public void formParamOnGet() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").get("/query-parameter").then() + .statusCode(200); + assertThatRequest(this.request).hasQueryParametersWithMethod(HttpMethod.GET); + } + + @Test + public void formParamOnHead() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").head("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.HEAD); + } + + @Test + public void formParamOnPost() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").post("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.POST); + } + + @Test + public void formParamOnPut() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").put("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.PUT); + } + + @Test + public void formParamOnPatch() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").patch("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.PATCH); + } + + @Test + public void formParamOnDelete() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").delete("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.DELETE); + } + + @Test + public void formParamOnOptions() { + this.spec.formParam("a", "alpha", "apple").formParam("b", "bravo").options("/form-url-encoded").then() + .statusCode(200); + assertThatRequest(this.request).isFormUrlEncodedWithMethod(HttpMethod.OPTIONS); + } + + private OperationRequestAssert assertThatRequest(OperationRequest request) { + return new OperationRequestAssert(request); + } + + private static final class OperationRequestAssert extends AbstractAssert { + + private OperationRequestAssert(OperationRequest actual) { + super(actual, OperationRequestAssert.class); + } + + private void isFormUrlEncodedWithMethod(HttpMethod method) { + assertThat(this.actual.getMethod()).isEqualTo(method); + assertThat(this.actual.getUri().getRawQuery()).isNull(); + assertThat(this.actual.getContentAsString()).isEqualTo("a=alpha&a=apple&b=bravo"); + assertThat(this.actual.getHeaders().getContentType()).isEqualTo(APPLICATION_FORM_URLENCODED_ISO_8859_1); + } + + private void hasQueryParametersWithMethod(HttpMethod method) { + assertThat(this.actual.getMethod()).isEqualTo(method); + assertThat(this.actual.getUri().getRawQuery()).isEqualTo("a=alpha&a=apple&b=bravo"); + assertThat(this.actual.getContentAsString()).isEmpty(); + } + + } + +} diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java index ba17a0b5..ae8054b5 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRequestConverterTests.java @@ -21,7 +21,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.net.URI; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -79,8 +78,7 @@ public class RestAssuredRequestConverterTests { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).queryParam("foo", "bar"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getParameters()).hasSize(1); - assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar")); + assertThat(request.getUri().getRawQuery()).isEqualTo("foo=bar"); } @Test @@ -88,26 +86,15 @@ public class RestAssuredRequestConverterTests { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()); requestSpec.get("/?foo=bar&foo=qix"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getParameters()).hasSize(1); - assertThat(request.getParameters()).containsEntry("foo", Arrays.asList("bar", "qix")); + assertThat(request.getUri().getRawQuery()).isEqualTo("foo=bar&foo=qix"); } @Test - public void formParameters() { - RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).formParam("foo", "bar"); - requestSpec.get("/"); - OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getParameters()).hasSize(1); - assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar")); - } - - @Test - public void requestParameters() { + public void paramOnGetRequestIsMappedToQueryString() { RequestSpecification requestSpec = RestAssured.given().port(tomcat.getPort()).param("foo", "bar"); requestSpec.get("/"); OperationRequest request = this.factory.convert((FilterableRequestSpecification) requestSpec); - assertThat(request.getParameters()).hasSize(1); - assertThat(request.getParameters()).containsEntry("foo", Collections.singletonList("bar")); + assertThat(request.getUri().getRawQuery()).isEqualTo("foo=bar"); } @Test diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java index 18bacf8e..37aa74c3 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/RestAssuredRestDocumentationIntegrationTests.java @@ -68,7 +68,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.subsecti 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.restassured.RestAssuredRestDocumentation.document; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; @@ -143,7 +143,7 @@ public class RestAssuredRestDocumentationIntegrationTests { .has(content(codeBlock(TemplateFormats.asciidoctor(), "bash") .withContent(String.format("$ curl " + "'http://localhost:" + tomcat.getPort() + "/?foo=bar' -i -X POST \\%n" + " -H 'Accept: application/json' \\%n" - + " -H 'Content-Type: " + contentType + "' \\%n" + " -d 'a=alpha'")))); + + " -H 'Content-Type: " + contentType + "' \\%n" + " -d 'foo=bar&a=alpha'")))); } @Test @@ -166,13 +166,13 @@ public class RestAssuredRestDocumentationIntegrationTests { } @Test - public void requestParametersSnippet() { + public void queryParametersSnippet() { given().port(tomcat.getPort()).filter(documentationConfiguration(this.restDocumentation)) - .filter(document("request-parameters", - requestParameters(parameterWithName("foo").description("The description")))) + .filter(document("query-parameters", + queryParameters(parameterWithName("foo").description("The description")))) .accept("application/json").param("foo", "bar").get("/").then().statusCode(200); - assertExpectedSnippetFilesExist(new File("build/generated-snippets/request-parameters"), "http-request.adoc", - "http-response.adoc", "curl-request.adoc", "request-parameters.adoc"); + assertExpectedSnippetFilesExist(new File("build/generated-snippets/query-parameters"), "http-request.adoc", + "http-response.adoc", "curl-request.adoc", "query-parameters.adoc"); } @Test @@ -385,8 +385,10 @@ public class RestAssuredRestDocumentationIntegrationTests { @Override public boolean matches(File value) { try { - return delegate.matches(FileCopyUtils - .copyToString(new InputStreamReader(new FileInputStream(value), StandardCharsets.UTF_8))); + String copyToString = FileCopyUtils + .copyToString(new InputStreamReader(new FileInputStream(value), StandardCharsets.UTF_8)); + System.out.println(copyToString); + return delegate.matches(copyToString); } catch (IOException ex) { fail("Failed to read '" + value + "'", ex); diff --git a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java index 38e5fbb3..55e3badb 100644 --- a/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java +++ b/spring-restdocs-restassured/src/test/java/org/springframework/restdocs/restassured/TomcatServer.java @@ -17,6 +17,7 @@ package org.springframework.restdocs.restassured; import java.io.IOException; +import java.io.InputStreamReader; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -33,6 +34,9 @@ import org.apache.catalina.LifecycleException; import org.apache.catalina.startup.Tomcat; import org.junit.rules.ExternalResource; +import org.springframework.http.MediaType; +import org.springframework.util.FileCopyUtils; + /** * {@link ExternalResource} that starts and stops a Tomcat server. * @@ -53,6 +57,10 @@ class TomcatServer extends ExternalResource { context.addServletMappingDecoded("/", "test"); this.tomcat.addServlet("/", "set-cookie", new CookiesServlet()); context.addServletMappingDecoded("/set-cookie", "set-cookie"); + this.tomcat.addServlet("/", "query-parameter", new QueryParameterServlet()); + context.addServletMappingDecoded("/query-parameter", "query-parameter"); + this.tomcat.addServlet("/", "form-url-encoded", new FormUrlEncodedServlet()); + context.addServletMappingDecoded("/form-url-encoded", "form-url-encoded"); this.tomcat.start(); this.port = this.tomcat.getConnector().getLocalPort(); } @@ -121,4 +129,33 @@ class TomcatServer extends ExternalResource { } + private static final class QueryParameterServlet extends HttpServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!req.getQueryString().equals("a=alpha&a=apple&b=bravo")) { + throw new ServletException("Incorrect query string"); + } + resp.setStatus(200); + } + + } + + private static final class FormUrlEncodedServlet extends HttpServlet { + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!MediaType.APPLICATION_FORM_URLENCODED + .isCompatibleWith(MediaType.parseMediaType(req.getContentType()))) { + throw new ServletException("Incorrect Content-Type"); + } + String content = FileCopyUtils.copyToString(new InputStreamReader(req.getInputStream())); + if (!"a=alpha&a=apple&b=bravo".equals(content)) { + throw new ServletException("Incorrect body content"); + } + resp.setStatus(200); + } + + } + } diff --git a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java index 4afabb82..30ebbaec 100644 --- a/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java +++ b/spring-restdocs-webtestclient/src/main/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverter.java @@ -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 { - 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 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 extractRequestParts(ExchangeResult result) { HttpMessageReader partHttpMessageReader = new DefaultPartHttpMessageReader(); return new MultipartHttpMessageReader(partHttpMessageReader) diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java index d416c084..ac6e32dc 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRequestConverterTests.java @@ -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 diff --git a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java index 57b7ef30..87662a43 100644 --- a/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java +++ b/spring-restdocs-webtestclient/src/test/java/org/springframework/restdocs/webtestclient/WebTestClientRestDocumentationIntegrationTests.java @@ -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 content(final Condition delegate) { - return new Condition() { + return new Condition<>() { @Override public boolean matches(File value) {