Swagger-ui: OAS 3: Support for uploading an array of files in multipart requests

Created on 28 May 2018  路  40Comments  路  Source: swagger-api/swagger-ui

Swagger UI 3.16.0 added support for binary file upload using multipart requests. It works if a multipart request uses files as specific named fields, but doesn't work if the request uses an array of files.

Q&A (please complete the following information)

  • OS: Windows 7
  • Browser: Chrome 66
  • Method of installation: http://petstore.swagger.io, also using a local copy of Editor.
  • Swagger-UI version: 3.16.0
  • Swagger/OpenAPI version: OpenAPI 3.0

    Content & configuration

This spec is based on an example from the OpenAPI 3.0.1 Specification:
```yaml
openapi: 3.0.1
info:
title: Multiple file upload test
version: 0.0.0
servers:

  • url: http://httpbin.org
    paths:
    /post:
    post:
    requestBody:
    content:
    multipart/form-data:
    # This works:
    # schema:
    # type: object
    # properties:
    # upload:
    # type: string
    # format: binary
        # This doesn't work:
        schema:
          type: object
          properties:
            # The property name 'file' will be used for all files.
            file:
              type: array
              items:
                type: string
                format: binary
  responses:
    '200':
      description: OK

```

Is your feature request related to a problem?

Yes, clicking "try it out" shows "馃槺 Could not render this component, see the console." The console errors are:

TypeError: Cannot read property 'inferSchema' of undefined
    at t.value (swagger-ui.js:1)
    at t.render (swagger-ui.js:1)
    at u._renderValidatedComponentWithoutOwnerOrContext (ReactCompositeComponent.js:796)
    at u._renderValidatedComponent (ReactCompositeComponent.js:819)
    at u.performInitialMount (ReactCompositeComponent.js:359)
    at u.mountComponent (ReactCompositeComponent.js:255)
    at Object.mountComponent (ReactReconciler.js:43)
    at u.performInitialMount (ReactCompositeComponent.js:368)
    at u.mountComponent (ReactCompositeComponent.js:255)
    at Object.mountComponent (ReactReconciler.js:43)

TypeError: Cannot read property 'inferSchema' of undefined
    at t.value (swagger-ui.js:1)
    at t.render (swagger-ui.js:1)
    at u._renderValidatedComponentWithoutOwnerOrContext (ReactCompositeComponent.js:796)
    at u._renderValidatedComponent (ReactCompositeComponent.js:819)
    at u._updateRenderedComponent (ReactCompositeComponent.js:743)
    at u._performComponentUpdate (ReactCompositeComponent.js:721)
    at updateComponent (ReactCompositeComponent.js:642)
    at u.receiveComponent (ReactCompositeComponent.js:544)
    at Object.receiveComponent (ReactReconciler.js:122)
    at u._updateRenderedComponent (ReactCompositeComponent.js:751)

馃槺

Describe the solution you'd like

Clicking "try it out" presents a form where we can upload multiple files.

Describe alternatives you've considered

N/a

Additional context

This is (sort of) related to #3641 "OAS 3.0 Support Backlog".

try-it-out 3.x enhancement

Most helpful comment

Is there any news???
The same problem for me

All 40 comments

I'm having the same issue here on swaggerhub.
Any updat on this?

Works for me actually.

@cedric-c84-eu It looks like file array upload is not implemented yet. The file inputs are displayed now, but the actual request contains the string [object File] instead of the file contents.

curl -X POST "http://httpbin.org/post" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "file=[object File],[object File]"
POST http://httpbin.org/post
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIk1aWkovTmf7LSJX

------WebKitFormBoundaryIk1aWkovTmf7LSJX
Content-Disposition: form-data; name="file"

[object File],[object File]
------WebKitFormBoundaryIk1aWkovTmf7LSJX--

I dd in laravel the result of this and get ...characters">[object File],[object File]"...

same as @malutanpetronel

It is failing in curlify.js

        if (v instanceof win.File) {
          curlified.push( `"${k}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
        } else {
          curlified.push( `"${k}=${v}"` )
        }

curlify is not taking the array into account

probably it is better to take a blob

Is there any news???
The same problem for me

Hello is there any progress ? its opened more than year ...
The same problem for me

As always, PRs are welcome.

It will be more than welcome when it is ready

I did everything following documentation and it did work and did send files like this:

Content-Disposition: form-data; name="attachments"

[object File],[object File],[object File]
------WebKitFormBoundarykREvSfNcPTrA7BI7--

However, my backend app (Node.js and Multer for uploading files) was unable to see it.

I tried to upload files with javascript using FormData class like this:

const formData = new FormData();
formData.append('attachments', this.file[0]);
formData.append('attachments', this.file[1]);
formData.append('attachments', this.file[2]);

And it worked as expected.

I'm not sure if this header swagger ui sends is right or wrong but I think this is the reason. Sending single files works perfectly

same issue .... I'm stuck on this ...did anyone find any solution ??

Hi, any update on this?

Any news?

Please follow our contribution guidelines about voting to see how to make a more effective impact.

Any news?

Any News?

It is failing in curlify.js

        if (v instanceof win.File) {
          curlified.push( `"${k}=@${v.name}${v.type ? `;type=${v.type}` : ""}"` )
        } else {
          curlified.push( `"${k}=${v}"` )
        }

curlify is not taking the array into account

As far as I could figure out this is not (just) related to the curlify.js, the real problem comes from swagger-api/swagger-js.

In the actions.js the request parameters get parsed calling buildRequest from swagger-js.

When printing out the request parameters before calling _buildRequest_ we have a valid array containing the file information and when calling parsedRequest.body.get("file") after _buildRequest_ we already have a stringified version with something like _"[object File],[object File]"_.

So in _curlify.js_ we don't even get an array but a string with messed up file information already.

It looks like this is related to the issue https://github.com/swagger-api/swagger-js/issues/1470.

The actual problem is here where Arrays of File objects get stringified:

              if (typeof val === 'object' && !isFile) {
                if (Array.isArray(val)) {
                  newVal = val.toString()
                }
                else {
                  newVal = JSON.stringify(val)
                }
              }
              else {
                newVal = val
              }

Same problem. I get "[object File],[object File]" instead files

Same issue for me. Single file upload works perfectly !

Same here...

same here

Any news?

Any news? Single file upload works. Multiple file upload does not.

package com.riskcontrollimited.ratingsengine.rest.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Callable;

import static java.util.Objects.requireNonNull;

@RestController
@RequestMapping(value = "/fileupload")
@PropertySource("classpath:application.properties")
public class FileUploadController {

    private final File uploadDirRoot;
    private final UserService userService;
    private final HttpServletRequest request;

    public FileUploadController(@Value("${file.upload.dir}") String uploadDir,
                                final UserService userService,
                                final HttpServletRequest request) {
        this.uploadDirRoot = new File(uploadDir);
        this.userService = userService;
        this.request = request;
    }


    @Operation(
            summary = "Upload a single file.",
            description = "Upload a single file.", tags = {"fileupload"},
            requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
            )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "successful operation",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = MessageResponse.class)))),
            @ApiResponse(responseCode = "404", description = "entity not found",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class)))),
            @ApiResponse(responseCode = "403", description = "forbidden",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class)))),
            @ApiResponse(responseCode = "500", description = "internal server error",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class))))})
    @PostMapping(
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    Callable<ResponseEntity<MessageResponse>> uploadFile(@RequestParam(value = "file", required = false) MultipartFile file) throws Exception {
        final Optional<String> token = WebUtil.getTokenFromRequest(request);
        return () -> token
                .map(this.userService::getUserFromToken)
                .map(user -> this.uploadUserFiles(user, new MultipartFile[] {file}))
                .orElseThrow(() -> new RuntimeException("Unable to upload file."));
    }

    @Operation(
            summary = "Upload multiple files.",
            description = "Upload multiple files.", tags = {"fileupload"},
            requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE))
            )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "successful operation",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = MessageResponse.class)))),
            @ApiResponse(responseCode = "404", description = "entity not found",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class)))),
            @ApiResponse(responseCode = "403", description = "forbidden",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class)))),
            @ApiResponse(responseCode = "500", description = "internal server error",
                    content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiError.class))))})
    @PostMapping(
            value = "/multiple",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    Callable<ResponseEntity<MessageResponse>> uploadMultipleFiles(@RequestParam(value = "files", required= true) MultipartFile[] files) throws Exception {
        final Optional<String> token = WebUtil.getTokenFromRequest(request);
        return () -> token
                .map(this.userService::getUserFromToken)
                .map(user -> this.uploadUserFiles(user, files))
                .orElseThrow(() -> new RuntimeException("Unable to upload file."));
    }

    private ResponseEntity<MessageResponse> uploadUserFiles(User user, MultipartFile[] files) {
        Arrays.asList(files).forEach(file ->
        {
            File fileForUser;

            try {
                fileForUser = uploadPath(user, file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            try (InputStream in = file.getInputStream(); OutputStream out = new FileOutputStream(fileForUser)) {
                FileCopyUtils.copy(in, out);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });

        return ResponseEntity.ok(MessageResponse.builder()
                .status(HttpStatus.OK)
                .message("File(s) uploaded successfully.")
                .timestamp(LocalDateTime.now()).createApiResponse());

    }

    private File uploadPath(User e, MultipartFile file) throws IOException {
        File uploadPath = Paths.get(this.uploadDirRoot.getPath(), e.getId().toString()).toFile();
        if (!uploadPath.exists()) {
            uploadPath.mkdirs();
        }
        return new File(uploadPath.getAbsolutePath(), requireNonNull(file.getOriginalFilename()));
    }
}

There seems to be a PR connected to this #5999 that has already been merged. Has anyone tested this?

I just tried PR #5999 but still does not work. The input type is now text instead of file, so you can't choose files. If I change it to file it just posts a list of filenames, but no files.

image

@anzoman @dfeinzeig Uploading arrays of files is not supported yet.

@hkosova , can you point me at any relevant parts of the code that would need to be updated to support it?

@dfeinzeig

can you point me at any relevant parts of the code that would need to be updated to support it?

https://github.com/swagger-api/swagger-ui/issues/4600#issuecomment-585210238

Any news ?

@hkosova @anzoman @dfeinzeig @webhacking multiple file upload using OAS3.0 and arrays is now properly supported in the most recent SwaggerClient release (v3.25.4). Here's my sample definition:

{
  "openapi": "3.0.0",
  "servers": [
    {
      "url": "http://localhost:3300/api/v1"
    }
  ],
  "info": {
    "version": "1",
    "title": "MULTI PART TEST - OAS3",
    "description": ""
  },
  "paths": {
    "/land/content/ViewOfAuthOwner": {
      "post": {
        "summary": "",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "hhlContent:sort": {
                    "description": "",
                    "default": "id",
                    "type": "string",
                    "enum": [
                      "id",
                      "title"
                    ]
                  },
                  "hhlContent:order": {
                    "description": "",
                    "default": "desc",
                    "type": "string",
                    "enum": [
                      "asc",
                      "desc"
                    ]
                  },
                  "email[]": {
                    "description": "The list of emails.",
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "encoding": {
                "style": "pipeDelimited",
                "explode": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/land/content/uploadImage": {
      "post": {
        "summary": "upload image(s)",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "imageId": {
                    "description": "",
                    "default": "id",
                    "type": "string"
                  },
                  "images[]": {
                    "description": "The list of files",
                    "type": "array",
                    "items": {
                      "type": "file",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  }
}

screenshots (live local server response):
oas3-multiple-files-request
oas3-multiple-files-response

What version of org.springdoc should I need to update to?

I am currently working with version 1.3.4.

    <parent>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi</artifactId>
        <version>1.3.4</version>
    </parent>

Regards,

FO

@tim-lai OAS3 does not have type: file, it uses type: string + format: binary. Use this definition instead:

openapi: 3.0.0
servers:
  - url: 'http://localhost:3300/api/v1'
info:
  version: '1'
  title: MULTI PART TEST - OAS3
paths:
  /land/content/uploadImage:
    post:
      summary: upload image(s)
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                imageId:
                  description: ''
                  default: id
                  type: string
                'images[]':
                  description: The list of files
                  type: array
                  items:
                    type: string    # <----------------
                    format: binary
      responses:
        '200':
          description: ''

The images[] array is displayed as text inputs instead of file inputs:
image

@tim-lai What is the maven dependency you use for SwaggerClient release (v3.25.4)

@Mahalaxmi-13 We don't supply any maven dependencies that wrap our javascript projects.

@webron

this dependency should solve my problem:

<!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.3.9</version>
</dependency>

image

@hkosova @tim-lai I've fixed upload array file items for type string format binary in this PR #6040

@hkosova closing issue, per resolution with PR #6040. I also fixed the SwaggerClient sample definition.

hi, i am facing the same problem in flask_swager_ui , I can not upload array of files files from swagger , when doing request.files i am getting an empty dictionary but i m sending the data from Swagger-UI .

Was this page helpful?
0 / 5 - 0 ratings