From ea2ba0c3499bdc33f1462c4758713d44cbf55489 Mon Sep 17 00:00:00 2001
From: William Cheng
Date: Sun, 15 Dec 2024 22:32:46 +0800
Subject: [PATCH] [java][native] fix empty response body (#20334)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [Java][Client] (#13968)
* update
* update
* update slack url
---------
Co-authored-by: András Gábor Kis
---
README.md | 2 +-
docs/faq.md | 2 +-
.../Java/libraries/native/api.mustache | 44 ++-
.../codegen/java/JavaClientCodegenTest.java | 69 +++++
.../resources/3_0/java/native/issue13968.yaml | 33 +++
.../org/openapitools/client/api/BodyApi.java | 68 ++++-
.../org/openapitools/client/api/PetApi.java | 122 ++++++--
.../org/openapitools/client/api/StoreApi.java | 61 +++-
.../org/openapitools/client/api/UserApi.java | 94 ++++---
.../client/api/AnotherFakeApi.java | 17 +-
.../openapitools/client/api/DefaultApi.java | 17 +-
.../org/openapitools/client/api/FakeApi.java | 263 ++++++++++++------
.../client/api/FakeClassnameTags123Api.java | 17 +-
.../org/openapitools/client/api/PetApi.java | 125 ++++++---
.../org/openapitools/client/api/StoreApi.java | 61 +++-
.../org/openapitools/client/api/UserApi.java | 94 ++++---
website/src/pages/index.js | 2 +-
17 files changed, 812 insertions(+), 279 deletions(-)
create mode 100644 modules/openapi-generator/src/test/resources/3_0/java/native/issue13968.yaml
diff --git a/README.md b/README.md
index 08d16048eff..b50afb52365 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.openapitools%22%20AND%20a%3A%22openapi-generator%22)
[](./LICENSE)
[](https://opencollective.com/openapi_generator)
-[](https://join.slack.com/t/openapi-generator/shared_invite/zt-2uoef5v0g-XGwo8~2oJ3EoziDSO1CmdQ)
+[](https://join.slack.com/t/openapi-generator/shared_invite/zt-2wmkn4s8g-n19PJ99Y6Vei74WMUIehQA)
[](https://twitter.com/oas_generator)
[](https://gitpod.io/#https://github.com/OpenAPITools/openapi-generator)
[](https://conan.io/center/recipes/openapi-generator)
diff --git a/docs/faq.md b/docs/faq.md
index 9b416e374d5..061135a68b5 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -5,7 +5,7 @@ title: "FAQ: General"
## Do you have a chat room?
-[](https://join.slack.com/t/openapi-generator/shared_invite/zt-2uoef5v0g-XGwo8~2oJ3EoziDSO1CmdQ)
+[](https://join.slack.com/t/openapi-generator/shared_invite/zt-2wmkn4s8g-n19PJ99Y6Vei74WMUIehQA)
## What is the governance structure of the OpenAPI Generator project?
diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache
index 0ade8e9c2ba..309ee2fc321 100644
--- a/modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache
+++ b/modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache
@@ -271,22 +271,46 @@ public class {{classname}} {
}
{{/vendorExtensions.x-java-text-plain-string}}
{{^vendorExtensions.x-java-text-plain-string}}
- return new ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- {{#returnType}}
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<{{{returnType}}}>() {}) // closes the InputStream
- {{/returnType}}
- {{^returnType}}
- null
- {{/returnType}}
+ {{#returnType}}
+ {{! Fix for https://github.com/OpenAPITools/openapi-generator/issues/13968 }}
+ {{! 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. }}
+ {{! Keep this documentation here, because the problem is not obvious. }}
+ {{! `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. }}
+ {{! https://github.com/OpenAPITools/openapi-generator/pull/13993/commits/3e!37411d2acef0311c82e6d941a8e40b3bc0b6da }}
+ {{! 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. }}
+ {{! 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. }}
+ {{! 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. }}
+ {{! 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.}}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse<{{{returnType}}}>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
+ return new ApiResponse<{{{returnType}}}>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference<{{{returnType}}}>() {})
);
+ {{/returnType}}
+ {{^returnType}}
+ return new ApiResponse<{{{returnType}}}>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ {{/returnType}}
{{/vendorExtensions.x-java-text-plain-string}}
} finally {
{{^returnType}}
// Drain the InputStream
while (localVarResponse.body().read() != -1) {
- // Ignore
+ // Ignore
}
localVarResponse.body().close();
{{/returnType}}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
index dc31a2f42a1..f050ffe7877 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java
@@ -3263,4 +3263,73 @@ public class JavaClientCodegenTest {
" getCall(Integer queryParameter, final ApiCallback _callback)"
);
}
+
+ @Test
+ public void callNativeServiceWithEmptyResponseSync() throws IOException {
+ Map properties = new HashMap<>();
+ properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
+ properties.put("asyncNative", "false");
+
+ File output = Files.createTempDirectory("test").toFile();
+ output.deleteOnExit();
+
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName("java")
+ .setLibrary(JavaClientCodegen.NATIVE)
+ .setAdditionalProperties(properties)
+ .setInputSpec("src/test/resources/3_0/java/native/issue13968.yaml")
+ .setOutputDir(output.getAbsolutePath().replace("\\", "/"));
+
+ final ClientOptInput clientOptInput = configurator.toClientOptInput();
+ DefaultGenerator generator = new DefaultGenerator();
+
+ Map files = generator.opts(clientOptInput).generate().stream()
+ .collect(Collectors.toMap(File::getName, Function.identity()));
+
+ File apiFile = files.get("DefaultApi.java");
+ assertNotNull(apiFile);
+
+ JavaFileAssert.assertThat(apiFile).fileContains(
+ //reading the body into a string, then checking if it is blank.
+ "String responseBody = new String(localVarResponse.body().readAllBytes());",
+ "responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})"
+ );
+ }
+
+
+ /**
+ * This checks that the async client is not affected by this fix.
+ * See https://github.com/OpenAPITools/openapi-generator/issues/13968
+ */
+ @Test
+ public void callNativeServiceWithEmptyResponseAsync() throws IOException {
+ Map properties = new HashMap<>();
+ properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api");
+ properties.put("asyncNative", "true");
+
+ File output = Files.createTempDirectory("test").toFile();
+ output.deleteOnExit();
+
+ final CodegenConfigurator configurator = new CodegenConfigurator()
+ .setGeneratorName("java")
+ .setLibrary(JavaClientCodegen.NATIVE)
+ .setAdditionalProperties(properties)
+ .setInputSpec("src/test/resources/3_0/java/native/issue13968.yaml")
+ .setOutputDir(output.getAbsolutePath().replace("\\", "/"));
+
+ final ClientOptInput clientOptInput = configurator.toClientOptInput();
+ DefaultGenerator generator = new DefaultGenerator();
+
+ Map files = generator.opts(clientOptInput).generate().stream()
+ .collect(Collectors.toMap(File::getName, Function.identity()));
+
+ File apiFile = files.get("DefaultApi.java");
+ assertNotNull(apiFile);
+
+ JavaFileAssert.assertThat(apiFile).fileDoesNotContain(
+ //reading the body into a string, then checking if it is blank.
+ "String responseBody = new String(localVarResponse.body().readAllBytes());",
+ "responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})"
+ );
+ }
}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/test/resources/3_0/java/native/issue13968.yaml b/modules/openapi-generator/src/test/resources/3_0/java/native/issue13968.yaml
new file mode 100644
index 00000000000..e44e500d5d2
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/java/native/issue13968.yaml
@@ -0,0 +1,33 @@
+openapi: 3.0.3
+info:
+ title: Example Hello API
+ description: ''
+ version: v1
+servers:
+ - url: http://localhost
+ description: Global Endpoint
+paths:
+ /v1/emptyResponse:
+ get:
+ operationId: empty
+ description: returns an empty response
+ responses:
+ 200:
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/LocationData'
+ 204:
+ description: Empty response
+components:
+ schemas:
+ LocationData:
+ type: object
+ properties:
+ xPos:
+ type: integer
+ format: int32
+ yPos:
+ type: integer
+ format: int32
diff --git a/samples/client/echo_api/java/native/src/main/java/org/openapitools/client/api/BodyApi.java b/samples/client/echo_api/java/native/src/main/java/org/openapitools/client/api/BodyApi.java
index 257a7a54506..850a0067f4b 100644
--- a/samples/client/echo_api/java/native/src/main/java/org/openapitools/client/api/BodyApi.java
+++ b/samples/client/echo_api/java/native/src/main/java/org/openapitools/client/api/BodyApi.java
@@ -120,10 +120,21 @@ public class BodyApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("testBinaryGif", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -494,10 +505,21 @@ public class BodyApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("testEchoBodyAllOfPet", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -650,10 +672,21 @@ public class BodyApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("testEchoBodyPet", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -806,10 +839,21 @@ public class BodyApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("testEchoBodyStringEnum", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
diff --git a/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/PetApi.java b/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/PetApi.java
index 8a29cf444b0..d8e77055b86 100644
--- a/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/PetApi.java
+++ b/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/PetApi.java
@@ -121,10 +121,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("addPet", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -199,15 +210,15 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("deletePet", localVarResponse);
}
- return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- null
+ return new ApiResponse<>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
);
} finally {
// Drain the InputStream
while (localVarResponse.body().read() != -1) {
- // Ignore
+ // Ignore
}
localVarResponse.body().close();
}
@@ -280,10 +291,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("findPetsByStatus", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse>(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference>() {})
);
} finally {
}
@@ -371,10 +393,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("findPetsByTags", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse>(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference>() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference>() {})
);
} finally {
}
@@ -458,10 +491,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("getPetById", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -535,10 +579,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("updatePet", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
@@ -615,15 +670,15 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("updatePetWithForm", localVarResponse);
}
- return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- null
+ return new ApiResponse<>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
);
} finally {
// Drain the InputStream
while (localVarResponse.body().read() != -1) {
- // Ignore
+ // Ignore
}
localVarResponse.body().close();
}
@@ -714,10 +769,21 @@ public class PetApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("uploadFile", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
+ );
+ }
+
+ String responseBody = new String(localVarResponse.body().readAllBytes());
+ localVarResponse.body().close();
+
return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference() {}) // closes the InputStream
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ responseBody.isBlank()? null: memberVarObjectMapper.readValue(responseBody, new TypeReference() {})
);
} finally {
}
diff --git a/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/StoreApi.java b/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/StoreApi.java
index 40fd9c15c9e..69dda1a1214 100644
--- a/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/StoreApi.java
+++ b/samples/client/petstore/java/native-jakarta/src/main/java/org/openapitools/client/api/StoreApi.java
@@ -117,15 +117,15 @@ public class StoreApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("deleteOrder", localVarResponse);
}
- return new ApiResponse(
- localVarResponse.statusCode(),
- localVarResponse.headers().map(),
- null
+ return new ApiResponse<>(
+ localVarResponse.statusCode(),
+ localVarResponse.headers().map(),
+ null
);
} finally {
// Drain the InputStream
while (localVarResponse.body().read() != -1) {
- // Ignore
+ // Ignore
}
localVarResponse.body().close();
}
@@ -193,10 +193,21 @@ public class StoreApi {
if (localVarResponse.statusCode()/ 100 != 2) {
throw getApiException("getInventory", localVarResponse);
}
+ if (localVarResponse.body() == null) {
+ return new ApiResponse
Slack is free to download, and our
workspace is free to sign up.
>