diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java index 8c6236ba038..326b13a41b3 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/JavaClientCodegen.java @@ -176,6 +176,7 @@ public class JavaClientCodegen extends AbstractJavaCodegen if ("feign".equals(getLibrary())) { additionalProperties.put("jackson", "true"); supportingFiles.add(new SupportingFile("ParamExpander.mustache", invokerFolder, "ParamExpander.java")); + supportingFiles.add(new SupportingFile("EncodingUtils.mustache", invokerFolder, "EncodingUtils.java")); } else if ("okhttp-gson".equals(getLibrary()) || StringUtils.isEmpty(getLibrary())) { // the "okhttp-gson" library template requires "ApiCallback.mustache" for async call supportingFiles.add(new SupportingFile("ApiCallback.mustache", invokerFolder, "ApiCallback.java")); diff --git a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/EncodingUtils.mustache b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/EncodingUtils.mustache new file mode 100644 index 00000000000..6f43e1c3c2a --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/EncodingUtils.mustache @@ -0,0 +1,86 @@ +package {{invokerPackage}}; + +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() {} + + /** + *

Encodes a collection of query parameters according to the Swagger + * collection format.

+ * + *

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.

+ * + *

Note, as part of reformatting, it URL encodes the parameters as + * well.

+ * @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 + * + * Swagger Spec 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 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); + } + } +} diff --git a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api.mustache b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api.mustache index 6aadf2305d1..67a772a8772 100644 --- a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api.mustache @@ -1,6 +1,7 @@ package {{package}}; import {{invokerPackage}}.ApiClient; +import {{invokerPackage}}.EncodingUtils; {{#legacyDates}} import {{invokerPackage}}.ParamExpander; {{/legacyDates}} @@ -71,7 +72,7 @@ public interface {{classname}} extends ApiClient.Api { "{{baseName}}: {{=<% %>=}}{<%paramName%>}<%={{ }}=%>"{{#hasMore}}, {{/hasMore}}{{/headerParams}} }) - {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap Map queryParams); + {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isQueryParam}}{{^isBodyParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isBodyParam}}{{{dataType}}} {{paramName}}, {{/isQueryParam}}{{/allParams}}@QueryMap(encoded=true) Map queryParams); /** * A convenience class for generating query parameters for the @@ -80,7 +81,12 @@ public interface {{classname}} extends ApiClient.Api { public static class {{operationIdCamelCase}}QueryParams extends HashMap { {{#queryParams}} public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) { - put("{{baseName}}", value); + {{#collectionFormat}} + put("{{baseName}}", EncodingUtils.encodeCollection(value, "{{collectionFormat}}")); + {{/collectionFormat}} + {{^collectionFormat}} + put("{{baseName}}", EncodingUtils.encode(value)); + {{/collectionFormat}} return this; } {{/queryParams}} diff --git a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/pom.mustache b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/pom.mustache index d823f258557..c7d253ad685 100644 --- a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/pom.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/pom.mustache @@ -246,6 +246,18 @@ ${junit-version} test + + com.squareup.okhttp3 + mockwebserver + 3.6.0 + test + + + org.assertj + assertj-core + 1.7.1 + test + {{#java8}}1.8{{/java8}}{{^java8}}1.7{{/java8}} diff --git a/samples/client/petstore/java/feign/pom.xml b/samples/client/petstore/java/feign/pom.xml index 73a08a6c53c..d21f1775565 100644 --- a/samples/client/petstore/java/feign/pom.xml +++ b/samples/client/petstore/java/feign/pom.xml @@ -230,6 +230,18 @@ ${junit-version} test + + com.squareup.okhttp3 + mockwebserver + 3.6.0 + test + + + org.assertj + assertj-core + 1.7.1 + test + 1.7 diff --git a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/EncodingUtils.java b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/EncodingUtils.java new file mode 100644 index 00000000000..c474fc62187 --- /dev/null +++ b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/EncodingUtils.java @@ -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() {} + + /** + *

Encodes a collection of query parameters according to the Swagger + * collection format.

+ * + *

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.

+ * + *

Note, as part of reformatting, it URL encodes the parameters as + * well.

+ * @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 + * + * Swagger Spec 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 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); + } + } +} diff --git a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/FakeApi.java b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/FakeApi.java index 1c035b3fe38..04b4b05e6bc 100644 --- a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/FakeApi.java +++ b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/FakeApi.java @@ -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 enumFormStringArray, @Param("enumFormString") String enumFormString, @Param("enumHeaderStringArray") List enumHeaderStringArray, @Param("enumHeaderString") String enumHeaderString, @Param("enumQueryDouble") Double enumQueryDouble, @QueryMap Map queryParams); + void testEnumParameters(@Param("enumFormStringArray") List enumFormStringArray, @Param("enumFormString") String enumFormString, @Param("enumHeaderStringArray") List enumHeaderStringArray, @Param("enumHeaderString") String enumHeaderString, @Param("enumQueryDouble") Double enumQueryDouble, @QueryMap(encoded=true) Map 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 { public TestEnumParametersQueryParams enumQueryStringArray(final List 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; } } diff --git a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/PetApi.java b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/PetApi.java index 48987c67f38..5a6813c6198 100644 --- a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/PetApi.java +++ b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/PetApi.java @@ -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 findPetsByStatus(@QueryMap Map queryParams); + List findPetsByStatus(@QueryMap(encoded=true) Map 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 { public FindPetsByStatusQueryParams status(final List 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 findPetsByTags(@QueryMap Map queryParams); + List findPetsByTags(@QueryMap(encoded=true) Map 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 { public FindPetsByTagsQueryParams tags(final List value) { - put("tags", value); + put("tags", EncodingUtils.encodeCollection(value, "csv")); return this; } } diff --git a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/StoreApi.java b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/StoreApi.java index 9e1ecddbece..1ad8111d3d6 100644 --- a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/StoreApi.java +++ b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/StoreApi.java @@ -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; diff --git a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/UserApi.java b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/UserApi.java index c271b9deda8..e85fca41f8b 100644 --- a/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/UserApi.java +++ b/samples/client/petstore/java/feign/src/main/java/io/swagger/client/api/UserApi.java @@ -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 queryParams); + String loginUser(@QueryMap(encoded=true) Map 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 { 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; } } diff --git a/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/PetApiTest.java b/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/PetApiTest.java index 4a65cc4d176..c27524de1f6 100644 --- a/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/PetApiTest.java +++ b/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/PetApiTest.java @@ -13,17 +13,25 @@ import java.util.Arrays; import java.util.Collections; 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 { - private ApiClient apiClient; - private PetApi api; + 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 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());