OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
22.01k stars 6.6k forks source link

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

Open balzmo opened 5 years ago

balzmo commented 5 years ago
Description

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

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
LBoraz commented 4 years ago

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

thomaslechat commented 4 years ago

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

PieterJanMindCapture commented 2 years ago

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) {

thecrazzymouse commented 2 years ago

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

MrDolch commented 2 years ago

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.

PieterJanMindCapture commented 2 years ago

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

tofi86 commented 2 years ago

It looks like, this bug is back:

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