Support Swagger collectionFormat encodings in Feign (#5266)

* Support Swagger collectionFormat encodings in Feign

Feign only natively supports the "multi" collectionFormat for encoding lists
of parameter values. This change adds manual encoding of the other formats, such
as "csv" (the default for collections), "tsv", space-separated, and pipes.

* Fix typo in anchor tag.
This commit is contained in:
Benjamin Douglas
2017-04-01 00:33:20 -07:00
committed by wing328
parent 909f392745
commit 5c3fe23e9f
11 changed files with 242 additions and 13 deletions

View File

@@ -0,0 +1,86 @@
package io.swagger.client;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Utilities to support Swagger encoding formats in Feign.
*/
public final class EncodingUtils {
/**
* Private constructor. Do not construct this class.
*/
private EncodingUtils() {}
/**
* <p>Encodes a collection of query parameters according to the Swagger
* collection format.</p>
*
* <p>Of the various collection formats defined by Swagger ("csv", "tsv",
* etc), Feign only natively supports "multi". This utility generates the
* other format types so it will be properly processed by Feign.</p>
*
* <p>Note, as part of reformatting, it URL encodes the parameters as
* well.</p>
* @param parameters The collection object to be formatted. This object will
* not be changed.
* @param collectionFormat The Swagger collection format (eg, "csv", "tsv",
* "pipes"). See the
* <a href="http://swagger.io/specification/#parameter-object-44">
* Swagger Spec</a> for more details.
* @return An object that will be correctly formatted by Feign.
*/
public static Object encodeCollection(Collection<?> parameters,
String collectionFormat) {
if (parameters == null) {
return parameters;
}
List<String> stringValues = new ArrayList<>(parameters.size());
for (Object parameter : parameters) {
// ignore null values (same behavior as Feign)
if (parameter != null) {
stringValues.add(encode(parameter));
}
}
// Feign natively handles single-element lists and the "multi" format.
if (stringValues.size() < 2 || "multi".equals(collectionFormat)) {
return stringValues;
}
// Otherwise return a formatted String
String[] stringArray = stringValues.toArray(new String[0]);
switch (collectionFormat) {
case "csv":
default:
return StringUtil.join(stringArray, ",");
case "ssv":
return StringUtil.join(stringArray, " ");
case "tsv":
return StringUtil.join(stringArray, "\t");
case "pipes":
return StringUtil.join(stringArray, "|");
}
}
/**
* URL encode a single query parameter.
* @param parameter The query parameter to encode. This object will not be
* changed.
* @return The URL encoded string representation of the parameter. If the
* parameter is null, returns null.
*/
public static String encode(Object parameter) {
if (parameter == null) {
return null;
}
try {
return URLEncoder.encode(parameter.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should never happen, UTF-8 is always supported
throw new RuntimeException(e);
}
}
}

View File

@@ -1,6 +1,7 @@
package io.swagger.client.api;
import io.swagger.client.ApiClient;
import io.swagger.client.EncodingUtils;
import java.math.BigDecimal;
import io.swagger.client.model.Client;
@@ -106,7 +107,7 @@ public interface FakeApi extends ApiClient.Api {
"enum_header_string: {enumHeaderString}"
})
void testEnumParameters(@Param("enumFormStringArray") List<String> enumFormStringArray, @Param("enumFormString") String enumFormString, @Param("enumHeaderStringArray") List<String> enumHeaderStringArray, @Param("enumHeaderString") String enumHeaderString, @Param("enumQueryDouble") Double enumQueryDouble, @QueryMap Map<String, Object> queryParams);
void testEnumParameters(@Param("enumFormStringArray") List<String> enumFormStringArray, @Param("enumFormString") String enumFormString, @Param("enumHeaderStringArray") List<String> enumHeaderStringArray, @Param("enumHeaderString") String enumHeaderString, @Param("enumQueryDouble") Double enumQueryDouble, @QueryMap(encoded=true) Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
@@ -114,15 +115,15 @@ public interface FakeApi extends ApiClient.Api {
*/
public static class TestEnumParametersQueryParams extends HashMap<String, Object> {
public TestEnumParametersQueryParams enumQueryStringArray(final List<String> value) {
put("enum_query_string_array", value);
put("enum_query_string_array", EncodingUtils.encodeCollection(value, "csv"));
return this;
}
public TestEnumParametersQueryParams enumQueryString(final String value) {
put("enum_query_string", value);
put("enum_query_string", EncodingUtils.encode(value));
return this;
}
public TestEnumParametersQueryParams enumQueryInteger(final Integer value) {
put("enum_query_integer", value);
put("enum_query_integer", EncodingUtils.encode(value));
return this;
}
}

View File

@@ -1,6 +1,7 @@
package io.swagger.client.api;
import io.swagger.client.ApiClient;
import io.swagger.client.EncodingUtils;
import java.io.File;
import io.swagger.client.model.ModelApiResponse;
@@ -75,7 +76,7 @@ public interface PetApi extends ApiClient.Api {
"Content-Type: application/json",
"Accept: application/json",
})
List<Pet> findPetsByStatus(@QueryMap Map<String, Object> queryParams);
List<Pet> findPetsByStatus(@QueryMap(encoded=true) Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
@@ -83,7 +84,7 @@ public interface PetApi extends ApiClient.Api {
*/
public static class FindPetsByStatusQueryParams extends HashMap<String, Object> {
public FindPetsByStatusQueryParams status(final List<String> value) {
put("status", value);
put("status", EncodingUtils.encodeCollection(value, "csv"));
return this;
}
}
@@ -121,7 +122,7 @@ public interface PetApi extends ApiClient.Api {
"Content-Type: application/json",
"Accept: application/json",
})
List<Pet> findPetsByTags(@QueryMap Map<String, Object> queryParams);
List<Pet> findPetsByTags(@QueryMap(encoded=true) Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
@@ -129,7 +130,7 @@ public interface PetApi extends ApiClient.Api {
*/
public static class FindPetsByTagsQueryParams extends HashMap<String, Object> {
public FindPetsByTagsQueryParams tags(final List<String> value) {
put("tags", value);
put("tags", EncodingUtils.encodeCollection(value, "csv"));
return this;
}
}

View File

@@ -1,6 +1,7 @@
package io.swagger.client.api;
import io.swagger.client.ApiClient;
import io.swagger.client.EncodingUtils;
import io.swagger.client.model.Order;

View File

@@ -1,6 +1,7 @@
package io.swagger.client.api;
import io.swagger.client.ApiClient;
import io.swagger.client.EncodingUtils;
import io.swagger.client.model.User;
@@ -110,7 +111,7 @@ public interface UserApi extends ApiClient.Api {
"Content-Type: application/json",
"Accept: application/json",
})
String loginUser(@QueryMap Map<String, Object> queryParams);
String loginUser(@QueryMap(encoded=true) Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
@@ -118,11 +119,11 @@ public interface UserApi extends ApiClient.Api {
*/
public static class LoginUserQueryParams extends HashMap<String, Object> {
public LoginUserQueryParams username(final String value) {
put("username", value);
put("username", EncodingUtils.encode(value));
return this;
}
public LoginUserQueryParams password(final String value) {
put("password", value);
put("password", EncodingUtils.encode(value));
return this;
}
}

View File

@@ -13,17 +13,25 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
public class PetApiTest {
ApiClient apiClient;
PetApi api;
MockWebServer localServer;
ApiClient localClient;
@Before
public void setup() {
apiClient = new ApiClient();
api = apiClient.buildClient(PetApi.class);
localServer = new MockWebServer();
localClient = new ApiClient();
}
@Test
@@ -211,6 +219,20 @@ public class PetApiTest {
assertTrue(pet1.hashCode() == pet1.hashCode());
}
@Test
public void testCSVDelimitedArray() throws Exception {
localServer.enqueue(new MockResponse().setBody("[{\"id\":5,\"name\":\"rocky\"}]"));
localServer.start();
PetApi api = localClient.setBasePath(localServer.url("/").toString()).buildClient(PetApi.class);
PetApi.FindPetsByTagsQueryParams queryParams = new PetApi.FindPetsByTagsQueryParams()
.tags(Arrays.asList("friendly","energetic"));
List<Pet> pets = api.findPetsByTags(queryParams);
assertNotNull(pets);
RecordedRequest request = localServer.takeRequest();
assertThat(request.getPath()).contains("tags=friendly,energetic");
localServer.shutdown();
}
private Pet createRandomPet() {
Pet pet = new Pet();
pet.setId(TestUtils.nextId());