openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] [Java Native] client incorrect generation of `application/octet-stream`

Open melloware opened this issue 2 years ago • 1 comments

Bug Report Checklist

  • [x]Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output?
  • [ ] [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

When generating a native Java client using application/octet-stream it incorrectly tries to decode it to JSON using ObjectMapper.

openapi-generator version

7.2.0

OpenAPI declaration file content or url
"/datasetrw/snapshot/{snapshotId}/file/raw": {
      "get": {
        "operationId": "getFileRaw",
        "parameters": [
          {
            "description": "snapshot ID",
            "in": "path",
            "name": "snapshotId",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-f]{24}$",
              "type": "string"
            }
          },
          {
            "description": "path of file to get raw content for",
            "in": "query",
            "name": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "whether endpoint is used for download",
            "in": "query",
            "name": "download",
            "required": false,
            "schema": {
              "nullable": true,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "success"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "summary": "Get snapshot file raw content at specified path",
        "tags": [
          "DatasetRw"
        ]
      }
    },
Generation Details

It then generates this Java code trying to convert it to java.io.File...

public ApiResponse<File> getFileRawWithHttpInfo(String snapshotId, String path, Boolean download) throws ApiException {
    HttpRequest.Builder localVarRequestBuilder = getFileRawRequestBuilder(snapshotId, path, download);
    try {
      HttpResponse<InputStream> localVarResponse = memberVarHttpClient.send(
          localVarRequestBuilder.build(),
          HttpResponse.BodyHandlers.ofInputStream());
      if (memberVarResponseInterceptor != null) {
        memberVarResponseInterceptor.accept(localVarResponse);
      }
      try {
        if (localVarResponse.statusCode()/ 100 != 2) {
          throw getApiException("getFileRaw", localVarResponse);
        }
        return new ApiResponse<File>(
          localVarResponse.statusCode(),
          localVarResponse.headers().map(),
          localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<File>() {}) // closes the InputStream
        );
      } finally {
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }
    catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new ApiException(e);
    }
  }

You can see this line is incorrect.

  localVarResponse.body() == null ? null : memberVarObjectMapper.readValue(localVarResponse.body(), new TypeReference<File>() {}) // closes the InputStream

Its using the JSON Mapper to try and convert to a File.

melloware avatar Feb 06 '24 18:02 melloware

I encountered this same issue with version 7.17.0 using the java generator from the openapi-generator-maven-plugin. The specific error is:

org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class java.io.File] and content type [application/octet-stream]
	at org.springframework.web.client.DefaultRestClient.readWithMessageConverters(DefaultRestClient.java:251) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.readBody(DefaultRestClient.java:826) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.lambda$body$1(DefaultRestClient.java:765) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:586) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:540) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.RestClient$RequestHeadersSpec.exchange(RestClient.java:680) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.executeAndExtract(DefaultRestClient.java:821) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.client.DefaultRestClient$DefaultResponseSpec.body(DefaultRestClient.java:765) ~[spring-web-6.2.10.jar:6.2.10]
    ...

My OpenAPI spec is the following:

paths:
  ...
  /files/{id}/download:
    get:
      tags:
        - file-controller
      summary: Download a file by id
      description: Download a file by id
      operationId: downloadFile
      parameters:
        - name: id
          in: path
          description: UUID of the file
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: The actual contents of the file, as byte stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

With the following configuration of the maven plugin:

<configuration>
	<inputSpec>${project.basedir}/src/main/resources/swagger/client/files-service.yaml</inputSpec>
	<generatorName>java</generatorName>
	<library>restclient</library>
	<apiPackage>com.xxx.files_service.api</apiPackage>
	<modelPackage>com.xxx.files_service.model.api</modelPackage>
	<generateApiTests>false</generateApiTests>
	<generateModelTests>false</generateModelTests>
	<generateApiDocumentation>false</generateApiDocumentation>
	<strictSpec>true</strictSpec>
	<modelNamePrefix>Fs</modelNamePrefix>
	<configOptions>
		<annotationLibrary>none</annotationLibrary>
		<delegatePattern>true</delegatePattern>
		<documentationProvider>none</documentationProvider>
		<generateClientAsBean>true</generateClientAsBean>
		<openApiNullable>false</openApiNullable>
		<serializableModel>true</serializableModel>
		<useBeanValidation>true</useBeanValidation>
		<useOptional>true</useOptional>
		<useJakartaEe>true</useJakartaEe>
		<performBeanValidation>true</performBeanValidation>
	</configOptions>
</configuration>

In the meantime, I opted for returning a JSON object with the binary file data as String, not the best solution (especially with large files) but it works.

aronfiechter avatar Dec 05 '25 13:12 aronfiechter