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

[Java][Spring][BUG] Code for multipart not working

Open balzmo opened this issue 6 years ago • 6 comments

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

balzmo avatar Dec 10 '18 09:12 balzmo

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

LBoraz avatar Jan 29 '20 21:01 LBoraz

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

thomaslechat avatar Jul 03 '20 07:07 thomaslechat

Confirmed this is still an issue as of swagger generator 3.0.29 with "spring" language and latest supported spec of "openapi: 3.0.3"

the following:

      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                someValue:
                  type: string
                  required: true
                file:
                  description: 'single file binary for upload.'
                  type: string
                  format: binary

is generated as: 1 @requestParam and 1 @requestPart. when it should have been: 2 @requestPart (making that change makes it work for the swagger-UI) Also: unrelated but "required" is still ignored.

details:

@Parameter(in = ParameterIn.DEFAULT, description = "",schema=@Schema()) @RequestParam(value="someValue", required=false)
String someValue,

@Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file) {

PieterJanMindCapture avatar Dec 10 '21 13:12 PieterJanMindCapture

Is there a plan to fix this. The only workaround as of now is to specify string instead of the complex object type to avoid type conversion

thecrazzymouse avatar May 31 '22 10:05 thecrazzymouse

It looks like, this bug is back:

        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                uploadtyp:
                  $ref: '#/components/schemas/UploadTyp'
                files:
                  type: array
                  items:
                    type: string
                    format: binary
              required:
                - uploadtyp
                - files

With openapi-generator-maven-plugin Version 5.4.0 it renders correctly both uploadtyp and files as RequestPart. But in Version 6.0.1 and 6.1.0 it renders uploadtyp as RequestParam and files as RequestPart.

MrDolch avatar Sep 14 '22 08:09 MrDolch

I've independantly verified: on 6.1.0. The binary file is the only parameter that receives a @RequestPart. All other properties become @RequestParam. Now, it will "work", but it doesn't do what the spec promises ( multipart/form-data, you'd expect the other form-data to be in the other 'parts')

6.1.0: 
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                test:
                  type: string
                test2:
                  $ref: '#/components/schemas/AddOnOption'
                assetFile:
                  type: string
                  format: binary
              required:
                - assetFile

generates:

     @RequestParam(value = "test", required = false) String test,
     @RequestParam(value = "test2", required = false) AddOnOption test2,
     @RequestPart(value = "assetFile", required = true) MultipartFile assetFile

when expected is: @RequestPart(value = "test", required = false) String test, @RequestPart(value = "test2", required = false) AddOnOption test2, @RequestPart(value = "assetFile", required = true) MultipartFile assetFile

PieterJanMindCapture avatar Sep 15 '22 15:09 PieterJanMindCapture

It looks like, this bug is back:

Yep, there's a regression issue for that: https://github.com/OpenAPITools/openapi-generator/issues/12498

tofi86 avatar Sep 28 '22 09:09 tofi86