Improve curl request snippet's handling of query parameters
Previously, if a request had no content and it was a POST or a PUT
request, the curl request snippet would use the request's parameters
as application/x-www-form-urlencoded data sent using the -d option.
This resulted in the wrong snippet being produced if those parameters
had actually come from the request's query string.
This commit enhances CurlRequestSnippet so that, when it's using the
request's parameter's to create its content, it only includes
parameters that are not specified in the query string. With these
changes in place, this MockMvc request:
post("/?foo=bar").param("foo", "bar").param("a", "alpha")
Will produce a curl snippet with the following command:
$ curl 'http://localhost:8080/?foo=bar' -i -X POST -d 'a=alpha'
foo=bar only appears in the command's query string, despite also
being a general request parameter, and a=alpha only appears in the
request's data.
Closes gh-139
This commit is contained in:
@@ -31,6 +31,7 @@ import org.springframework.http.HttpMethod;
|
||||
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.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.util.Base64Utils;
|
||||
@@ -165,9 +166,41 @@ public class CurlRequestSnippet extends TemplatedSnippet {
|
||||
}
|
||||
}
|
||||
else if (isPutOrPost(request)) {
|
||||
String queryString = request.getParameters().toQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
writer.print(String.format(" -d '%s'", queryString));
|
||||
writeContentUsingParameters(request, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContentUsingParameters(OperationRequest request, PrintWriter writer) {
|
||||
Parameters uniqueParameters = getUniqueParameters(request);
|
||||
String queryString = uniqueParameters.toQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
writer.print(String.format(" -d '%s'", queryString));
|
||||
}
|
||||
}
|
||||
|
||||
private Parameters getUniqueParameters(OperationRequest request) {
|
||||
Parameters queryStringParameters = new QueryStringParser()
|
||||
.parse(request.getUri());
|
||||
Parameters uniqueParameters = new Parameters();
|
||||
|
||||
for (Entry<String, List<String>> parameter : request.getParameters().entrySet()) {
|
||||
addIfUnique(parameter, queryStringParameters, uniqueParameters);
|
||||
}
|
||||
return uniqueParameters;
|
||||
}
|
||||
|
||||
private void addIfUnique(Entry<String, List<String>> parameter,
|
||||
Parameters queryStringParameters, Parameters uniqueParameters) {
|
||||
if (!queryStringParameters.containsKey(parameter.getKey())) {
|
||||
uniqueParameters.put(parameter.getKey(), parameter.getValue());
|
||||
}
|
||||
else {
|
||||
List<String> candidates = parameter.getValue();
|
||||
List<String> existing = queryStringParameters.get(parameter.getKey());
|
||||
for (String candidate : candidates) {
|
||||
if (!existing.contains(candidate)) {
|
||||
uniqueParameters.add(parameter.getKey(), candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.curl;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
|
||||
/**
|
||||
* A parser for the query string of a URI.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class QueryStringParser {
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
scanner.useDelimiter("&");
|
||||
while (scanner.hasNext()) {
|
||||
processParameter(scanner.next(), parameters);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private void processParameter(String parameter, Parameters parameters) {
|
||||
String[] components = parameter.split("=");
|
||||
if (components.length == 2) {
|
||||
String name = components[0];
|
||||
String value = components[1];
|
||||
parameters.add(decode(name), decode(value));
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("The parameter '" + parameter
|
||||
+ "' is malformed");
|
||||
}
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class CurlRequestSnippetTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWithQueryString() throws IOException {
|
||||
public void getRequestWithQueryString() throws IOException {
|
||||
this.snippet.expectCurlRequest("request-with-query-string")
|
||||
.withContents(
|
||||
codeBlock("bash").content(
|
||||
@@ -94,6 +94,16 @@ public class CurlRequestSnippetTests {
|
||||
"http://localhost/foo?param=value").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithQueryString() throws IOException {
|
||||
this.snippet.expectCurlRequest("post-request-with-query-string").withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ curl 'http://localhost/foo?param=value' -i -X POST"));
|
||||
new CurlRequestSnippet().document(new OperationBuilder(
|
||||
"post-request-with-query-string", this.snippet.getOutputDirectory())
|
||||
.request("http://localhost/foo?param=value").method("POST").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithOneParameter() throws IOException {
|
||||
this.snippet.expectCurlRequest("post-request-with-one-parameter").withContents(
|
||||
@@ -132,6 +142,35 @@ public class CurlRequestSnippetTests {
|
||||
.method("POST").param("k1", "a&b").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithQueryStringAndParameter() throws IOException {
|
||||
this.snippet
|
||||
.expectCurlRequest("post-request-with-query-string-and-parameter")
|
||||
.withContents(
|
||||
codeBlock("bash")
|
||||
.content(
|
||||
"$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'"));
|
||||
new CurlRequestSnippet().document(new OperationBuilder(
|
||||
"post-request-with-query-string-and-parameter", this.snippet
|
||||
.getOutputDirectory()).request("http://localhost/foo?a=alpha")
|
||||
.method("POST").param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithOverlappingQueryStringAndParameters() throws IOException {
|
||||
this.snippet
|
||||
.expectCurlRequest(
|
||||
"post-request-with-overlapping-query-string-and-parameters")
|
||||
.withContents(
|
||||
codeBlock("bash")
|
||||
.content(
|
||||
"$ curl 'http://localhost/foo?a=alpha' -i -X POST -d 'b=bravo'"));
|
||||
new CurlRequestSnippet().document(new OperationBuilder(
|
||||
"post-request-with-overlapping-query-string-and-parameters", this.snippet
|
||||
.getOutputDirectory()).request("http://localhost/foo?a=alpha")
|
||||
.method("POST").param("a", "alpha").param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putRequestWithOneParameter() throws IOException {
|
||||
this.snippet.expectCurlRequest("put-request-with-one-parameter").withContents(
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.curl;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link QueryStringParser}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class QueryStringParserTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final QueryStringParser queryStringParser = new QueryStringParser();
|
||||
|
||||
@Test
|
||||
public void noParameters() {
|
||||
Parameters parameters = this.queryStringParser.parse(URI
|
||||
.create("http://localhost"));
|
||||
assertThat(parameters.size(), is(equalTo(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleParameter() {
|
||||
Parameters parameters = this.queryStringParser.parse(URI
|
||||
.create("http://localhost?a=alpha"));
|
||||
assertThat(parameters.size(), is(equalTo(1)));
|
||||
assertThat(parameters, hasEntry("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(), is(equalTo(3)));
|
||||
assertThat(parameters, hasEntry("a", Arrays.asList("alpha")));
|
||||
assertThat(parameters, hasEntry("b", Arrays.asList("bravo")));
|
||||
assertThat(parameters, hasEntry("c", Arrays.asList("charlie")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleParametersWithSameKey() {
|
||||
Parameters parameters = this.queryStringParser.parse(URI
|
||||
.create("http://localhost?a=apple&a=avocado"));
|
||||
assertThat(parameters.size(), is(equalTo(1)));
|
||||
assertThat(parameters, hasEntry("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(), is(equalTo(1)));
|
||||
assertThat(parameters, hasEntry("a", Arrays.asList("al&=pha")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedParameter() {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown
|
||||
.expectMessage(equalTo("The parameter 'a=apple=avocado' is malformed"));
|
||||
this.queryStringParser.parse(URI.create("http://localhost?a=apple=avocado"));
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,25 @@ public class MockMvcRestDocumentationIntegrationTests {
|
||||
+ "-H 'Accept: application/json' -d 'content'"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
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)).andExpect(status().isOk())
|
||||
.andDo(document("curl-snippet-with-query-string"));
|
||||
assertThat(
|
||||
new File(
|
||||
"build/generated-snippets/curl-snippet-with-query-string/curl-request.adoc"),
|
||||
is(snippet().withContents(
|
||||
codeBlock("bash").content(
|
||||
"$ curl "
|
||||
+ "'http://localhost:8080/?foo=bar' -i -X POST "
|
||||
+ "-H 'Accept: application/json' -d 'a=alpha'"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linksSnippet() throws Exception {
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
|
||||
Reference in New Issue
Block a user