Skip to content

Commit f7c67d5

Browse files
authored
Added queryParams to the HttpRequestInformation annotation. (Azure#40376)
* Renamed `HttpRequestInformation.requestHeaders()` to `headers()`. * Added `HttpRequestInformation.queryParams()`. * Added logic to parse query parameters to `SwaggerMethodParser`. * Added tests and updated `HttpClientTestsServer` and `HttpBinJSON` to handle query params. * Added comma escaping and a test. * Applied PR feedback and added more tests. * Removed empty list creation for empty query param values. * Also encoded query param names.
1 parent 271d3c0 commit f7c67d5

File tree

9 files changed

+304
-16
lines changed

9 files changed

+304
-16
lines changed

sdk/clientcore/core/src/main/java/io/clientcore/core/http/annotation/HeaderParam.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
/**
1313
* Replaces the header with the value of its target. The value specified here replaces headers specified statically in
14-
* the {@link HttpRequestInformation#requestHeaders()}. If the parameter this annotation is attached to is a Map type, then
14+
* the {@link HttpRequestInformation#headers()}. If the parameter this annotation is attached to is a Map type, then
1515
* this will be treated as a header collection. In that case each of the entries in the argument's map will be
1616
* individual header values that use the value of this annotation as a prefix to their key/header name.
1717
*

sdk/clientcore/core/src/main/java/io/clientcore/core/http/annotation/HttpRequestInformation.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
*
4343
* @return The list of static headers to send with the request.
4444
*/
45-
String[] requestHeaders() default {};
45+
String[] headers() default {};
4646

4747
/**
4848
* Get expected the status code(s) to receive with a response.
@@ -57,4 +57,12 @@
5757
* @return The type of the response body sent over the wire.
5858
*/
5959
Class<?> returnValueWireType() default Void.class;
60+
61+
/**
62+
* Get the query parameters to be appended to the request URL. Elements in the array shall be written in the format
63+
* of 'key=value'.
64+
*
65+
* @return The query parameters to be appended to the request URL.
66+
*/
67+
String[] queryParams() default {};
6068
}

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/http/rest/SwaggerMethodParser.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.BitSet;
4848
import java.util.Collections;
4949
import java.util.HashMap;
50+
import java.util.LinkedHashMap;
5051
import java.util.List;
5152
import java.util.Map;
5253
import java.util.Objects;
@@ -72,6 +73,7 @@ public class SwaggerMethodParser implements HttpResponseDecodeData {
7273
private final ClientLogger methodLogger;
7374
private final HttpMethod httpMethod;
7475
private final String relativePath;
76+
private final Map<String, List<String>> queryParams = new LinkedHashMap<>();
7577
final List<RangeReplaceSubstitution> hostSubstitutions = new ArrayList<>();
7678
private final List<RangeReplaceSubstitution> pathSubstitutions = new ArrayList<>();
7779
private final List<QuerySubstitution> querySubstitutions = new ArrayList<>();
@@ -122,7 +124,7 @@ public SwaggerMethodParser(Method swaggerMethod) {
122124

123125
returnType = swaggerMethod.getGenericReturnType();
124126

125-
final String[] requestHeaders = httpRequestInformation.requestHeaders();
127+
final String[] requestHeaders = httpRequestInformation.headers();
126128

127129
if (requestHeaders != null) {
128130
for (final String requestHeader : requestHeaders) {
@@ -148,6 +150,50 @@ public SwaggerMethodParser(Method swaggerMethod) {
148150
}
149151
}
150152

153+
final String[] requestQueryParams = httpRequestInformation.queryParams();
154+
155+
if (requestQueryParams != null) {
156+
for (final String queryParam : requestQueryParams) {
157+
if (CoreUtils.isNullOrEmpty(queryParam)) {
158+
throw new IllegalStateException("Query parameters cannot be null or empty.");
159+
}
160+
161+
// We take the first equals sign as the delimiter between the name and value of the query parameter.
162+
// If more than one equals sign is present, the rest of the string is considered part of the value.
163+
final int equalsIndex = queryParam.indexOf("=");
164+
final String paramName;
165+
final String paramValue;
166+
167+
if (equalsIndex >= 0) {
168+
paramName = UrlEscapers.QUERY_ESCAPER.escape(queryParam.substring(0, equalsIndex));
169+
170+
if (!paramName.isEmpty()) {
171+
paramValue = UrlEscapers.QUERY_ESCAPER.escape(queryParam.substring(equalsIndex + 1));
172+
} else {
173+
throw new IllegalStateException("Names for query parameters cannot be empty.");
174+
}
175+
} else {
176+
// No equals sign was found, so the entire string is considered the name of the query parameter.
177+
paramName = UrlEscapers.QUERY_ESCAPER.escape(queryParam);
178+
paramValue = null;
179+
}
180+
181+
List<String> currentValues = queryParams.get(paramName);
182+
183+
if (!CoreUtils.isNullOrEmpty(paramValue)) {
184+
if (currentValues == null) {
185+
currentValues = new ArrayList<>();
186+
}
187+
188+
currentValues.add(paramValue);
189+
190+
queryParams.put(paramName, currentValues);
191+
} else {
192+
queryParams.put(paramName, null);
193+
}
194+
}
195+
}
196+
151197
Class<?> returnValueWireType = httpRequestInformation.returnValueWireType();
152198

153199
if (returnValueWireType == Base64Url.class || returnValueWireType == DateTimeRfc1123.class) {
@@ -227,8 +273,10 @@ public SwaggerMethodParser(Method swaggerMethod) {
227273
Class<?>[] parameterTypes = swaggerMethod.getParameterTypes();
228274
int requestOptionsPosition = -1;
229275
int serverSentEventListenerPosition = -1;
276+
230277
for (int i = 0; i < parameterTypes.length; i++) {
231278
Class<?> parameterType = parameterTypes[i];
279+
232280
// Check for the RequestOptions position.
233281
// To retain previous behavior, only track the first instance found.
234282
if (parameterType == RequestOptions.class && requestOptionsPosition == -1) {
@@ -327,10 +375,23 @@ public String setPath(Object[] methodArguments, ObjectSerializer serializer) {
327375
@SuppressWarnings("unchecked")
328376
public void setEncodedQueryParameters(Object[] swaggerMethodArguments, UrlBuilder urlBuilder,
329377
ObjectSerializer serializer) {
378+
// First we add the constant query parameters.
379+
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
380+
if (entry.getValue() == null || entry.getValue().isEmpty()) {
381+
urlBuilder.addQueryParameter(entry.getKey(), null);
382+
} else {
383+
for (String paramValue : entry.getValue()) {
384+
urlBuilder.addQueryParameter(entry.getKey(), paramValue);
385+
}
386+
}
387+
}
388+
330389
if (swaggerMethodArguments == null) {
331390
return;
332391
}
333392

393+
// Then we add query parameters passed as arguments to the request method. If any of them share a name with the
394+
// constant query parameters, the constant query parameter will be overwritten.
334395
for (QuerySubstitution substitution : querySubstitutions) {
335396
final int parameterIndex = substitution.getMethodParameterIndex();
336397

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/QueryParameter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public String toString() {
153153
if (value != null) {
154154
return name + "=" + value;
155155
} else if (CoreUtils.isNullOrEmpty(values)) {
156-
return "";
156+
return name;
157157
}
158158

159159
checkCachedStringValue();

sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/UrlBuilder.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,24 @@ private void appendQueryString(StringBuilder stringBuilder) {
282282
}
283283

284284
private static boolean writeQueryValues(StringBuilder builder, String key, List<String> values, boolean first) {
285-
for (String value : values) {
285+
if (CoreUtils.isNullOrEmpty(values)) {
286286
if (!first) {
287287
builder.append('&');
288288
}
289289

290-
builder.append(key).append('=').append(value);
290+
builder.append(key);
291+
291292
first = false;
293+
} else {
294+
for (String value : values) {
295+
if (!first) {
296+
builder.append('&');
297+
}
298+
299+
builder.append(key).append('=').append(value);
300+
301+
first = false;
302+
}
292303
}
293304

294305
return first;

sdk/clientcore/core/src/test/java/io/clientcore/core/implementation/http/rest/SwaggerMethodParserTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,14 @@ interface HeaderMethods {
170170
@HttpRequestInformation(method = HttpMethod.GET, path = "test")
171171
void noHeaders();
172172

173-
@HttpRequestInformation(method = HttpMethod.GET, path = "test", requestHeaders = {"", ":", "nameOnly:", ":valueOnly"})
173+
@HttpRequestInformation(method = HttpMethod.GET, path = "test", headers = {"", ":", "nameOnly:", ":valueOnly"})
174174
void malformedHeaders();
175175

176176
@HttpRequestInformation(method = HttpMethod.GET, path = "test",
177-
requestHeaders = {"name1:value1", "name2:value2", "name3:value3"})
177+
headers = {"name1:value1", "name2:value2", "name3:value3"})
178178
void headers();
179179

180-
@HttpRequestInformation(method = HttpMethod.GET, path = "test", requestHeaders = {"name:value1", "name:value2"})
180+
@HttpRequestInformation(method = HttpMethod.GET, path = "test", headers = {"name:value1", "name:value2"})
181181
void sameKeyTwiceLastWins();
182182
}
183183

@@ -395,7 +395,7 @@ interface HeaderSubstitutionMethods {
395395
@HttpRequestInformation(method = HttpMethod.GET, path = "test")
396396
void addHeaders(@HeaderParam("sub1") String sub1, @HeaderParam("sub2") boolean sub2);
397397

398-
@HttpRequestInformation(method = HttpMethod.GET, path = "test", requestHeaders = {"sub1:sub1", "sub2:false"})
398+
@HttpRequestInformation(method = HttpMethod.GET, path = "test", headers = {"sub1:sub1", "sub2:false"})
399399
void overrideHeaders(@HeaderParam("sub1") String sub1, @HeaderParam("sub2") boolean sub2);
400400

401401
@HttpRequestInformation(method = HttpMethod.GET, path = "test")

sdk/clientcore/core/src/test/java/io/clientcore/core/shared/HttpBinJSON.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class HttpBinJSON implements JsonSerializable<HttpBinJSON> {
2323

2424
private Object data;
2525

26+
private Map<String, List<String>> queryParams;
27+
2628
/**
2729
* Gets the URL associated with this request.
2830
*
@@ -39,6 +41,7 @@ public String url() {
3941
*/
4042
public HttpBinJSON url(String url) {
4143
this.url = url;
44+
4245
return this;
4346
}
4447

@@ -58,6 +61,7 @@ public Map<String, List<String>> headers() {
5861
*/
5962
public HttpBinJSON headers(Map<String, List<String>> headers) {
6063
this.headers = headers;
64+
6165
return this;
6266
}
6367

@@ -77,6 +81,27 @@ public Object data() {
7781
*/
7882
public HttpBinJSON data(Object data) {
7983
this.data = data;
84+
85+
return this;
86+
}
87+
88+
/**
89+
* Gets the response headers.
90+
*
91+
* @return The response headers.
92+
*/
93+
public Map<String, List<String>> queryParams() {
94+
return queryParams;
95+
}
96+
97+
/**
98+
* Sets the response headers.
99+
*
100+
* @param queryParams The response headers.
101+
*/
102+
public HttpBinJSON queryParams(Map<String, List<String>> queryParams) {
103+
this.queryParams = queryParams;
104+
80105
return this;
81106
}
82107

@@ -107,6 +132,8 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
107132
jsonWriter.writeMapField("headers", headers, (headerWriter, headerList) ->
108133
headerWriter.writeArray(headerList, JsonWriter::writeString));
109134
jsonWriter.writeUntypedField("data", data);
135+
jsonWriter.writeMapField("queryParams", queryParams, (paramWriter, paramList) ->
136+
paramWriter.writeArray(paramList, JsonWriter::writeString));
110137

111138
return jsonWriter.writeEndObject();
112139
}
@@ -140,6 +167,10 @@ public static HttpBinJSON fromJson(JsonReader jsonReader) throws IOException {
140167
reader.readMap(headerReader -> headerReader.readArray(JsonReader::getString));
141168
} else if ("data".equalsIgnoreCase(fieldName)) {
142169
httpBinJSON.data = reader.readUntyped();
170+
} else if ("queryParams".equalsIgnoreCase(fieldName)) {
171+
// Pass the JsonReader to another JsonSerializable to read the inner object.
172+
httpBinJSON.queryParams =
173+
reader.readMap(paramReader -> paramReader.readArray(JsonReader::getString));
143174
} else {
144175
reader.skipChildren();
145176
}

0 commit comments

Comments
 (0)