Search code examples
swagger-uiopenapiquarkus

How to view a FileUpload response body as an upload button when seen through Swagger UI


I'm working on building a Quarkus REST API for uploading files and I'd like to use the Swagger UI while developing to have a tight feedback loop as I develop the site. However I'm struggling to get Swagger UI to format the file input elegantly.

I've tried to follow the RESTEasy Reactive guide for instruction on how to accept a file as a response body for a route but I can't seem to get Swagger UI to display the input as anything but a large text field. Here is the Quarkus 2.14 example as seen through the Swagger UI.

Quarkus example

While developing APIs before I've grown familiar with a file upload prompt in the Swagger UI as shown here in Swagger docs. The file upload prompt allows an end user to select any arbitrary file (binary or text) to upload. I'd expect I should be able to convey to Swagger UI that I'd like this field treated as a file instead of text.

Is this a bug or do I need some extra metadata to pick a more appropriate view for this API's inputs?

Details to reproduce

I'm using Quarkus 2.14 and am able to reproduce the issue simply using the example found in the Quarkus RESTEasy guide.

Quarkus Extensions:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-swagger-ui</artifactId>
</dependency>

RESTEasy Route:

package com.me.example;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;

@Path("/files")
@RequestScoped
public class ExampleResource {

    public static class Person {
        public String firstName;
        public String lastName;
    }

    @POST
    public void multipart(@RestForm String description,
            @RestForm("image") FileUpload file,
            @RestForm @PartType(MediaType.APPLICATION_JSON) Person person) {
        
    }

}

Rendered OpenAPI document:

---
openapi: 3.0.3
info:
  title: API
  version: 0.1.0-SNAPSHOT
paths:
  /files:
    post:
      tags:
      - Example Resource
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                description:
                  type: string
                image:
                  $ref: '#/components/schemas/FileUpload'
                person:
                  $ref: '#/components/schemas/Person'
            encoding:
              person:
                contentType: application/json
      responses:
        "201":
          description: Created
components:
  schemas:
    FileUpload:
      type: object
    Person:
      type: object
      properties:
        firstName:
          type: string
        lastName:
          type: string

Solution

  • Helen found a relevant Quarkus issue that helped identify a workaround to my issue. With some modifications I was able to get a single file to upload while maintaining the intuitive file picker interface in Swagger UI.

    package com.me.example;
    
    import javax.enterprise.context.RequestScoped;
    import javax.ws.rs.Consumes;
    import javax.ws.rs.POST;
    import javax.ws.rs.Path;
    import javax.ws.rs.core.MediaType;
    
    import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
    import org.eclipse.microprofile.openapi.annotations.media.Schema;
    import org.jboss.resteasy.reactive.RestForm;
    import org.jboss.resteasy.reactive.multipart.FileUpload;
    
    @Path("/files")
    @RequestScoped
    public class ExampleResource {
    
        @Schema(type = SchemaType.STRING, format = "binary")
        public static class UploadItemSchema {
    
        }
    
        @POST
        @Consumes(MediaType.MULTIPART_FORM_DATA)
        public void multipart(@RestForm("image") @Schema(implementation = UploadItemSchema.class) FileUpload file) {
    
        }
    
    }