diff --git a/CHANGES.md b/CHANGES.md index 6a030b02..048da240 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,7 @@ ### Version 4.4 * Support overriding default HostnameVerifier * Support GZIP content encoding for request bodies +* Support Iterable args for query parameters ### Version 4.3 * Add ability to configure zero or more RequestInterceptors. diff --git a/core/src/main/java/feign/RequestTemplate.java b/core/src/main/java/feign/RequestTemplate.java index 6fd35bb0..b6df8661 100644 --- a/core/src/main/java/feign/RequestTemplate.java +++ b/core/src/main/java/feign/RequestTemplate.java @@ -84,11 +84,11 @@ public final class RequestTemplate implements Serializable { * just the URL */ public RequestTemplate resolve(Map unencoded) { + replaceQueryValues(unencoded); Map encoded = new LinkedHashMap(); for (Entry entry : unencoded.entrySet()) { encoded.put(entry.getKey(), urlEncode(String.valueOf(entry.getValue()))); } - replaceQueryValues(encoded); String resolvedUrl = expand(url.toString(), encoded).replace("%2F", "/"); url = new StringBuilder(resolvedUrl); @@ -509,8 +509,15 @@ public final class RequestTemplate implements Serializable { if (value.indexOf('{') == 0 && value.indexOf('}') == value.length() - 1) { Object variableValue = unencoded.get(value.substring(1, value.length() - 1)); // only add non-null expressions - if (variableValue != null) { - values.add(String.valueOf(variableValue)); + if (variableValue == null) { + continue; + } + if (variableValue instanceof Iterable) { + for (Object val : Iterable.class.cast(variableValue)) { + values.add(urlEncode(String.valueOf(val))); + } + } else { + values.add(urlEncode(String.valueOf(variableValue))); } } else { values.add(value); diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index 224cb65e..c0dc93e3 100644 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -91,6 +91,8 @@ public class FeignTest { @RequestLine("GET /{1}/{2}") Response uriParam(@Named("1") String one, URI endpoint, @Named("2") String two); + @RequestLine("GET /?1={1}&2={2}") Response queryParams(@Named("1") String one, @Named("2") Iterable twos); + @RequestLine("POST /") Observable observableVoid(); @RequestLine("POST /") Observable observableString(); @@ -114,14 +116,29 @@ public class FeignTest { } }; } + } + } + + @Test + public void iterableQueryParams() throws IOException, InterruptedException { + final MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setBody("foo")); + server.play(); + + try { + TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); + api.queryParams("user", Arrays.asList("apple", "pear")); + assertEquals(server.takeRequest().getRequestLine(), "GET /?1=user&2=apple&2=pear HTTP/1.1"); + } finally { + server.shutdown(); } } @Test public void observableVoid() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -156,7 +173,7 @@ public class FeignTest { @Test public void observableResponse() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -202,7 +219,7 @@ public class FeignTest { @Test public void incrementString() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -237,8 +254,8 @@ public class FeignTest { @Test public void multipleObservers() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -275,7 +292,7 @@ public class FeignTest { @Test public void postTemplateParamsResolve() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -292,7 +309,7 @@ public class FeignTest { @Test public void postFormParams() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -309,7 +326,7 @@ public class FeignTest { @Test public void postBodyParam() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -327,7 +344,7 @@ public class FeignTest { @Test public void postGZIPEncodedBodyParam() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -359,7 +376,7 @@ public class FeignTest { @Test public void singleInterceptor() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -387,7 +404,7 @@ public class FeignTest { @Test public void multipleInterceptor() throws IOException, InterruptedException { final MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("foo")); + server.enqueue(new MockResponse().setBody("foo")); server.play(); try { @@ -445,7 +462,7 @@ public class FeignTest { @Test public void retriesLostConnectionBeforeRead() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -474,7 +491,7 @@ public class FeignTest { public void overrideTypeSpecificDecoder() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -508,8 +525,8 @@ public class FeignTest { */ public void retryableExceptionInDecoder() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("retry!".getBytes())); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("retry!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -538,7 +555,7 @@ public class FeignTest { @Test(expectedExceptions = FeignException.class, expectedExceptionsMessageRegExp = "error reading response POST http://.*") public void doesntRetryAfterResponseIsSent() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -562,7 +579,7 @@ public class FeignTest { @Test public void canOverrideSSLSocketFactory() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); server.useHttps(TrustingSSLSocketFactory.get("localhost"), false); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -584,7 +601,7 @@ public class FeignTest { @Test public void canOverrideHostnameVerifier() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); server.useHttps(TrustingSSLSocketFactory.get("bad.example.com"), false); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { @@ -600,7 +617,7 @@ public class FeignTest { MockWebServer server = new MockWebServer(); server.useHttps(TrustingSSLSocketFactory.get("localhost"), false); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); - server.enqueue(new MockResponse().setResponseCode(200).setBody("success!".getBytes())); + server.enqueue(new MockResponse().setBody("success!".getBytes())); server.play(); try { diff --git a/core/src/test/java/feign/RequestTemplateTest.java b/core/src/test/java/feign/RequestTemplateTest.java index ffc13e9d..2e5cd09d 100644 --- a/core/src/test/java/feign/RequestTemplateTest.java +++ b/core/src/test/java/feign/RequestTemplateTest.java @@ -20,6 +20,8 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import org.testng.annotations.Test; +import java.util.Arrays; + import static feign.RequestTemplate.expand; import static org.testng.Assert.assertEquals; @@ -83,6 +85,20 @@ public class RequestTemplateTest { + "GET https://iam.amazonaws.com/?Action=DescribeRegions&RegionName.1=eu-west-1 HTTP/1.1\n"); } + @Test public void resolveTemplateWithBaseAndParameterizedIterableQuery() { + RequestTemplate template = new RequestTemplate().method("GET") + .append("/?Query=one").query("Queries", "{queries}"); + + template.resolve(ImmutableMap.of("queries", Arrays.asList("us-east-1", "eu-west-1"))); + assertEquals(template.queries(), + ImmutableListMultimap. builder() + .put("Query", "one") + .putAll("Queries", "us-east-1", "eu-west-1") + .build().asMap()); + + assertEquals(template.toString(), "GET /?Query=one&Queries=us-east-1&Queries=eu-west-1 HTTP/1.1\n"); + } + @Test public void resolveTemplateWithMixedRequestLineParams() throws Exception { RequestTemplate template = new RequestTemplate().method("GET")// .append("/domains/{domainId}/records")//