From 55b7db34563e4b8f601a8d12637200e286687a72 Mon Sep 17 00:00:00 2001 From: Benjamin Douglas Date: Thu, 23 Mar 2017 00:01:07 -0700 Subject: [PATCH] #5142: Add @QueryMap support for Feign API (#5143) * use builder pattern for operations * @QueryMap parameter only for query parameters The previous iteration had replaced all parameters (body, path, query, etc) within a single @QueryMap. But Feign only supports this style of parameter passing for query parameters. Besides, for the case of a body parameter (like soxhlet uses) it only added extra verbosity. With this change, the query parameters are gathered together in a single @QueryMap and the other parameters are left alone. * Adding template for generating test code * Make javadoc consistent with rest of file's conventions/indents * Update samples The files in src/main were generated by running $ bin/java-petstore-feign.sh The files in src/test were manually fixed. * Correct capitalization of @QueryMap class in feign Adds a field operationIdCamelCase (a la operationIdLowerCase) to the CodegenOperation container and uses it in the feign-generated classes with @QueryMap parameters. Also re-generated the feign samples. * Adding hyphen to javadocs for extra readability. * Adding (not replacing) api method with @QueryParam overload. In order to keep backwards compatibility, switched to adding a new method to the interface instead of replacing the old call. * Adding newline to generated source for readability. --- .../io/swagger/codegen/CodegenOperation.java | 6 +- .../io/swagger/codegen/DefaultCodegen.java | 1 + .../languages/ElixirClientCodegen.java | 1 + .../Java/libraries/feign/api.mustache | 48 ++++++++++++++ .../Java/libraries/feign/api_test.mustache | 26 ++++++++ .../java/io/swagger/client/api/FakeApi.java | 50 ++++++++++++++ .../java/io/swagger/client/api/PetApi.java | 66 +++++++++++++++++++ .../java/io/swagger/client/api/UserApi.java | 38 +++++++++++ .../io/swagger/client/api/PetApiTest.java | 29 ++++++++ .../io/swagger/client/api/UserApiTest.java | 6 ++ 10 files changed, 270 insertions(+), 1 deletion(-) diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenOperation.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenOperation.java index fc07e0d7174b..c72a844e056f 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenOperation.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/CodegenOperation.java @@ -36,6 +36,7 @@ public class CodegenOperation { public Map vendorExtensions; public String nickname; // legacy support public String operationIdLowerCase; // for mardown documentation + public String operationIdCamelCase; // for class names /** * Check if there's at least one parameter @@ -279,7 +280,9 @@ public class CodegenOperation { return false; if ( prioritizedContentTypes != null ? !prioritizedContentTypes.equals(that.prioritizedContentTypes) : that.prioritizedContentTypes != null ) return false; - return operationIdLowerCase != null ? operationIdLowerCase.equals(that.operationIdLowerCase) : that.operationIdLowerCase == null; + if ( operationIdLowerCase != null ? !operationIdLowerCase.equals(that.operationIdLowerCase) : that.operationIdLowerCase != null ) + return false; + return operationIdCamelCase != null ? operationIdCamelCase.equals(that.operationIdCamelCase) : that.operationIdCamelCase == null; } @@ -332,6 +335,7 @@ public class CodegenOperation { result = 31 * result + (nickname != null ? nickname.hashCode() : 0); result = 31 * result + (prioritizedContentTypes != null ? prioritizedContentTypes.hashCode() : 0); result = 31 * result + (operationIdLowerCase != null ? operationIdLowerCase.hashCode() : 0); + result = 31 * result + (operationIdCamelCase != null ? operationIdCamelCase.hashCode() : 0); return result; } } diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java index 8e9449bdfde1..e684a94e3752 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/DefaultCodegen.java @@ -2827,6 +2827,7 @@ public class DefaultCodegen { } co.operationId = uniqueName; co.operationIdLowerCase = uniqueName.toLowerCase(); + co.operationIdCamelCase = DefaultCodegen.camelize(uniqueName); opList.add(co); co.baseName = tag; } diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ElixirClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ElixirClientCodegen.java index 1a611e4d5909..21a237dd1b6b 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ElixirClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/ElixirClientCodegen.java @@ -356,6 +356,7 @@ public class ElixirClientCodegen extends DefaultCodegen implements CodegenConfig this.vendorExtensions = o.vendorExtensions; this.nickname = o.nickname; this.operationIdLowerCase = o.operationIdLowerCase; + this.operationIdCamelCase = o.operationIdCamelCase; } public List getPathTemplateNames() { 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 ce2c205ea9d9..df26e39fc55e 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 @@ -38,6 +38,54 @@ public interface {{classname}} extends ApiClient.Api { {{/hasMore}}{{/headerParams}} }) {{#returnType}}{{{returnType}}} {{/returnType}}{{^returnType}}void {{/returnType}}{{nickname}}({{#allParams}}{{^isBodyParam}}{{^legacyDates}}@Param("{{paramName}}") {{/legacyDates}}{{#legacyDates}}@Param(value="{{paramName}}", expander=ParamExpander.class) {{/legacyDates}}{{/isBodyParam}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); + {{#hasQueryParams}} + + /** + * {{summary}} + * {{notes}} + * Note, this is equivalent to the other {{operationId}} method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link {{operationIdCamelCase}}QueryParams} class that allows for + * building up this map in a fluent style. + {{#allParams}} + {{^isQueryParam}} + * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/isQueryParam}} + {{/allParams}} + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + {{#queryParams}} + *
  • {{paramName}} - {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
  • + {{/queryParams}} + *
+ {{#returnType}} + * @return {{returnType}} + {{/returnType}} + */ + @RequestLine("{{httpMethod}} {{{path}}}?{{#queryParams}}{{baseName}}={{=<% %>=}}{<%paramName%>}<%={{ }}=%>{{#hasMore}}&{{/hasMore}}{{/queryParams}}") + @Headers({ + "Content-Type: {{vendorExtensions.x-contentType}}", + "Accept: {{vendorExtensions.x-accepts}}",{{#headerParams}} + "{{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); + + /** + * A convenience class for generating query parameters for the + * {{operationId}} method in a fluent style. + */ + public static class {{operationIdCamelCase}}QueryParams extends HashMap { + {{#queryParams}} + public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) { + put("{{paramName}}", value); + return this; + } + {{/queryParams}} + } + {{/hasQueryParams}} {{/operation}} {{/operations}} } diff --git a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api_test.mustache b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api_test.mustache index 303fda344e99..bcc14a987c68 100644 --- a/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api_test.mustache +++ b/modules/swagger-codegen/src/main/resources/Java/libraries/feign/api_test.mustache @@ -40,5 +40,31 @@ public class {{classname}}Test { // TODO: test validations } + + {{#hasQueryParams}} + /** + * {{summary}} + * + * {{notes}} + * + * This tests the overload of the method that uses a Map for query parameters instead of + * listing them out individually. + */ + @Test + public void {{operationId}}TestQueryMap() { + {{#allParams}} + {{^isQueryParam}} + {{{dataType}}} {{paramName}} = null; + {{/isQueryParam}} + {{/allParams}} + {{classname}}.{{operationIdCamelCase}}QueryParams queryParams = new {{classname}}.{{operationIdCamelCase}}QueryParams() + {{#queryParams}} + .{{paramName}}(null){{^hasMore}};{{/hasMore}} + {{/queryParams}} + // {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{^isQueryParam}}{{paramName}}, {{/isQueryParam}}{{/allParams}}queryParams); + + // TODO: test validations + } + {{/hasQueryParams}} {{/operation}}{{/operations}} } 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 0b30eeb442c2..0e634f7bc6e6 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 @@ -76,4 +76,54 @@ 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("enumQueryStringArray") List enumQueryStringArray, @Param("enumQueryString") String enumQueryString, @Param("enumQueryInteger") Integer enumQueryInteger, @Param("enumQueryDouble") Double enumQueryDouble); + + /** + * To test enum parameters + * To test enum parameters + * Note, this is equivalent to the other testEnumParameters method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link TestEnumParametersQueryParams} class that allows for + * building up this map in a fluent style. + * @param enumFormStringArray Form parameter enum test (string array) (optional) + * @param enumFormString Form parameter enum test (string) (optional, default to -efg) + * @param enumHeaderStringArray Header parameter enum test (string array) (optional) + * @param enumHeaderString Header parameter enum test (string) (optional, default to -efg) + * @param enumQueryDouble Query parameter enum test (double) (optional) + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + *
  • enumQueryStringArray - Query parameter enum test (string array) (optional)
  • + *
  • enumQueryString - Query parameter enum test (string) (optional, default to -efg)
  • + *
  • enumQueryInteger - Query parameter enum test (double) (optional)
  • + *
+ */ + @RequestLine("GET /fake?enum_query_string_array={enumQueryStringArray}&enum_query_string={enumQueryString}&enum_query_integer={enumQueryInteger}") + @Headers({ + "Content-Type: */*", + "Accept: */*", + "enum_header_string_array: {enumHeaderStringArray}", + + "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); + + /** + * A convenience class for generating query parameters for the + * testEnumParameters method in a fluent style. + */ + public static class TestEnumParametersQueryParams extends HashMap { + public TestEnumParametersQueryParams enumQueryStringArray(final List value) { + put("enumQueryStringArray", value); + return this; + } + public TestEnumParametersQueryParams enumQueryString(final String value) { + put("enumQueryString", value); + return this; + } + public TestEnumParametersQueryParams enumQueryInteger(final Integer value) { + put("enumQueryInteger", 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 05133620f821..48987c67f380 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 @@ -55,6 +55,39 @@ public interface PetApi extends ApiClient.Api { }) List findPetsByStatus(@Param("status") List status); + /** + * Finds Pets by status + * Multiple status values can be provided with comma separated strings + * Note, this is equivalent to the other findPetsByStatus method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link FindPetsByStatusQueryParams} class that allows for + * building up this map in a fluent style. + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + *
  • status - Status values that need to be considered for filter (required)
  • + *
+ * @return List<Pet> + */ + @RequestLine("GET /pet/findByStatus?status={status}") + @Headers({ + "Content-Type: application/json", + "Accept: application/json", + }) + List findPetsByStatus(@QueryMap Map queryParams); + + /** + * A convenience class for generating query parameters for the + * findPetsByStatus method in a fluent style. + */ + public static class FindPetsByStatusQueryParams extends HashMap { + public FindPetsByStatusQueryParams status(final List value) { + put("status", value); + return this; + } + } + /** * Finds Pets by tags * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. @@ -68,6 +101,39 @@ public interface PetApi extends ApiClient.Api { }) List findPetsByTags(@Param("tags") List tags); + /** + * Finds Pets by tags + * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + * Note, this is equivalent to the other findPetsByTags method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link FindPetsByTagsQueryParams} class that allows for + * building up this map in a fluent style. + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + *
  • tags - Tags to filter by (required)
  • + *
+ * @return List<Pet> + */ + @RequestLine("GET /pet/findByTags?tags={tags}") + @Headers({ + "Content-Type: application/json", + "Accept: application/json", + }) + List findPetsByTags(@QueryMap Map queryParams); + + /** + * A convenience class for generating query parameters for the + * findPetsByTags method in a fluent style. + */ + public static class FindPetsByTagsQueryParams extends HashMap { + public FindPetsByTagsQueryParams tags(final List value) { + put("tags", value); + return this; + } + } + /** * Find pet by ID * Returns a single pet 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 ae2ea06f9b60..c271b9deda80 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 @@ -89,6 +89,44 @@ public interface UserApi extends ApiClient.Api { }) String loginUser(@Param("username") String username, @Param("password") String password); + /** + * Logs user into the system + * + * Note, this is equivalent to the other loginUser method, + * but with the query parameters collected into a single Map parameter. This + * is convenient for services with optional query parameters, especially when + * used with the {@link LoginUserQueryParams} class that allows for + * building up this map in a fluent style. + * @param queryParams Map of query parameters as name-value pairs + *

The following elements may be specified in the query map:

+ *
    + *
  • username - The user name for login (required)
  • + *
  • password - The password for login in clear text (required)
  • + *
+ * @return String + */ + @RequestLine("GET /user/login?username={username}&password={password}") + @Headers({ + "Content-Type: application/json", + "Accept: application/json", + }) + String loginUser(@QueryMap Map queryParams); + + /** + * A convenience class for generating query parameters for the + * loginUser method in a fluent style. + */ + public static class LoginUserQueryParams extends HashMap { + public LoginUserQueryParams username(final String value) { + put("username", value); + return this; + } + public LoginUserQueryParams password(final String value) { + put("password", value); + return this; + } + } + /** * Logs out current logged in user session * 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 fd649e0f9f47..19e44086a51e 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 @@ -83,6 +83,21 @@ public class PetApiTest { } assertTrue(found); + + PetApi.FindPetsByStatusQueryParams queryParams = new PetApi.FindPetsByStatusQueryParams() + .status(Arrays.asList(new String[]{"available"})); + pets = api.findPetsByStatus(queryParams); + assertNotNull(pets); + + found = false; + for (Pet fetched : pets) { + if (fetched.getId().equals(pet.getId())) { + found = true; + break; + } + } + + assertTrue(found); } @Test @@ -110,6 +125,20 @@ public class PetApiTest { } } assertTrue(found); + + PetApi.FindPetsByTagsQueryParams queryParams = new PetApi.FindPetsByTagsQueryParams() + .tags(Arrays.asList(new String[]{"friendly"})); + pets = api.findPetsByTags(queryParams); + assertNotNull(pets); + + found = false; + for (Pet fetched : pets) { + if (fetched.getId().equals(pet.getId())) { + found = true; + break; + } + } + assertTrue(found); } @Test diff --git a/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/UserApiTest.java b/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/UserApiTest.java index 513c84516ade..be7535306706 100644 --- a/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/UserApiTest.java +++ b/samples/client/petstore/java/feign/src/test/java/io/swagger/client/api/UserApiTest.java @@ -66,6 +66,12 @@ public class UserApiTest { String token = api.loginUser(user.getUsername(), user.getPassword()); assertTrue(token.startsWith("logged in user session:")); + + UserApi.LoginUserQueryParams queryParams = new UserApi.LoginUserQueryParams() + .username(user.getUsername()) + .password(user.getPassword()); + token = api.loginUser(queryParams); + assertTrue(token.startsWith("logged in user session:")); } @Test