Openapi-generator: [Java][Spring][BUG] Code for multipart not working

Created on 10 Dec 2018  路  2Comments  路  Source: OpenAPITools/openapi-generator

Description

When generating multipart interfaces, the interface code is not working for Spring with Jersey:

  • A mixture of @RequestPart and @RequestParam is used to describe the multipart, while only @RequestPart is recognized by the framework at runtime
  • The parameter name for binary parts is not taken from the specification, instead "file" is used
  • The flag "required" is ignored for binary parts

Actual result:

ResponseEntity<Void> upload(
    @ApiParam(value = "", required=true, defaultValue="null")
    @RequestParam(value="meta", required=true)  MetaData meta,
    @ApiParam(value = "file detail") @Valid
    @RequestPart("file") MultipartFile document) {

This code line leads to the following error on invocation:

Failed to convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'demo.oagen.fileupload.spring.gen.MetaData'; nested exception is
java.lang.IllegalStateException: Cannot convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'demo.oagen.fileupload.spring.gen.MetaData':
no matching editors or conversion strategy found

Expected result:

ResponseEntity<Void> upload(
    @ApiParam(value = "", required=true, defaultValue="null")
    @RequestPart(value="meta", required=true)  MetaData meta,
    @ApiParam(value = "file detail") @Valid
    @RequestPart(value = "document", required=true) MultipartFile document) {
openapi-generator version

3.3.4

OpenAPI declaration file content or url

Example interface extract, full example see attached:

      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                meta:
                  $ref: "#/components/schemas/MetaData"
                document:
                  type: string
                  format: binary
              required:
                - meta
                - document
Command line used for generation

Maven code:

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>3.3.4</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/demo.yaml</inputSpec>
                <generatorName>spring</generatorName>
                <output>${project.basedir}/target</output>
                <apiPackage>demo.oagen.fileupload.spring.gen</apiPackage>
                <modelPackage>demo.oagen.fileupload.spring.gen</modelPackage>
                <configOptions>
                    <sourceFolder>/generated-sources/java</sourceFolder>
                    <basePackage>demo.oagen.fileupload.spring.gen</basePackage>
                    <configPackage>demo.oagen.fileupload.spring.gen.config</configPackage>
                    <useTags>true</useTags>
                    <interfaceOnly>true</interfaceOnly>
                </configOptions>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>openapi-generator-maven-plugin</artifactId>
            <version>3.3.4</version>
            <type>maven-plugin</type>
        </dependency>
    </dependencies>
</plugin>
Steps to reproduce

Execute code generation via Maven on example project attached.
oagen-fileupload-spring-demo.zip

Send the following request:

POST http://127.0.0.1:8080/upload HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: multipart/form-data; boundary="----=_Part_17_744410358.1544434419692"
MIME-Version: 1.0
Content-Length: 82439
Host: 127.0.0.1:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_162)


------=_Part_17_744410358.1544434419692
Content-Type: application/json; name=demo-meta.json
Content-Transfer-Encoding: binary
Content-Disposition: form-data; name="meta"; filename="demo-meta.json"

{
    "fileName": "testfile.pdf",
    "comment": "hello"
}
------=_Part_17_744410358.1544434419692
Content-Type: application/pdf; name=Test.pdf
Content-Transfer-Encoding: binary
Content-Disposition: form-data; name="document"; filename="Test.pdf"

(binary content here)

Note: The example pom.xml contains a workaround for replacing the concerned code line with the correct code.

Suggest a fix
  • Use only @RequestParam for all parts of the multipart request body
  • Consider the property name for binary parts
  • Consider the "required" flag for binary parts
Bug Spring

Most helpful comment

this is still a bug in version 4.2.2 of the generator and multiple files (specified as an array of binary strings) don't produce a list of multiPartFiles. Swagger generator does a better job generating a list of Resource objects, respecting the "required" indications and using the names provided in the yaml for the parts

All 2 comments

this is still a bug in version 4.2.2 of the generator and multiple files (specified as an array of binary strings) don't produce a list of multiPartFiles. Swagger generator does a better job generating a list of Resource objects, respecting the "required" indications and using the names provided in the yaml for the parts

Hello,

I had the exact same problem, but had the particularity to use a forked version of openapi-generator. We switched to the official version 4.3.1 and it looks ok to me :

      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                myObject:
                  $ref: '#/components/schemas/MyObject'
                file:
                  type: string
                  format: binary
              required:
                - myObject
                - file

The corresponding java generation :

@RequestMapping(value = "/clients/{client_id}/accounts/{account_id}/myEndpoint",
        produces = { "application/json" }, 
        consumes = { "multipart/form-data" },
        method = RequestMethod.POST)
    ResponseEntity<DossierGestionReponse> myEndpoint(
        @ApiParam(value = "Client id",required=true) @PathVariable("client_id") String clientId,
        @ApiParam(value = "Account id",required=true) @PathVariable("account_id") String accountId,
        @ApiParam(value = "", required=true, defaultValue="null") @RequestPart(value="myObject", required=true) MyObject myObject,
        @ApiParam(value = "") @Valid @RequestPart(value = "file") MultipartFile file);

We also struggled to test this with postman. we need to specify explicitely the content type of "myObject" like this (Otherwise we get an error 415) :
image
And do the same in our tests :

// Given
MockPart fileMock = new MockPart("file", "file", "test data".getBytes(StandardCharsets.UTF_8));

MockPart myObjectMock = new MockPart("myObject", "myObject", objectMapper.writeValueAsString(myObject).getBytes(StandardCharsets.UTF_8));
myObjectMock.getHeaders().setContentType(MediaType.APPLICATION_JSON); // Set the content type explicitely in form-data for json objects

//WHEN
MvcResult mvcResult = this.mockMvc.perform(
    multipart("/clients/" + DEFAULT_CLIENT_ID + "/accounts/" + DEFAULT_ACCOUNT_ID + "/myEndpoint")
        .part(fileMock)
        .part(myObjectMock))
    .andDo(print())
    .andExpect(status().isOk())
    .andReturn();

Hope this helps

Was this page helpful?
0 / 5 - 0 ratings