[JAVA] [WebClient] Handle list of String special case in WebClient generator (#16326)

* [Java][WebClient] support string list return type

* [Java][WebClient] add test

* [Java][WebClient] support string list return type

* [Java][WebClient] support string list return type

* fix webclient auto-generated test files

---------

Co-authored-by: Gavin.Wu <gavin.guohao.wu@kingland.com>
This commit is contained in:
William Cheng 2023-08-15 13:53:04 +08:00 committed by GitHub
parent 76bb8a40d8
commit 4f6a25f4b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 8 deletions

View File

@ -10,6 +10,7 @@ public enum VendorExtension {
X_DISCRIMINATOR_VALUE("x-discriminator-value", ExtensionLevel.MODEL, "Used with model inheritance to specify value for discriminator that identifies current model", ""),
X_SETTER_EXTRA_ANNOTATION("x-setter-extra-annotation", ExtensionLevel.FIELD, "Custom annotation that can be specified over java setter for specific field", "When field is array & uniqueItems, then this extension is used to add `@JsonDeserialize(as = LinkedHashSet.class)` over setter, otherwise no value"),
X_WEBCLIENT_BLOCKING("x-webclient-blocking", ExtensionLevel.OPERATION, "Specifies if method for specific operation should be blocking or non-blocking(ex: return `Mono<T>/Flux<T>` or `return T/List<T>/Set<T>` & execute `.block()` inside generated method)", "false"),
X_WEBCLIENT_RETURN_EXCEPT_LIST_OF_STRING("x-webclient-return-except-list-of-string", ExtensionLevel.OPERATION, "Specifies if method for specific operation should return the type except List<String> and Set<String>(ex: return type expect the `Mono<List<String>>/Flux<List<String>>` and `Mono<Set<String>>/Flux<Set<String>>`)", "false"),
X_TAGS("x-tags", ExtensionLevel.OPERATION, "Specify multiple swagger tags for operation", null),
X_ACCEPTS("x-accepts", ExtensionLevel.OPERATION, "Specify custom value for 'Accept' header for operation", null),
X_CONTENT_TYPE("x-content-type", ExtensionLevel.OPERATION, "Specify custom value for 'Content-Type' header for operation", null),

View File

@ -885,6 +885,10 @@ public class JavaClientCodegen extends AbstractJavaCodegen
if (!operation.vendorExtensions.containsKey(VendorExtension.X_WEBCLIENT_BLOCKING.getName()) && webclientBlockingOperations) {
operation.vendorExtensions.put(VendorExtension.X_WEBCLIENT_BLOCKING.getName(), true);
}
if (operation.isArray && !"string".equalsIgnoreCase(operation.returnBaseType)) {
operation.vendorExtensions.put(VendorExtension.X_WEBCLIENT_RETURN_EXCEPT_LIST_OF_STRING.getName(), true);
}
}
}
}

View File

@ -129,7 +129,7 @@ public class {{classname}} {
String[] localVarAuthNames = new String[] { {{#authMethods}}"{{name}}"{{^-last}}, {{/-last}}{{/authMethods}} };
{{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
{{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
return apiClient.invokeAPI("{{{path}}}", HttpMethod.{{httpMethod}}, pathParams, queryParams, postBody, headerParams, cookieParams, formParams, localVarAccept, localVarContentType, localVarAuthNames, localVarReturnType);
}
@ -145,9 +145,9 @@ public class {{classname}} {
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
*/
public {{#returnType}}{{#vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{returnBaseType}}}>{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#isArray}}Flux<{{{returnBaseType}}}>{{/isArray}}{{^isArray}}Mono<{{{returnType}}}>{{/isArray}}{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{^returnType}}{{#vendorExtensions.x-webclient-blocking}}void{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}Mono<Void>{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{operationId}}({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<org.springframework.core.io.AbstractResource>{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException {
{{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
{{^returnType}}{{^vendorExtensions.x-webclient-blocking}}return {{/vendorExtensions.x-webclient-blocking}}{{/returnType}}{{#returnType}}return {{/returnType}}{{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#isArray}}bodyToFlux{{/isArray}}{{^isArray}}bodyToMono{{/isArray}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}{{/isArray}}.block(){{/vendorExtensions.x-webclient-blocking}};
public {{#returnType}}{{#vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}Set{{/uniqueItems}}{{^uniqueItems}}List{{/uniqueItems}}<{{{returnBaseType}}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Flux<{{{returnBaseType}}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono<{{{returnType}}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{^returnType}}{{#vendorExtensions.x-webclient-blocking}}void{{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}Mono<Void>{{/vendorExtensions.x-webclient-blocking}} {{/returnType}}{{operationId}}({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<org.springframework.core.io.AbstractResource>{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException {
{{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
{{^returnType}}{{^vendorExtensions.x-webclient-blocking}}return {{/vendorExtensions.x-webclient-blocking}}{{/returnType}}{{#returnType}}return {{/returnType}}{{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#vendorExtensions.x-webclient-return-except-list-of-string}}bodyToFlux{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}bodyToMono{{/vendorExtensions.x-webclient-return-except-list-of-string}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}.block(){{/vendorExtensions.x-webclient-blocking}};
}
/**
@ -162,9 +162,9 @@ public class {{classname}} {
* @see <a href="{{url}}">{{summary}} Documentation</a>
{{/externalDocs}}
*/
public {{#vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#isArray}}ResponseEntity<List<{{{returnBaseType}}}>>{{/isArray}}{{^isArray}}ResponseEntity<{{{returnType}}}>{{/isArray}}{{/returnType}}{{^returnType}}ResponseEntity<Void>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#isArray}}Mono<ResponseEntity<List<{{{returnBaseType}}}>>>{{/isArray}}{{^isArray}}Mono<ResponseEntity<{{{returnType}}}>>{{/isArray}}{{/returnType}}{{^returnType}}Mono<ResponseEntity<Void>>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{operationId}}WithHttpInfo({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<org.springframework.core.io.AbstractResource>{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException {
{{#returnType}}ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}> localVarReturnType = new ParameterizedTypeReference<{{#isArray}}{{{returnBaseType}}}{{/isArray}}{{^isArray}}{{{returnType}}}{{/isArray}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#isArray}}toEntityList{{/isArray}}{{^isArray}}toEntity{{/isArray}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}.block(){{/vendorExtensions.x-webclient-blocking}};
public {{#vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity<List<{{{returnBaseType}}}>>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}ResponseEntity<{{{returnType}}}>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}ResponseEntity<Void>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{^vendorExtensions.x-webclient-blocking}}{{#returnType}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}Mono<ResponseEntity<List<{{{returnBaseType}}}>>>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}Mono<ResponseEntity<{{{returnType}}}>>{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/returnType}}{{^returnType}}Mono<ResponseEntity<Void>>{{/returnType}} {{/vendorExtensions.x-webclient-blocking}}{{operationId}}WithHttpInfo({{#allParams}}{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<org.springframework.core.io.AbstractResource>{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) throws WebClientResponseException {
{{#returnType}}ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}> localVarReturnType = new ParameterizedTypeReference<{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnBaseType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}{{{returnType}}}{{/vendorExtensions.x-webclient-return-except-list-of-string}}>() {};{{/returnType}}{{^returnType}}ParameterizedTypeReference<Void> localVarReturnType = new ParameterizedTypeReference<Void>() {};{{/returnType}}
return {{operationId}}RequestCreation({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).{{#vendorExtensions.x-webclient-return-except-list-of-string}}toEntityList{{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}toEntity{{/vendorExtensions.x-webclient-return-except-list-of-string}}(localVarReturnType){{#vendorExtensions.x-webclient-blocking}}.block(){{/vendorExtensions.x-webclient-blocking}};
}
/**

View File

@ -32,7 +32,7 @@ public class {{classname}}Test {
{{#allParams}}
{{#isFile}}{{#useAbstractionForFiles}}{{#collectionFormat}}java.util.Collection<org.springframework.core.io.AbstractResource>{{/collectionFormat}}{{^collectionFormat}}org.springframework.core.io.AbstractResource{{/collectionFormat}}{{/useAbstractionForFiles}}{{^useAbstractionForFiles}}{{{dataType}}}{{/useAbstractionForFiles}}{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}} {{paramName}} = null;
{{/allParams}}
{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{^vendorExtensions.x-webclient-blocking}}{{#isArray}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}.block(){{/isArray}}{{^isArray}}.block(){{/isArray}}{{/vendorExtensions.x-webclient-blocking}};
{{#returnType}}{{{.}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{^vendorExtensions.x-webclient-blocking}}{{#vendorExtensions.x-webclient-return-except-list-of-string}}{{#uniqueItems}}.collect(Collectors.toSet()){{/uniqueItems}}{{^uniqueItems}}.collectList(){{/uniqueItems}}.block(){{/vendorExtensions.x-webclient-return-except-list-of-string}}{{^vendorExtensions.x-webclient-return-except-list-of-string}}.block(){{/vendorExtensions.x-webclient-return-except-list-of-string}}{{/vendorExtensions.x-webclient-blocking}};
// TODO: test validations
}

View File

@ -2277,4 +2277,38 @@ public class JavaClientCodegenTest {
// shouldn't throw stackoverflow exception
}
@Test
public void testWebClientSupportListOfStringReturnType_issue7118() throws IOException {
Map<String, Object> properties = new HashMap<>();
properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
properties.put(JavaClientCodegen.USE_ABSTRACTION_FOR_FILES, true);
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("java")
.setLibrary(JavaClientCodegen.WEBCLIENT)
.setAdditionalProperties(properties)
.setInputSpec("src/test/resources/bugs/issue_7118.yaml")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
validateJavaSourceFiles(files);
Path userApi = Paths.get(output + "/src/main/java/xyz/abcdef/api/UsersApi.java");
TestUtils.assertFileContains(userApi,
// set of string
"ParameterizedTypeReference<Set<String>> localVarReturnType = new ParameterizedTypeReference<Set<String>>() {};",
"getUserIdSetRequestCreation().toEntity(localVarReturnType)",
// list of string
"ParameterizedTypeReference<List<String>> localVarReturnType = new ParameterizedTypeReference<List<String>>() {};",
"getUserIdListRequestCreation().toEntity(localVarReturnType)"
);
}
}

View File

@ -0,0 +1,38 @@
openapi: 3.0.1
info:
title: OpenAPI Petstore
description: "sample to get all user id as string list and string set"
version: 1.0.0
tags: []
paths:
/users/id?type=set:
get:
operationId: getUserIdSet
tags:
- users
responses:
'200':
description: response
content:
application/json:
schema:
type: array
"uniqueItems": true
"items":
"type": "string"
/users/id?type=list:
get:
operationId: getUserIdList
tags:
- users
responses:
'200':
description: response
content:
application/json:
schema:
type: array
"uniqueItems": false
"items":
"type": "string"