Skip to content

Commit ea2ba0c

Browse files
wing328Frontrider
andauthored
[java][native] fix empty response body (#20334)
* [Java][Client] (#13968) * update * update * update slack url --------- Co-authored-by: András Gábor Kis <[email protected]>
1 parent 9b3484c commit ea2ba0c

File tree

17 files changed

+812
-279
lines changed

17 files changed

+812
-279
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Stable releases in Maven Central](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/org/openapitools/openapi-generator/maven-metadata.xml.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.openapitools%22%20AND%20a%3A%22openapi-generator%22)
77
[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-orange)](./LICENSE)
88
[![Open Collective backers](https://img.shields.io/opencollective/backers/openapi_generator?color=orange&label=OpenCollective%20Backers)](https://opencollective.com/openapi_generator)
9-
[![Join the Slack chat room](https://img.shields.io/badge/Slack-Join%20the%20chat%20room-orange)](https://join.slack.com/t/openapi-generator/shared_invite/zt-2uoef5v0g-XGwo8~2oJ3EoziDSO1CmdQ)
9+
[![Join the Slack chat room](https://img.shields.io/badge/Slack-Join%20the%20chat%20room-orange)](https://join.slack.com/t/openapi-generator/shared_invite/zt-2wmkn4s8g-n19PJ99Y6Vei74WMUIehQA)
1010
[![Follow OpenAPI Generator Twitter account to get the latest update](https://img.shields.io/twitter/follow/oas_generator.svg?style=social&label=Follow)](https://twitter.com/oas_generator)
1111
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/OpenAPITools/openapi-generator)
1212
[![Conan Center](https://shields.io/conan/v/openapi-generator)](https://conan.io/center/recipes/openapi-generator)

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: "FAQ: General"
55

66
## Do you have a chat room?
77

8-
[![Join the Slack chat room](https://img.shields.io/badge/Slack-Join%20the%20chat%20room-orange)](https://join.slack.com/t/openapi-generator/shared_invite/zt-2uoef5v0g-XGwo8~2oJ3EoziDSO1CmdQ)
8+
[![Join the Slack chat room](https://img.shields.io/badge/Slack-Join%20the%20chat%20room-orange)](https://join.slack.com/t/openapi-generator/shared_invite/zt-2wmkn4s8g-n19PJ99Y6Vei74WMUIehQA)
99

1010
## What is the governance structure of the OpenAPI Generator project?
1111

modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -271,22 +271,46 @@ public class {{classname}} {
271271
}
272272
{{/vendorExtensions.x-java-text-plain-string}}
273273
{{^vendorExtensions.x-java-text-plain-string}}
274-
return new ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>(
275-
localVarResponse.statusCode(),
276-
localVarResponse.headers().map(),
277-
{{#returnType}}
278-
localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {}) // closes the InputStream
279-
{{/returnType}}
280-
{{^returnType}}
281-
null
282-
{{/returnType}}
274+
{{#returnType}}
275+
{{! Fix for https://github.com/OpenAPITools/openapi-generator/issues/13968 }}
276+
{{! This part had a bugfix for an empty response in the past, but this part of that PR was reverted because it was not doing anything. }}
277+
{{! Keep this documentation here, because the problem is not obvious. }}
278+
{{! `InputStream.available()` was used, but that only works for inputstreams that are already in memory, it will not give the right result if it is a remote stream. We only work with remote streams here. }}
279+
{{! https://github.com/OpenAPITools/openapi-generator/pull/13993/commits/3e!37411d2acef0311c82e6d941a8e40b3bc0b6da }}
280+
{{! The `available` method would work with a `PushbackInputStream`, because we could read 1 byte to check if it exists then push it back so Jackson can read it again. The issue with that is that it will also insert an ascii character for "head of input" and that will break Jackson as it does not handle special whitespace characters. }}
281+
{{! A fix for that problem is to read it into a string and remove those characters, but if we need to read it before giving it to jackson to fix the string then just reading it into a string as is to do an emptiness check is the cleaner solution. }}
282+
{{! We could also manipulate the inputstream to remove that bad character, but string manipulation is easier to read and this codepath is not asyncronus so we do not gain anything by reading the stream later. }}
283+
{{! This fix does make it unsuitable for large amounts of data because `InputStream.readAllbytes` is not meant for it, but a syncronus client is already not the right tool for that.}}
284+
if (localVarResponse.body() == null) {
285+
return new ApiResponse<{{{returnType}}}>(
286+
localVarResponse.statusCode(),
287+
localVarResponse.headers().map(),
288+
null
289+
);
290+
}
291+
292+
String responseBody = new String(localVarResponse.body().readAllBytes());
293+
localVarResponse.body().close();
294+
295+
return new ApiResponse<{{{returnType}}}>(
296+
localVarResponse.statusCode(),
297+
localVarResponse.headers().map(),
298+
responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {})
283299
);
300+
{{/returnType}}
301+
{{^returnType}}
302+
return new ApiResponse<{{{returnType}}}>(
303+
localVarResponse.statusCode(),
304+
localVarResponse.headers().map(),
305+
null
306+
);
307+
{{/returnType}}
284308
{{/vendorExtensions.x-java-text-plain-string}}
285309
} finally {
286310
{{^returnType}}
287311
// Drain the InputStream
288312
while (localVarResponse.body().read() != -1) {
289-
// Ignore
313+
// Ignore
290314
}
291315
localVarResponse.body().close();
292316
{{/returnType}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3263,4 +3263,73 @@ public void testGenerateParameterId() {
32633263
" getCall(Integer queryParameter, final ApiCallback _callback)"
32643264
);
32653265
}
3266+
3267+
@Test
3268+
public void callNativeServiceWithEmptyResponseSync() throws IOException {
3269+
Map<String, Object> properties = new HashMap<>();
3270+
properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
3271+
properties.put("asyncNative", "false");
3272+
3273+
File output = Files.createTempDirectory("test").toFile();
3274+
output.deleteOnExit();
3275+
3276+
final CodegenConfigurator configurator = new CodegenConfigurator()
3277+
.setGeneratorName("java")
3278+
.setLibrary(JavaClientCodegen.NATIVE)
3279+
.setAdditionalProperties(properties)
3280+
.setInputSpec("src/test/resources/3_0/java/native/issue13968.yaml")
3281+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
3282+
3283+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
3284+
DefaultGenerator generator = new DefaultGenerator();
3285+
3286+
Map<String, File> files = generator.opts(clientOptInput).generate().stream()
3287+
.collect(Collectors.toMap(File::getName, Function.identity()));
3288+
3289+
File apiFile = files.get("DefaultApi.java");
3290+
assertNotNull(apiFile);
3291+
3292+
JavaFileAssert.assertThat(apiFile).fileContains(
3293+
//reading the body into a string, then checking if it is blank.
3294+
"String responseBody = new String(localVarResponse.body().readAllBytes());",
3295+
"responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<LocationData>() {})"
3296+
);
3297+
}
3298+
3299+
3300+
/**
3301+
* This checks that the async client is not affected by this fix.
3302+
* See https://github.com/OpenAPITools/openapi-generator/issues/13968
3303+
*/
3304+
@Test
3305+
public void callNativeServiceWithEmptyResponseAsync() throws IOException {
3306+
Map<String, Object> properties = new HashMap<>();
3307+
properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
3308+
properties.put("asyncNative", "true");
3309+
3310+
File output = Files.createTempDirectory("test").toFile();
3311+
output.deleteOnExit();
3312+
3313+
final CodegenConfigurator configurator = new CodegenConfigurator()
3314+
.setGeneratorName("java")
3315+
.setLibrary(JavaClientCodegen.NATIVE)
3316+
.setAdditionalProperties(properties)
3317+
.setInputSpec("src/test/resources/3_0/java/native/issue13968.yaml")
3318+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
3319+
3320+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
3321+
DefaultGenerator generator = new DefaultGenerator();
3322+
3323+
Map<String, File> files = generator.opts(clientOptInput).generate().stream()
3324+
.collect(Collectors.toMap(File::getName, Function.identity()));
3325+
3326+
File apiFile = files.get("DefaultApi.java");
3327+
assertNotNull(apiFile);
3328+
3329+
JavaFileAssert.assertThat(apiFile).fileDoesNotContain(
3330+
//reading the body into a string, then checking if it is blank.
3331+
"String responseBody = new String(localVarResponse.body().readAllBytes());",
3332+
"responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<LocationData>() {})"
3333+
);
3334+
}
32663335
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Example Hello API
4+
description: ''
5+
version: v1
6+
servers:
7+
- url: http://localhost
8+
description: Global Endpoint
9+
paths:
10+
/v1/emptyResponse:
11+
get:
12+
operationId: empty
13+
description: returns an empty response
14+
responses:
15+
200:
16+
description: Successful operation
17+
content:
18+
application/json:
19+
schema:
20+
$ref: '#/components/schemas/LocationData'
21+
204:
22+
description: Empty response
23+
components:
24+
schemas:
25+
LocationData:
26+
type: object
27+
properties:
28+
xPos:
29+
type: integer
30+
format: int32
31+
yPos:
32+
type: integer
33+
format: int32

samples/client/echo_api/java/native/src/main/java/org/openapitools/client/api/BodyApi.java

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,21 @@ public ApiResponse<File> testBinaryGifWithHttpInfo() throws ApiException {
120120
if (localVarResponse.statusCode()/ 100 != 2) {
121121
throw getApiException("testBinaryGif", localVarResponse);
122122
}
123+
if (localVarResponse.body() == null) {
124+
return new ApiResponse<File>(
125+
localVarResponse.statusCode(),
126+
localVarResponse.headers().map(),
127+
null
128+
);
129+
}
130+
131+
String responseBody = new String(localVarResponse.body().readAllBytes());
132+
localVarResponse.body().close();
133+
123134
return new ApiResponse<File>(
124-
localVarResponse.statusCode(),
125-
localVarResponse.headers().map(),
126-
localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<File>() {}) // closes the InputStream
135+
localVarResponse.statusCode(),
136+
localVarResponse.headers().map(),
137+
responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<File>() {})
127138
);
128139
} finally {
129140
}
@@ -494,10 +505,21 @@ public ApiResponse<Pet> testEchoBodyAllOfPetWithHttpInfo(Pet pet) throws ApiExce
494505
if (localVarResponse.statusCode()/ 100 != 2) {
495506
throw getApiException("testEchoBodyAllOfPet", localVarResponse);
496507
}
508+
if (localVarResponse.body() == null) {
509+
return new ApiResponse<Pet>(
510+
localVarResponse.statusCode(),
511+
localVarResponse.headers().map(),
512+
null
513+
);
514+
}
515+
516+
String responseBody = new String(localVarResponse.body().readAllBytes());
517+
localVarResponse.body().close();
518+
497519
return new ApiResponse<Pet>(
498-
localVarResponse.statusCode(),
499-
localVarResponse.headers().map(),
500-
localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<Pet>() {}) // closes the InputStream
520+
localVarResponse.statusCode(),
521+
localVarResponse.headers().map(),
522+
responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<Pet>() {})
501523
);
502524
} finally {
503525
}
@@ -650,10 +672,21 @@ public ApiResponse<Pet> testEchoBodyPetWithHttpInfo(Pet pet) throws ApiException
650672
if (localVarResponse.statusCode()/ 100 != 2) {
651673
throw getApiException("testEchoBodyPet", localVarResponse);
652674
}
675+
if (localVarResponse.body() == null) {
676+
return new ApiResponse<Pet>(
677+
localVarResponse.statusCode(),
678+
localVarResponse.headers().map(),
679+
null
680+
);
681+
}
682+
683+
String responseBody = new String(localVarResponse.body().readAllBytes());
684+
localVarResponse.body().close();
685+
653686
return new ApiResponse<Pet>(
654-
localVarResponse.statusCode(),
655-
localVarResponse.headers().map(),
656-
localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<Pet>() {}) // closes the InputStream
687+
localVarResponse.statusCode(),
688+
localVarResponse.headers().map(),
689+
responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<Pet>() {})
657690
);
658691
} finally {
659692
}
@@ -806,10 +839,21 @@ public ApiResponse<StringEnumRef> testEchoBodyStringEnumWithHttpInfo(String body
806839
if (localVarResponse.statusCode()/ 100 != 2) {
807840
throw getApiException("testEchoBodyStringEnum", localVarResponse);
808841
}
842+
if (localVarResponse.body() == null) {
843+
return new ApiResponse<StringEnumRef>(
844+
localVarResponse.statusCode(),
845+
localVarResponse.headers().map(),
846+
null
847+
);
848+
}
849+
850+
String responseBody = new String(localVarResponse.body().readAllBytes());
851+
localVarResponse.body().close();
852+
809853
return new ApiResponse<StringEnumRef>(
810-
localVarResponse.statusCode(),
811-
localVarResponse.headers().map(),
812-
localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<StringEnumRef>() {}) // closes the InputStream
854+
localVarResponse.statusCode(),
855+
localVarResponse.headers().map(),
856+
responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<StringEnumRef>() {})
813857
);
814858
} finally {
815859
}

0 commit comments

Comments
 (0)