#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.
This commit is contained in:
Benjamin Douglas
2017-03-23 00:01:07 -07:00
committed by wing328
parent bd81dfd08e
commit 55b7db3456
10 changed files with 270 additions and 1 deletions

View File

@@ -36,6 +36,7 @@ public class CodegenOperation {
public Map<String, Object> 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;
}
}

View File

@@ -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;
}

View File

@@ -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<String> getPathTemplateNames() {

View File

@@ -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 <code>{{operationId}}</code> 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
* <p>The following elements may be specified in the query map:</p>
* <ul>
{{#queryParams}}
* <li>{{paramName}} - {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}</li>
{{/queryParams}}
* </ul>
{{#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<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
* <code>{{operationId}}</code> method in a fluent style.
*/
public static class {{operationIdCamelCase}}QueryParams extends HashMap<String, Object> {
{{#queryParams}}
public {{operationIdCamelCase}}QueryParams {{paramName}}(final {{{dataType}}} value) {
put("{{paramName}}", value);
return this;
}
{{/queryParams}}
}
{{/hasQueryParams}}
{{/operation}}
{{/operations}}
}

View File

@@ -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}}
}

View File

@@ -76,4 +76,54 @@ 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("enumQueryStringArray") List<String> 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 <code>testEnumParameters</code> 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
* <p>The following elements may be specified in the query map:</p>
* <ul>
* <li>enumQueryStringArray - Query parameter enum test (string array) (optional)</li>
* <li>enumQueryString - Query parameter enum test (string) (optional, default to -efg)</li>
* <li>enumQueryInteger - Query parameter enum test (double) (optional)</li>
* </ul>
*/
@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<String> enumFormStringArray, @Param("enumFormString") String enumFormString, @Param("enumHeaderStringArray") List<String> enumHeaderStringArray, @Param("enumHeaderString") String enumHeaderString, @Param("enumQueryDouble") Double enumQueryDouble, @QueryMap Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
* <code>testEnumParameters</code> method in a fluent style.
*/
public static class TestEnumParametersQueryParams extends HashMap<String, Object> {
public TestEnumParametersQueryParams enumQueryStringArray(final List<String> 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;
}
}
}

View File

@@ -55,6 +55,39 @@ public interface PetApi extends ApiClient.Api {
})
List<Pet> findPetsByStatus(@Param("status") List<String> status);
/**
* Finds Pets by status
* Multiple status values can be provided with comma separated strings
* Note, this is equivalent to the other <code>findPetsByStatus</code> 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
* <p>The following elements may be specified in the query map:</p>
* <ul>
* <li>status - Status values that need to be considered for filter (required)</li>
* </ul>
* @return List&lt;Pet&gt;
*/
@RequestLine("GET /pet/findByStatus?status={status}")
@Headers({
"Content-Type: application/json",
"Accept: application/json",
})
List<Pet> findPetsByStatus(@QueryMap Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
* <code>findPetsByStatus</code> method in a fluent style.
*/
public static class FindPetsByStatusQueryParams extends HashMap<String, Object> {
public FindPetsByStatusQueryParams status(final List<String> 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<Pet> findPetsByTags(@Param("tags") List<String> 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 <code>findPetsByTags</code> 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
* <p>The following elements may be specified in the query map:</p>
* <ul>
* <li>tags - Tags to filter by (required)</li>
* </ul>
* @return List&lt;Pet&gt;
*/
@RequestLine("GET /pet/findByTags?tags={tags}")
@Headers({
"Content-Type: application/json",
"Accept: application/json",
})
List<Pet> findPetsByTags(@QueryMap Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
* <code>findPetsByTags</code> method in a fluent style.
*/
public static class FindPetsByTagsQueryParams extends HashMap<String, Object> {
public FindPetsByTagsQueryParams tags(final List<String> value) {
put("tags", value);
return this;
}
}
/**
* Find pet by ID
* Returns a single pet

View File

@@ -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 <code>loginUser</code> 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
* <p>The following elements may be specified in the query map:</p>
* <ul>
* <li>username - The user name for login (required)</li>
* <li>password - The password for login in clear text (required)</li>
* </ul>
* @return String
*/
@RequestLine("GET /user/login?username={username}&password={password}")
@Headers({
"Content-Type: application/json",
"Accept: application/json",
})
String loginUser(@QueryMap Map<String, Object> queryParams);
/**
* A convenience class for generating query parameters for the
* <code>loginUser</code> method in a fluent style.
*/
public static class LoginUserQueryParams extends HashMap<String, Object> {
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
*

View File

@@ -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

View File

@@ -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