How to view a FileUpload response body as an upload button when seen through Swagger UI - 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.
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

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

Related

OpenAPI, contract-first with Springdoc and multiple specs

I am trying to port a Swagger UI from Springfox to Springdoc.
The Swagger UI is generated via Maven plugin
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
... from two different OpenAPI specifications, both implemented in the same Spring Boot application. We use two different specs for two different clients.
So far, my Swagger UI looked like this with Springfox. Note the drop-down list that allows to choose the spec to display.
Now, with Springdoc 1.6.6, the Swagger-UI looks like this.
Note that:
The default landing page is the sample Pet Store and none of my specifications, although I specified in my Spring Boot's application.yaml:
springdoc:
swagger-ui:
disable-swagger-default-url: true
No drop-down
I have to manually enter my specification's name into the "Explore" text field in order to see its Swagger UI
I tried addressing the missing drop-down by following I have installed OpenAPI 3 using springdoc, but the URL is strange. Can I change it to the expected value?, which claims to display a drop-down, but to no avail.
My questions:
How do I display the drop-down? Does Springdoc support it at all out-of-the-box?
Apparently, accessing http://localhost:8080/swagger-ui/index.html?urls.primaryName=Information makes the drop-down list of specs magically appear.
How do I make sure that the default URL http://localhost:8080/swagger-ui/index.html lands on one of my specs, and not on the Pet Store?
Here is btw. my Bean configuration:
#Configuration
public class SwaggerDocumentationConfig {
/**
* Path of the OpenAPI package extracted out of a random class in that package
*/
private static final String INFORMATION_PACKAGE = InformationApi.class.getPackageName();
/**
* Path of the OpenAPI package extracted out of a random class in that package
*/
private static final String OPERATIONS_PACKAGE = OperationsApi.class.getPackageName();
private GroupedOpenApi getBaseApiDoc(String groupName, String packagePath) {
return GroupedOpenApi.builder()
.group(groupName)
.packagesToScan(packagePath)
.build();
}
#Bean
public GroupedOpenApi getOperationsApiDoc() {
return getBaseApiDoc("Operations", OPERATIONS_PACKAGE);
}
#Bean
public GroupedOpenApi getInfoApiDoc() {
return getBaseApiDoc("Information", INFORMATION_PACKAGE);
}
#Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.info(new Info().title("SpringShop API")
.description("Spring shop sample application")
.version("v0.0.1")
.license(new License().name("Apache 2.0").url("http://springdoc.org")))
.externalDocs(new ExternalDocumentation()
.description("SpringShop Wiki Documentation")
.url("https://springshop.wiki.github.org/docs"));
}}

Using openapi.yaml in springdoc

I can customize OpenAPI from code.
Can I do same over openapi.yaml like swagger-petstore
I create simple springboot project with one #RestController.
I create openapi.yaml and copy it to /src/main/resources/.
But I see default values on open swagger-ui page.
This is available from the FAQ page in the spring-doc documentation.
See What is a proper way to set up Swagger UI to use provided spec.yml? and How can use custom json/yml file instead of generated one ? of the same page.
Example from the FAQ page
Turn off auto-generation in the project property file springdoc.api-docs.enabled=false
Put your yaml file in src/main/resources/static such as src/main/resources/static/myApiFile.yaml
Set the swagger-ui url for the file springdoc.swagger-ui.url=/myApiFile.yaml
Enable the minimal beans configuration
import org.springdoc.core.SpringDocConfigProperties;
import org.springdoc.core.SpringDocConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class SpringDocsConfiguration {
#Bean
SpringDocConfiguration springDocConfiguration() {
return new SpringDocConfiguration();
}
#Bean
public SpringDocConfigProperties springDocConfigProperties() {
return new SpringDocConfigProperties();
}
}
The code below is all what we needed to do to use openapi.yaml specification file instead of the default one that is generated from code.
Explanation:
org.springdoc.webflux.api.OpenApiResource is Controller that handles /v3/api-docs and /v3/api-docs.yaml endpoints. Swagger UI is using that endpoint to show swagger ui page - /swagger-ui.html. You can see the configuration when you hit /v3/api-docs/swagger-config endpoint.
org.springdoc.webflux.api.OpenApiResource bean is registered only if missing. You can see it in SpringDocWebFluxConfiguration. The method that creates the bean is annotated with #ConditionalOnMissingBean. So you just need to extend it and adjust OpenApi specification retrieval (see below).
org.springdoc.webflux.api.OpenApiResource is using getOpenApi() method to retrieve OpenAPI specification (by default the specification is generated based on the class annotation from code). So you just need to override getOpenApi() method and provide the specification from yaml file itself (getYamlMapper() method is also provided for you in the parent classes, so it's really that easy how it is in the file below)
You can see OpenApiResource is in webflux package because we use org.springdoc:springdoc-openapi-webflux-ui, Spring WebFlux. It is done similarly in Spring MVC.
Hope this helps :) When you hit /swagger-ui.html you should see the docs directly from .yaml spec. When you hit /v3/api-docs you should see the specs itself in JSON. When you hit /v3/api-docs.yaml you should see the specs itself in YAML. No Spring Configuration code is needed. Just the controller as you see below :)
Just to be clear. Our OpenAPI spec is in src/main/resources/openapi/api.yaml
package com.your.package;
...imports omitted for readability...
import org.springdoc.webflux.api.OpenApiResource;
#RestController
public class OpenApiController extends OpenApiResource {
#Value("classpath:openapi/api.yaml")
private Resource openAPIResource;
private OpenAPI openAPI;
public OpenApiController(ObjectFactory<OpenAPIBuilder> openAPIBuilderObjectFactory, AbstractRequestBuilder requestBuilder, GenericResponseBuilder responseBuilder, OperationBuilder operationParser, RequestMappingInfoHandlerMapping requestMappingHandlerMapping, Optional<List<OperationCustomizer>> operationCustomizers, Optional<List<OpenApiCustomiser>> openApiCustomisers, SpringDocConfigProperties springDocConfigProperties, Optional<ActuatorProvider> actuatorProvider) {
super(openAPIBuilderObjectFactory, requestBuilder, responseBuilder, operationParser, requestMappingHandlerMapping, operationCustomizers, openApiCustomisers, springDocConfigProperties, actuatorProvider);
}
#SneakyThrows
#PostConstruct
public void initOpenAPI() {
openAPI = getYamlMapper().readValue(openAPIResource.getInputStream(), OpenAPI.class);
}
#Override
protected synchronized OpenAPI getOpenApi() {
return openAPI;
}
}
If you need configuration file, you can have a look at the FAQ, documentation:
https://springdoc.org/faq.html#can-i-use-spring-property-with-swagger-annotations
And here is the link for code samples:
https://raw.githubusercontent.com/springdoc/springdoc-openapi/master/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app15/SpringDocApp15Test.java

How to tell OpenAPI to use context-root of application in it's URL

I am running two different Payara Micro microservices in one cluster.
The issue I have is that when I try to access the OpenAPI URL of MyApp1 like http://mylink.com/myApp1/openapi it does not work. It actually works when I use URL http://mylink.com/openapi.
This becomes an issue when I want to see the API for the other microservice like http://mylink.com/myApp2/openapi which does not work.
Is there a way in Payara Micro of telling OpenAPI to use the application's context in it's path just like all the other URL in the application do?
As you can see in my previous comment, I've also struggled with the same situation.
Context - openapi and microprofile
First let me say that having /openapi URL in the root is the intended behaviour of microprofile-open. Documentation always uses /openapi path as the right to get the document LINK
In the implementation, is very clear that this behaviour is both wanted as enforced:
In the ServletContainerInitializer for OpenApi one can see the following code
// Only deploy to app root
if (!"".equals(ctx.getContextPath())) {
return;
}
Workaround aka Solution.
Now that is clear that we cannot configured this, since it's intended behaviour, one solution ( the one I'm proposing ) is to proxy the request to /YOUR_APP/openapi to /openapi.
Since my application is a jax-rs one, deployed on openshift, and I don't want to have a dedicated proxy application for this, I've just created a simple Resource/Controller to proxy this specific request for me.
The outstanding method behind:
#GET
#Path("")
public Response proxyOpenApiCall(){
log.debug("proxyOpenApiCall called");
String entity = client.target("http://localhost:8080")
.path("openapi").request()
.get(String.class);
return Response.ok(entity).build();
}
I was able to fix this with a small forward proxy. Therefore I create a new REST enpoint wich is callable from public and returns the content of internal http endpoint.
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
#RequestScoped
#ApplicationPath("/")
#Path("/")
public class OpenApiProxyRestFacade extends Application {
private Client client;
#PostConstruct
public void init() {
this.client = ClientBuilder.newClient();
}
#GET
#Path("/openapi")
#Produces(MediaType.APPLICATION_JSON)
public Response proxyOpenApiCall() {
String entity = client.target("http://localhost:9080").path("openapi").request().get(String.class);
return Response.ok(entity).build();
}
#GET
#Path("/openapi/ui")
#Produces(MediaType.APPLICATION_JSON)
public Response proxyOpenApiUiCall() {
String entity = client.target("http://localhost:9080/openapi").path("ui").request().get(String.class);
return Response.ok(entity).build();
}
#PreDestroy
public void destroy() {
this.client.close();
}
}
For openapi, you can set this property for change of url, so it is configurable after all
mp.openapi.extensions.path=/yourapi/whatever
and for the openapi-UI set this
openapi.ui.yamlUrl=/yourapi/whatever
Sources: I first googled for mp.openapi.xxx parameters, (I found them in source code) which led me to this url
https://download.eclipse.org/microprofile/microprofile-open-api-1.0/microprofile-openapi-spec.html
and after looking for more stuff there was one simple sentence mentioning that there is also mp.openapi.extensions and after googling those further I found this random doc here https://github.com/wildfly/wildfly/blob/main/docs/src/main/asciidoc/_admin-guide/subsystem-configuration/MicroProfile_OpenAPI.adoc

Using Swagger Annotations to generate XML namespace information

My XML request looks like this:
<ns1: MyRequest ns1="http://example.com/api/2.0">:
<reference>A request to my endpoint</reference>
</ns1:MyRequest>
I believe it should be modeled in Swagger/OpenAPI 3 spec as follows:
MyRequest:
type: object
properties:
reference:
type: string
xml:
name: reference
xml:
prefix: ns1
namespace: https://example.com/api/2.0
Is it possible to annotate my MyRequest.java class appropriately using JAXB or Swagger annotations to get the prefix and namespace entries in the generated OpenAPI spec?
I tried setting the namespace attribute on the #XmlRootElement as below, but the namespace is not appearing in the generated OpenAPI spec. Further, I can't use that the XmlRootElement annotation to specify the prefix.
#XmlRootElement(namespace = "https://example.com/api/2.0")
public class MyRequest{
//omitted
}

Spring data rest shows id as path variable in swagger for POST method in mongo repository?

I am just using MongoRepository. Below is my entity class:
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "product")
#Data
#ToString
#EqualsAndHashCode
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Product {
#Id private String id;
#Indexed(unique = true)
private String name;
private boolean displayAds;
}
When I view the resource in Swagger the POST method appears as follows:
This is my repository:
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ProductRepository extends MongoRepository<Product, String> {
}
Why does this happen? Is there a fix? I'm using springfox-swagger2 and springfox-swagger-ui.
Anyway it can post via Postman without adding such path variable.
Updating to 2.8.0 should resolve the issue.
This seems to have been a bug in Springfox version 2.7.0. Should be fixed in 2.8.0 as highlighted here.

Resources