swagger-api / swagger-ui

Swagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.
https://swagger.io
Apache License 2.0
26.25k stars 8.9k forks source link

Inconsistent curl generated for array of objects in multipart/form-data request #7625

Open wojtekm462 opened 2 years ago

wojtekm462 commented 2 years ago

Hi,

Curl command generated for a request with multipart/form-data is inconsistent/mixed. Having array of objects as form field in multipart/form-data request. Each array object is JSON object. First element is properly presented in curl command without stringify operation applied. Any array object added as result of "Add object item" button is stringified and ends as string, not an object.

Issue spotted when working on project using Python 3.8, Connexion 2.9.0, swagger-ui bundle 0.0.9 but can be reproduced on editor.swagger.io as well.

Q&A (please complete the following information)

Content & configuration

Given below example Swagger/OpenAPI definition:

paths:
  /foo:
    post:
      tags:
      - foo
      summary: bar
      operationId: foo
      requestBody:
        description: ""
        content:
          multipart/form-data:
            schema:
              type: object
              description: "zzz"
              properties:
                property1:
                  type: string
                  description: "property of type string, holding kind of version"
                  example: "1.0"
                  default: "1.0"
                  nullable: false
                  enum: 
                    - "1.0"
                property2:
                  type: array
                  description: array of objects
                  items:
                    type: object
                    description: ""
                    properties:
                      aID: 
                        type: string
                      aName:
                        type: string
                      aVersion:
                        type: string
                      aLink:
                        type: string
                    example:
                      aID: "value aID"
                      aName: "value aName"
                      aVersion: "value aVersion"
                      aLink: "value aLink"
                    required:
                      - aID
                      - aName
                      - aVersion
                      - aLink
                property3:
                  type: integer
                  format: int32
                  description: "property of type integer"
                  nullable: true
                property4:
                  type: string
                  description: "yet another property of type string"
                  example: cow
                  default: cow
                  nullable: false
                  enum:
                    - cow
                    - dog
              required:
                - property1
                - property2
            encoding:
              property1:
                contentType: text/plain
              property2:
                contentType: application/json
              property3:
                contentType: text/plain
              property4:
                contentType: text/plain
      responses:
        200:
          description: OK
          content: {}

Swagger-UI configuration options: None

Describe the bug you're encountering

Produced curl command from editor.swagger.io is as follows:

curl -X 'POST' \
  'https://petstore.swagger.io/v2/foo' \
  -H 'accept: */*' \
  -H 'Content-Type: multipart/form-data' \
  -F 'property1=1.0' \
  -F 'property2=[{"aID":"value aID","aName":"value aName","aVersion":"value aVersion","aLink":"value aLink"},"{\n  \"aID\": \"value aID\",\n  \"aName\": \"value aName\",\n  \"aVersion\": \"value aVersion\",\n  \"aLink\": \"value aLink\"\n}"]' \
  -F 'property3=1234' \
  -F 'property4=cow'

property2 is the array where first element is true JSON object, but second one is plain string and not object according to schema. In case of input validation to the API specification as is in Connexion framework the second of stringified JSON object does not pass the JSON schema validation and request is rejected.

To reproduce...

Steps to reproduce the behavior:

  1. Go to editor.swagger.io
  2. Add path with definition from example
  3. Navigate to endpoint in rendered swagger-ui page
  4. Select path foo
  5. Click 'try it out'
  6. Under property2 add array object element with button 'Add object item'
  7. Click Execute
  8. Observe generated curl command

Expected behavior

Expected curl for array of objects shall be like first element, without stringified contents of elements added with 'Add object item'

curl -X 'POST' \
  'https://petstore.swagger.io/v2/foo' \
  -H 'accept: */*' \
  -H 'Content-Type: multipart/form-data' \
  -F 'property1=1.0' \
  -F 'property2=[{"aID":"value aID","aName":"value aName","aVersion":"value aVersion","aLink":"value aLink"}, {"aID":"value aID","aName":"value aName","aVersion":"value aVersion","aLink":"value aLink"}]' \
  -F 'property3=1234' \
  -F 'property4=cow'

Screenshots

Additional context or thoughts

Generated curl does not respect the supplied encoding in request body by not specifying the form fields with ';type=' Proper curl coding of array of JSON objects would be:

  -F 'property2=[{"aID":"value aID","aName":"value aName","aVersion":"value aVersion","aLink":"value aLink"}, {"aID":"value aID","aName":"value aName","aVersion":"value aVersion","aLink":"value aLink"}];type=application/json' \
josip-volarevic commented 1 year ago

@wojtekm462 Any secret knowledge you've obtained in the meantime that you might share with me?

I'm experiencing the same issue and haven't managed to dig out a solution

RedMickey commented 1 year ago

Hi, I faced the same issue, did you find any working solution?

josip-volarevic commented 1 year ago

@RedMickey I think I managed to find a workaround in Nest using @Transform from class-transformer. Check out this file: https://github.com/open-sauce-labs/d-reader-backend/blob/dev/src/comic-issue/dto/create-comic-issue.dto.ts

Let me know in return if you figure out how to handle multipart file uploads in nested properties! 🤣

e.g. request.body.someProperty.arrayOfObjectsWhichContainFiles has issues with Swagger I believe

EDIT: adding the relevant code snippet in case I ever ruin the link above.

What this code does is: if the property is of type string, turn it into an array. This essentially takes care of the faulty Swagger handling and ignores all other situations.

  @ArrayUnique()
  @Type(() => String)
  @ApiProperty({ type: [String] })
  @Transform(({ value }: { value: string[] | string }) => {
    if (typeof value === 'string') {
      return value.split(',');
    } else return value;
  })
  hashlist: string[];
}
RedMickey commented 1 year ago

Hi @josip-volarevic thanks for you reply. I managed to find the following solution:

    @Post('/test/req')
    @ApiConsumes('multipart/form-data')
    @ApiOkResponse({schema: {example: 1}})
    @ApiOperation({
        requestBody: {
            content: {
                'multipart/form-data': {
                    schema: {
                        type: 'object',
                        properties: {
                            testParam: {
                                type: 'array',
                                items: {
                                    type: 'string',
                                },
                            },
                        },
                    },
                    encoding: {
                        testParam: {
                            style: 'form',
                            explode: true,
                        },
                    },
                },
            },
        },
    })
    @UseInterceptors(FileInterceptor('logo'))
    testReq(@Req() req: Request, @Body() body: any, @UploadedFile() logo?: UploadFile) {
        console.log(req.body);
        return 1;
    }

If I specify encoding options, then the swagger splits the array properly on sending. My workaround is based on this comment. image

So, in the request body I get the following:

{ testParam: [ 'string', 'string' ] }
josip-volarevic commented 1 year ago

@RedMickey don't you now have issues with the logo file? I don't see it anywhere on your screenshot available for upload