Skip to content

Commit 40201e2

Browse files
committed
Improve handling of empty parameters in curl and HTTP request snippets
Previously, both the curl and HTTP request snippets would ignore a parameter with no value, for example from the query string of the url http://localhost:8080/foo?bar. This commit updates both snippets so that such parameters are included in the generated snippet, including a multi-part request that is uploading form data and a field in the form has no value. Additions have been made to the tests for both snippets. While the request parameters snippet correctly handled parameters with no value, there was no test verifying that this was the case. One has been added in this commit. Closes gh-200
1 parent 4d44401 commit 40201e2

File tree

7 files changed

+154
-20
lines changed

7 files changed

+154
-20
lines changed

spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
1919
import java.io.UnsupportedEncodingException;
2020
import java.net.URI;
2121
import java.net.URLDecoder;
22+
import java.util.LinkedList;
23+
import java.util.List;
2224
import java.util.Scanner;
2325

2426
import org.springframework.restdocs.operation.Parameters;
@@ -58,10 +60,18 @@ private Parameters parse(String query) {
5860

5961
private void processParameter(String parameter, Parameters parameters) {
6062
String[] components = parameter.split("=");
61-
if (components.length == 2) {
62-
String name = components[0];
63-
String value = components[1];
64-
parameters.add(decode(name), decode(value));
63+
if (components.length > 0 && components.length < 3) {
64+
if (components.length == 2) {
65+
String name = components[0];
66+
String value = components[1];
67+
parameters.add(decode(name), decode(value));
68+
}
69+
else {
70+
List<String> values = parameters.get(components[0]);
71+
if (values == null) {
72+
parameters.put(components[0], new LinkedList<String>());
73+
}
74+
}
6575
}
6676
else {
6777
throw new IllegalArgumentException(

spring-restdocs-core/src/main/java/org/springframework/restdocs/http/HttpRequestSnippet.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,16 @@ private boolean isPutOrPost(OperationRequest request) {
127127
private void writeParts(OperationRequest request, PrintWriter writer) {
128128
writer.println();
129129
for (Entry<String, List<String>> parameter : request.getParameters().entrySet()) {
130-
for (String value : parameter.getValue()) {
130+
if (parameter.getValue().isEmpty()) {
131131
writePartBoundary(writer);
132-
writePart(parameter.getKey(), value, null, writer);
133-
writer.println();
132+
writePart(parameter.getKey(), "", null, writer);
133+
}
134+
else {
135+
for (String value : parameter.getValue()) {
136+
writePartBoundary(writer);
137+
writePart(parameter.getKey(), value, null, writer);
138+
writer.println();
139+
}
134140
}
135141
}
136142
for (OperationRequestPart part : request.getParts()) {

spring-restdocs-core/src/main/java/org/springframework/restdocs/operation/Parameters.java

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,17 +40,33 @@ public class Parameters extends LinkedMultiValueMap<String, String> {
4040
public String toQueryString() {
4141
StringBuilder sb = new StringBuilder();
4242
for (Map.Entry<String, List<String>> entry : entrySet()) {
43-
for (String value : entry.getValue()) {
44-
if (sb.length() > 0) {
45-
sb.append("&");
43+
if (entry.getValue().isEmpty()) {
44+
append(sb, entry.getKey());
45+
}
46+
else {
47+
for (String value : entry.getValue()) {
48+
append(sb, entry.getKey(), value);
4649
}
47-
sb.append(urlEncodeUTF8(entry.getKey())).append('=')
48-
.append(urlEncodeUTF8(value));
4950
}
5051
}
5152
return sb.toString();
5253
}
5354

55+
private static void append(StringBuilder sb, String key) {
56+
append(sb, key, "");
57+
}
58+
59+
private static void append(StringBuilder sb, String key, String value) {
60+
doAppend(sb, urlEncodeUTF8(key) + "=" + urlEncodeUTF8(value));
61+
}
62+
63+
private static void doAppend(StringBuilder sb, String toAppend) {
64+
if (sb.length() > 0) {
65+
sb.append("&");
66+
}
67+
sb.append(toAppend);
68+
}
69+
5470
private static String urlEncodeUTF8(String s) {
5571
try {
5672
return URLEncoder.encode(s, "UTF-8");

spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,6 +95,17 @@ public void getRequestWithQueryString() throws IOException {
9595
.request("http://localhost/foo?param=value").build());
9696
}
9797

98+
@Test
99+
public void getRequestWithQueryStringWithNoValue() throws IOException {
100+
this.snippet.expectCurlRequest("request-with-query-string-with-no-value")
101+
.withContents(codeBlock("bash")
102+
.content("$ curl 'http://localhost/foo?param' -i"));
103+
new CurlRequestSnippet()
104+
.document(new OperationBuilder("request-with-query-string-with-no-value",
105+
this.snippet.getOutputDirectory())
106+
.request("http://localhost/foo?param").build());
107+
}
108+
98109
@Test
99110
public void postRequestWithQueryString() throws IOException {
100111
this.snippet.expectCurlRequest("post-request-with-query-string")
@@ -107,6 +118,18 @@ public void postRequestWithQueryString() throws IOException {
107118
.method("POST").build());
108119
}
109120

121+
@Test
122+
public void postRequestWithQueryStringWithNoValue() throws IOException {
123+
this.snippet.expectCurlRequest("post-request-with-query-string-with-no-value")
124+
.withContents(codeBlock("bash")
125+
.content("$ curl 'http://localhost/foo?param' -i -X POST"));
126+
new CurlRequestSnippet().document(
127+
new OperationBuilder("post-request-with-query-string-with-no-value",
128+
this.snippet.getOutputDirectory())
129+
.request("http://localhost/foo?param").method("POST")
130+
.build());
131+
}
132+
110133
@Test
111134
public void postRequestWithOneParameter() throws IOException {
112135
this.snippet.expectCurlRequest("post-request-with-one-parameter")
@@ -118,6 +141,17 @@ public void postRequestWithOneParameter() throws IOException {
118141
.method("POST").param("k1", "v1").build());
119142
}
120143

144+
@Test
145+
public void postRequestWithOneParameterWithNoValue() throws IOException {
146+
this.snippet.expectCurlRequest("post-request-with-one-parameter-with-no-value")
147+
.withContents(codeBlock("bash")
148+
.content("$ curl 'http://localhost/foo' -i -X POST -d 'k1='"));
149+
new CurlRequestSnippet().document(
150+
new OperationBuilder("post-request-with-one-parameter-with-no-value",
151+
this.snippet.getOutputDirectory()).request("http://localhost/foo")
152+
.method("POST").param("k1").build());
153+
}
154+
121155
@Test
122156
public void postRequestWithMultipleParameters() throws IOException {
123157
this.snippet.expectCurlRequest("post-request-with-multiple-parameters")

spring-restdocs-core/src/test/java/org/springframework/restdocs/http/HttpRequestSnippetTests.java

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,6 +75,18 @@ public void getRequestWithQueryString() throws IOException {
7575
.request("http://localhost/foo?bar=baz").build());
7676
}
7777

78+
@Test
79+
public void getRequestWithQueryStringWithNoValue() throws IOException {
80+
this.snippet.expectHttpRequest("get-request-with-query-string-with-no-value")
81+
.withContents(httpRequest(RequestMethod.GET, "/foo?bar")
82+
.header(HttpHeaders.HOST, "localhost"));
83+
84+
new HttpRequestSnippet().document(
85+
new OperationBuilder("get-request-with-query-string-with-no-value",
86+
this.snippet.getOutputDirectory())
87+
.request("http://localhost/foo?bar").build());
88+
}
89+
7890
@Test
7991
public void postRequestWithContent() throws IOException {
8092
String content = "Hello, world";
@@ -123,6 +135,20 @@ public void postRequestWithParameter() throws IOException {
123135
.build());
124136
}
125137

138+
@Test
139+
public void postRequestWithParameterWithNoValue() throws IOException {
140+
this.snippet.expectHttpRequest("post-request-with-parameter")
141+
.withContents(httpRequest(RequestMethod.POST, "/foo")
142+
.header(HttpHeaders.HOST, "localhost")
143+
.header("Content-Type", "application/x-www-form-urlencoded")
144+
.content("bar="));
145+
146+
new HttpRequestSnippet()
147+
.document(new OperationBuilder("post-request-with-parameter",
148+
this.snippet.getOutputDirectory()).request("http://localhost/foo")
149+
.method("POST").param("bar").build());
150+
}
151+
126152
@Test
127153
public void putRequestWithContent() throws IOException {
128154
String content = "Hello, world";
@@ -201,6 +227,30 @@ public void multipartPostWithParameters() throws IOException {
201227
.part("image", "<< data >>".getBytes()).build());
202228
}
203229

230+
@Test
231+
public void multipartPostWithParameterWithNoValue() throws IOException {
232+
String paramPart = createPart(
233+
String.format("Content-Disposition: form-data; " + "name=a%n"), false);
234+
String filePart = createPart(String
235+
.format("Content-Disposition: form-data; " + "name=image%n%n<< data >>"));
236+
String expectedContent = paramPart + filePart;
237+
this.snippet
238+
.expectHttpRequest(
239+
"multipart-post-with-parameter-with-no-value")
240+
.withContents(httpRequest(RequestMethod.POST, "/upload")
241+
.header("Content-Type",
242+
"multipart/form-data; boundary=" + BOUNDARY)
243+
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
244+
new HttpRequestSnippet().document(
245+
new OperationBuilder("multipart-post-with-parameter-with-no-value",
246+
this.snippet.getOutputDirectory())
247+
.request("http://localhost/upload").method("POST")
248+
.header(HttpHeaders.CONTENT_TYPE,
249+
MediaType.MULTIPART_FORM_DATA_VALUE)
250+
.param("a").part("image", "<< data >>".getBytes())
251+
.build());
252+
}
253+
204254
@Test
205255
public void multipartPostWithContentType() throws IOException {
206256
String expectedContent = createPart(

spring-restdocs-core/src/test/java/org/springframework/restdocs/request/RequestParametersSnippetTests.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -109,6 +109,18 @@ public void requestParameters() throws IOException {
109109
.build());
110110
}
111111

112+
@Test
113+
public void requestParameterWithNoValue() throws IOException {
114+
this.snippet.expectRequestParameters("request-parameter-with-no-value")
115+
.withContents(
116+
tableWithHeader("Parameter", "Description").row("a", "one"));
117+
new RequestParametersSnippet(
118+
Arrays.asList(parameterWithName("a").description("one")))
119+
.document(new OperationBuilder("request-parameter-with-no-value",
120+
this.snippet.getOutputDirectory())
121+
.request("http://localhost").param("a").build());
122+
}
123+
112124
@Test
113125
public void ignoredRequestParameter() throws IOException {
114126
this.snippet.expectRequestParameters("ignored-request-parameter").withContents(

spring-restdocs-core/src/test/java/org/springframework/restdocs/test/OperationBuilder.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.net.URI;
2121
import java.util.ArrayList;
22+
import java.util.Collections;
2223
import java.util.HashMap;
2324
import java.util.List;
2425
import java.util.Map;
@@ -146,8 +147,13 @@ public OperationRequestBuilder content(byte[] content) {
146147
}
147148

148149
public OperationRequestBuilder param(String name, String... values) {
149-
for (String value : values) {
150-
this.parameters.add(name, value);
150+
if (values.length > 0) {
151+
for (String value : values) {
152+
this.parameters.add(name, value);
153+
}
154+
}
155+
else {
156+
this.parameters.put(name, Collections.<String>emptyList());
151157
}
152158
return this;
153159
}

0 commit comments

Comments
 (0)