error when using my own input with swagger 2 and spring boot 2 - spring-boot

I'm new to spring boot and trying to set up a swagger documentation for a web app I'm working on. It works quite well when my endpoints only require strings, list or other basic request bodies but whenever I use custom inputs I get this error : could not resolve reference because of could not resolve pointer does not exist in document
the error
I created a small project with just one end point to search more easily. Those are my swagger dependencies :
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'
Here is the swagger config :
#Configuration
#EnableSwagger2
class SwaggerConfig {
#Bean
Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("io.example.bss.testswagger"))
.paths(any())
.build()
.apiInfo(metaInfo())
}
private ApiInfo metaInfo() {
ApiInfo apiInfo = new ApiInfo(
"API",
"A description",
"0.0.1",
"urn:tos",
new Contact("someone", "https://www.google.com",
"an.email#gmail.com"),
"",
"",
new ArrayList<VendorExtension>()
)
return apiInfo
}
}
Then there is the controller :
#Controller
class ControllerHello {
#PostMapping("/")
def hello(#RequestBody Profile profile){
return "hello $profile.name $profile.surname, I see you're $profile.age years old"
}
}
And finally,the DTO :
#ApiModel(value = "the profile containing the name surname and age of a person")
#Document
class Profile {
#ApiModelProperty(notes = "the name of the person", required = true)
String name
#ApiModelProperty(notes = "the surname of the person", required = true)
String surname
#ApiModelProperty(notes = "the age of the person", required = true)
int age
}
In a similar post someone was advised to use alternateTypeRules in Docket, but I'm not sure it would work for my issue and I don't know how to set it up.

It turns out that swagger-ui was having trouble because my Profile class was written in groovy and it was taking the metaclass into account when displaying the model.
The problem was solved by adding this line .ignoredParameterTypes(groovy.lang.MetaClass.class) like so :
#Bean
Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("io.example.bss.testswagger"))
.paths(any())
.build()
.apiInfo(metaInfo())
.ignoredParameterTypes(groovy.lang.MetaClass.class)
}
This is my source: https://github.com/springfox/springfox/issues/752

Related

Remove Controller Name in Swagger UI

Is there any way to hide the controller name in the swagger-ui.
My class is like this. I do not want my controller name on the ui.
#Api(tags = {"group"})
public class MyControllerName {}
I did check some existing answers. for ex: How to remove controller list from Swagger UI did not help at all.
Create Docket bean and assign to it custom ApiInfo object, in this way:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(PathSelectors.ant("/foos/*"))
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfo(
"My REST API",
"Some custom description of API.",
"API TOS",
"Terms of service",
new Contact("John Doe", "www.example.com", "myeaddress#company.com"),
"License of API", "API license URL", Collections.emptyList());
}
You can exclude any controller:
import { Controller, Get } from '#nestjs/common';
import { ApiExcludeController } from '#nestjs/swagger';
import { AppService } from './app.service';
#ApiExcludeController()
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return this.appService.getHello();
}
}
This sould be good feature request to springfox team. If you need to customize the swagger-UI you should be doing it on your own.
Maybe the below steps are useful for someone.
Go to https://github.com/swagger-api/swagger-ui
Download the latest code
customize whatever you want to customize
package either as a web jar or as resources in your application
create resource handler mapping if required
Refer - https://github.com/springfox/springfox/issues/1108

Post Validation didn't work with Spring-Boot Swagger RestAPI

I'm currently working on a Cross Platform App to record the expenses and the incomes. So far so good. However, when I want to add a Backend Post Validation (so nobody can save incomes like €19,99999 etc.) the Post Validation does not work. It would be great if somebody could help me. Here are the details:
I am adding the springfox-bean-validation to my Backend
Entity
#ApiModel(
value = "Expense",
description =
"Information of all expenses with cost, expense date as well as the purpose to which these expenses went"
)
#Entity
#Table(name = "Expenses")
data class Expense(
#ApiModelProperty(notes = "Cost of the Expense in €")
#DecimalMin(value = "0.0", inclusive = false, message = "Price have to be higher than 0.0")
#Digits(integer = Int.MAX_VALUE, fraction = 2, message = "Price have only 2 decimal places")
var cost: BigDecimal,
#ApiModelProperty(notes = "Creation date of the Expense")
#NotNull(message = "Date may not be Null")
var date: LocalDate,
#ApiModelProperty(notes = "Purpose of the expense")
var purpose: String?
) : DbEntity() {
constructor() : this(BigDecimal.ZERO, LocalDate.now(), "")
}
Controller
#CrossOrigin
#RestController
#Api(value = "/finance", description = "FinanceController", produces = "application/json")
#RequestMapping("/api/finance")
class FinanceController(
private val expenseRepository: ExpenseRepository
) {
#ApiOperation(value = "Create an Expense", response = Expense::class)
#PostMapping("/expanse")
fun createExpense(#RequestBody expenseInfo: Expense): Expense {
return expenseRepository.save(expenseInfo)
}
}
SpringFoxConfig
#Configuration
#EnableSwagger2
#Import(BeanValidatorPluginsConfiguration::class)
class SwaggerConfiguration {
#Bean
fun api(): Docket = Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
}
And that is the resulting Output from the swagger UI. Why are there no annotations like #NotNull or #Digits(...)?
When I test a Post request, I get 200 as HTTP Response Code and no Error!
{
"cost": 19.99999999,
"date": "2020-06-06",
"purpose": "test",
"id": 15
}

Mandatory header for all API in openapi 3.0

I am using OpenAPI 3.0 with Spring-boot 5 and therefore have no configuration YAML. I have a header that contains the client Identification ID(This is not an authentication header). I want to make that a mandatory header param. Added below OpenAPI configuration
#Configuration
public class OpenAPIConfiguration {
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components()
.addParameters("myCustomHeader", new Parameter().in("header").schema(new StringSchema()).required(true).description("myCustomHeader").name("myCustomHeader")))
.info(new Info()
.title("My Rest Application")
.version("1.2.26"));
}
}
However, the swagger UI does not show the required param in any API. Can someone help as to what I am doing wrong?
Adding parameter definition to a custom OpenAPI bean will not work because the parameter won't get propagated to the operations definitions. You can achieve your goal using OperationCustomizer:
#Bean
public OperationCustomizer customize() {
return (operation, handlerMethod) -> operation.addParametersItem(
new Parameter()
.in("header")
.required(true)
.description("myCustomHeader")
.name("myCustomHeader"));
}
The OperationCustomizer interface was introduced in the springdoc-openapi 1.2.22. In previous versions you would need to use OpenApiCustomiser:
#Component
public class MyOpenApiCustomizer implements OpenApiCustomiser {
private static final List<Function<PathItem, Operation>> OPERATION_GETTERS = Arrays.asList(
PathItem::getGet, PathItem::getPost, PathItem::getDelete, PathItem::getHead,
PathItem::getOptions, PathItem::getPatch, PathItem::getPut);
private Stream<Operation> getOperations(PathItem pathItem) {
return OPERATION_GETTERS.stream()
.map(getter -> getter.apply(pathItem))
.filter(Objects::nonNull);
}
#Override
public void customise(OpenAPI openApi) {
openApi.getPaths().values().stream()
.flatMap(this::getOperations)
.forEach(this::customize);
}
private void customize(Operation operation) {
operation.addParametersItem(
new Parameter()
.in("header")
.required(true)
.description("myCustomHeader")
.name("myCustomHeader"));
}
}

Swagger shows Mongo ObjectId as complex JSON instead of String

Project setup
I have a Kotlin Spring Boot 2.0 project that exposes a #RestController API that returns MongoDB models. For example, this model and controller:
#RestController
#RequestMapping("/api/accounts")
class AccountsController() {
#GetMapping
fun list(): List<Account> {
return listOf(Account(ObjectId(), "Account 1"), Account(ObjectId(), "Account 2"), Account(ObjectId(), "Account 3"))
}
}
#Document
data class Account(
#Id val id: ObjectId? = null,
val name: String
)
These models have ObjectId identifiers, but in the API I want them to be treated as plain String (i.e. instead of a complex JSON, the default behaviour).
To achieve this, I created these components to configure Spring Boot parameter binding and JSON parsing:
#JsonComponent
class ObjectIdJsonSerializer : JsonSerializer<ObjectId>() {
override fun serialize(value: ObjectId?, gen: JsonGenerator?, serializers: SerializerProvider?) {
if (value == null || gen == null) return
gen.writeString(value.toHexString())
}
}
#JsonComponent
class ObjectIdJsonDeserializer : JsonDeserializer<ObjectId>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): ObjectId? {
if (p == null) return null
val text = p.getCodec().readTree<TextNode>(p).textValue()
return ObjectId(text)
}
}
#Component
class StringToObjectIdConverter : Converter<String, ObjectId> {
override fun convert(source: String): ObjectId? {
return ObjectId(source)
}
}
So far this works as intended, calls to the API return this JSON:
[
{
"id": "5da454f4307b0a8b30838839",
"name": "Account 1"
},
{
"id": "5da454f4307b0a8b3083883a",
"name": "Account 2"
},
{
"id": "5da454f4307b0a8b3083883b",
"name": "Account 3"
}
]
Issue
The problem comes when integrating Swagger into the project, the documentation shows that calling this method returns a complex JSON instead of a plain String as the id property:
Adding #ApiModelProperty(dataType = "string") to the id field made no difference, and I can't find a way to solve it without changing all the id fields in the project to String. Any help would be appreciated.
I couldn't get #ApiModelProperty(dataType = "") to work, but I found a more convenient way configuring a direct substitute in the Swagger configuration using directModelSubstitute method of the Docket instance in this response.
#Configuration
#EnableSwagger2
class SwaggerConfig() {
#Bean
fun api(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.directModelSubstitute(ObjectId::class.java, String::class.java)
}
}
Java equivalent:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.directModelSubstitute(ObjectId.class, String.class);
}
For OpenApi (Swagger 3.0) and SpringDoc the following global configuration could be used.
static {
SpringDocUtils.getConfig().replaceWithSchema(ObjectId.class, new StringSchema());
}

Spring Boot with Swagger 2

I'm trying to use #SwaggerDefinition in my sample Spring Boot REST API. I'm using springfox-swagger2 version 2.6.1
I have annotated my Product model with #ApiModelProperty and the endpoints with #ApiOperation. I'm able to view the end point and model documentations.
But the #SwaggerDefinition are not getting generated.
I have created the following SwaggerConfig:
#Configuration
#EnableSwagger2
#SwaggerDefinition(
info = #io.swagger.annotations.Info(
description = "Spring Boot Product Store API",
version = "V1.2.3",
title = "API of a Spring Boot product store",
termsOfService = "share and care",
contact = #io.swagger.annotations.Contact(name = "John Pack",
email = "john#productsample.com ", url =
"http://productsample.com"),
license = #io.swagger.annotations.License(name = "Apache 2.0",
url = "http://www.apache.org")
)
)
public class SwaggerConfig {
#Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("pkg.controllers"))
.paths(regex("/product.*"))
.build();
}
}
What else needs to be done for #SwaggerDefinition documentations? Thanks in advance

Resources