Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAS 3: Support for uploading an array of files in multipart requests #4600

Closed
hkosova opened this issue May 28, 2018 · 40 comments
Closed

OAS 3: Support for uploading an array of files in multipart requests #4600

hkosova opened this issue May 28, 2018 · 40 comments

Comments

@hkosova
Copy link
Contributor

hkosova commented May 28, 2018

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:

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".

@tuxedo0801
Copy link

tuxedo0801 commented Jul 24, 2018

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

@cedric-c84-eu
Copy link

Works for me actually.

@hkosova
Copy link
Contributor Author

hkosova commented Aug 6, 2018

@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--

@malutanpetronel
Copy link

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

@bleuscyther
Copy link

same as @malutanpetronel

@lcahlander
Copy link

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

@default-writer
Copy link

probably it is better to take a blob

@mansuryan
Copy link

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

@0s3v3n0
Copy link

0s3v3n0 commented Aug 22, 2019

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

@webron
Copy link
Contributor

webron commented Aug 22, 2019

As always, PRs are welcome.

@jmfernandez
Copy link

It will be more than welcome when it is ready

@Darkzarich
Copy link

Darkzarich commented Oct 1, 2019

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

------WebKitFormBoundarykREvSfNcPTrA7BI7
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

@rafiGaucho
Copy link

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

@NSwati
Copy link

NSwati commented Nov 14, 2019

Hi, any update on this?

@wstrametz
Copy link

Any news?

@webron
Copy link
Contributor

webron commented Nov 26, 2019

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

@kasvith

This comment has been minimized.

@YaakovDantas

This comment has been minimized.

@petriashev
Copy link

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

@vman-x
Copy link

vman-x commented Mar 17, 2020

Same issue for me. Single file upload works perfectly !

@PedroD
Copy link

PedroD commented Mar 25, 2020

Same here...

@tksarul
Copy link

tksarul commented Mar 26, 2020

same here

@jieny
Copy link

jieny commented Apr 12, 2020

Any news?

@ffroliva
Copy link

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()));
    }
}

@anzoman
Copy link

anzoman commented May 20, 2020

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

@dfeinzeig
Copy link

dfeinzeig commented May 21, 2020

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

@hkosova
Copy link
Contributor Author

hkosova commented May 21, 2020

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

@dfeinzeig
Copy link

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

@hkosova
Copy link
Contributor Author

hkosova commented May 21, 2020

@dfeinzeig

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

#4600 (comment)

@webhacking
Copy link

Any news ?

@tim-lai
Copy link
Contributor

tim-lai commented May 27, 2020

@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

@ffroliva
Copy link

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

@hkosova
Copy link
Contributor Author

hkosova commented May 28, 2020

@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

@Mahalaxmi-13
Copy link

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

@webron
Copy link
Contributor

webron commented May 28, 2020

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

@ffroliva
Copy link

@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

@JustMik
Copy link
Contributor

JustMik commented May 29, 2020

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

@tim-lai
Copy link
Contributor

tim-lai commented Jun 2, 2020

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

@KuldeepPCodes
Copy link

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 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests