I was generating Swagger API specification from Spring Boot REST controllers using Springfox.
I noticed an issue where the example value/model could not be shown for response.
As an investigation, I checked the JSON API doc at http://localhost:8080/v2/api-docs ,
and converted it to YMAL at https://editor.swagger.io/ , which it could not show the example value/model as well.
This seems to caused by the schema is not referring to the model object ("Car" here) correctly.
But from the API documentation of Swagger (https://docs.swagger.io/swagger-core/v1.5.0/apidocs/io/swagger/annotations/ApiResponse.html#response()), it says that the "response" attribute of the annotation #ApiResponse should correspond to the "schema" field of the specification.
By specifying response = "Object.class", shouldn't the Swagger UI populate the example value/model accordingly?
Welcome for any advice, and please kindly correct if I have any misconfigurations/misconceptions, thank you very much.
REST controller and the annotations:
#GetMapping(path = "/car")
#ApiOperation(value = "Get car by color.", response = Car.class)
#ApiParam(value = "Color of the car.", required = true)
#ApiResponses(value = { #ApiResponse(code = 200, message = "OK.", response = Car.class),
#ApiResponse(code = 400, message = "Invalid color provided."),
#ApiResponse(code = 404, message = "Car not found.") })
public ResponseEntity<Object> getCarByColor(#RequestParam String color) {
return ResponseEntity.ok(testService.getCarByColor(color));
}
Model:
package com.example.demo.model;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
#ApiModel(value = "Car", description = "The model for car")
#Data
public class Car {
#ApiModelProperty(notes = "Car ID.", example = "12345", required = false, position = 0)
private Long id;
#ApiModelProperty(notes = "Car name.", example = "Suzuki Swift 2020", required = true, position = 1)
#NotNull
#Max(value = 30, message = "Name can only have a maximum length of 30")
private String name;
#ApiModelProperty(notes = "Car color.", example = "blue", required = true, position = 2)
#NotNull
#Max(value = 30, message = "Color can only have a maximum length of 30")
#Pattern(regexp = "^(blue|yellow)$", message = "Only blue or yellow color is allowed.")
private String color;
public Car(Long id, String name, String color) {
this.id = id;
this.name = name;
this.color = color;
}
}
Swagger UI:
Springfox dependency in pom.xml:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<UPDATE (31 Jul 2020)>
Made the following changes to use OAS3.0 specification and annotations, but still have issue. It also gives an error in Swagger UI.
REST controller and the annotations:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
...
......
#GetMapping(path = "/car", produces = "application/json")
#Operation(summary = "Get car by color.", responses = {
#ApiResponse(responseCode = "200", description = "OK.", content = {
#Content(mediaType = "application/json", schema = #Schema(type = "object", implementation = Car.class)) }) })
public ResponseEntity<Object> getCarByColor(#RequestParam String color) {
return ResponseEntity.ok(testService.getCarByColor(color));
}
Model:
package com.example.demo.model;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
#ApiModel(value = "Car", description = "The model for car")
#Schema
#Data
public class Car {
#ApiModelProperty(notes = "Car ID.", example = "12345", required = false, position = 0)
private Long id;
#ApiModelProperty(notes = "Car name.", example = "Suzuki Swift 2020", required = true, position = 1)
#NotNull
#Max(value = 30, message = "Name can only have a maximum length of 30")
private String name;
#ApiModelProperty(notes = "Car color.", example = "blue", required = true, position = 2)
#NotNull
#Max(value = 30, message = "Color can only have a maximum length of 30")
#Pattern(regexp = "^(blue|yellow)$", message = "Only blue or yellow color is allowed.")
private String color;
public Car(Long id, String name, String color) {
this.id = id;
this.name = name;
this.color = color;
}
}
Swagger UI:
You can override V3 models with V2 models. Just add a property in your application.properties and your #ApiResponse annotation should work properly.
springfox.documentation.swagger.use-model-v3=false
Make sure to use older #ApiResponses and #ApiResponse annotations.
This issue has been documented an https://github.com/springfox/springfox/issues/3503
In the case of using the springdoc-openapi-ui (>=1.5.0) here is my working sample to show example data in the request and response sections of SwaggerUI if it's the JSON object.
Hopefully, it would fit your case with small changes too.
#Operation(summary = "Send some JSON")
#ApiResponses(value = {
#ApiResponse(
responseCode = "200",
description = "Success: our action has been completed",
content = #Content(mediaType = "application/json",
schema = #Schema(
type = "SampleHttpResponseDto",
example = "{\"status\":\"OK\",\"message\":\"sample OK answer\"}")))})
#PostMapping(value = "/resource", consumes = MediaType.APPLICATION_JSON_VALUE)
public SampleHttpResponseDto postRequest(
#Parameter(
name ="json",
schema = #Schema(
description = "additional description of the model",
type = "string",
example = "{\"status\":\"OK\",\"message\":\"message body\"}"))
#RequestBody Map<String, Object> request
) {
return new SampleHttpResponseDto(request.propert1, request.propert2);
}
Gist: https://gist.github.com/antukhov/7dece86c6d16cc81bb6f83f47ffc0c8d
SwaggerUI will look like this
Using swagger-2 annotation worked for me by adding produces='application/json' to #GetMapping or any #RequestMapping.
Related
I would like to add an example with Swagger in my method, I have tried a few things, but they didn't work.
I have my Interface, where I define the method:
#Api(value = "test API")
#RequestMapping("/api/v1/product")
public interface TestController {
#ApiOperation(
value = "Service that return a Product",
notes = "This service returns a Product by the ID",
nickname = "getProductById",
response = ProductResponse.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "The request has succeeded.", response = ProductResponse.class),
#ApiResponse(code = 500, message = "Internal server error.", response = ProductResponse.class) })
#GetMapping(
value = "/productById",
produces = { "application/json" }
)
ResponseEntity<ProductResponse> getProductById(#RequestParam(value = "productId", required = true) String productId);
The ProductResponse class is the following:
#Getter
#Setter
#AllArgsConstructor
public class ProductResponse {
private Product product;
private CustomException customException;
}
The Product class is the following:
#Getter
#Setter
#AllArgsConstructor
public class Product {
#JsonProperty("id")
private String id;
#JsonProperty("productName")
private String productName;
#JsonProperty("productDescription")
private String productDescription;
#JsonProperty("unitPrice")
private Double unitPrice;
And the CustomException class is the following:
#Getter
public class CustomException {
private final String message;
private final String errorCode;
private final String errorType;
private final Exception exceptionDetail;
public CustomException(String message, String errorCode, String errorType, Exception exceptionDetail) {
this.message = message;
this.errorCode = errorCode;
this.errorType = errorType;
this.exceptionDetail = exceptionDetail;
}
When the response is 200, the response is like:
{
"product": {
"id": "12345",
"productName": "Product name",
"productDescription": "This is a description",
"unitPrice": 3.25
},
"customException": null
}
But when the response is 500, the response is like:
{
"product": "null,",
"customException": {
"message": "/ by zero",
"errorCode": "500",
"errorType": "Internal server error",
"exceptionDetail": null,
"cause": null,
"stackTrace": [
{
"classLoaderName": "app",
"moduleName": null,
"moduleVersion": null,
"methodName": "getProductById",
"fileName": "TestControllerImpl.java",
"lineNumber": 33,
"className": "com.myproject.testmicroservice.controller.impl.TestControllerImpl",
"nativeMethod": false
}
]
}
}
How can I add a custom example in the #ApiResponse annotation?
You are probably missing the #Operation annotation, where inside you put the #ApiResponse.
Example:
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
#Operation(responses = {
#ApiResponse(responseCode = "200", content = #Content(examples = {
#ExampleObject(name = "getUserAttribute",
summary = "Retrieves a User's attributes.",
description = "Retrieves a User's attributes.",
value = "[{\"value\": [\"area1\", \"area2\", \"area3\"], \"key\":\"GENERAL_AREAS\"}, {\"value\":\"933933933\", \"key\":\"FONyE\"}]")
}, mediaType = MediaType.APPLICATION_JSON_VALUE))})
public ResponseEntity<List<UserPreferenceDto>> getUserPreferenceByCode(
#Pattern(regexp = "\\w+") #PathVariable String userCode, #Parameter(hidden = true) Pageable pageable) {
...
}
Good evening hope you are doing well. In the case you are describing, I would do something like this
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Found the book",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = Book.class)) }),
#ApiResponse(responseCode = "400", description = "Invalid id supplied",
content = #Content),
the approach described is explained here. I think that paragraph 9. Generate Documentation Using #Operation and #ApiResponses is of particular interest in your case. I hope this helps, Have a good night
You can try something like this. In your controller you already have #ApiResponses annotation. What you need to do is add #ApiModel to your Product class and then add
#ApiModelProperty(notes = "Your comments", required = true, example = "example value")
to members of your Product class i.e. ProductResponse and CustomException. One thing that you will need to verify is whether #ApiModelProperty can be set on custom objects like ProductResponse and CustomException. If not you will need to set #ApiModelProperty 1 level deep.
As shown in article, the examples are auto populated from model property to response.
PS: As of now, I do not have setup of a swagger project so can only help you theoretically.
may be this late answer but incase any one need it, you can add the requestBody description along with content type within #Operation
#io.swagger.v3.oas.annotations.Operation(summary = "", description = "",
requestBody = #io.swagger.v3.oas.annotations.parameters.RequestBody(content = #Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
Is it ok to use annotation for this? I don't understand how to use Yaml.
Example:
#Operation(summary = "Get page of top by rating articles by title")
#ApiResponses(value = {
#ApiResponse(responseCode = "200 successful operation",
content = #Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = #Schema(???)))
})
#GetMapping(value = {"/articles/topByRating"})
#JsonView({JsonViews.Short.class})
#ResponseStatus(HttpStatus.OK)
public ItemsDto<Article> getArticlesTopByRating(#RequestParam int numberPage, #RequestParam int sizePage) {
return articleCrud.getTopByRating(numberPage, sizePage);
}
ItemsDto:
public class ItemsDto<E> {
Long total = 0L;
List<E> items;
}
Article:
public class Article{
private Author author;
private String title;
private String body;
}
https://swagger.io/docs/specification/data-models/dictionaries/
Free-Form Objects
"If the dictionary values can be of any type (aka free-form object), use additionalProperties: true:"
1. type: object
2. additionalProperties: true
I will try to use 'ref = myfile.yaml' property into #Schema annotation
The object are converted automatically to Schema by springdoc-openapi.
Note that on your DTOs, you can add the #Schema annotation as well to customise the different fields.
I have this api called /predict.
The required parameters can sometimes be score1 and score2 or
info.score1 and info.score2 depending on the dataset we get.
Right now how it looks like on my localhost:8080/swagger-ui.html is
Below is how I make it happen:
#ApiOperation(value="", response=RequestInput.class)
#RequestMapping(value="/predict", method= RequestMethod.POST, produces="application/json", consumes="application/json")
public ResponseEntity predict(Map<String, Object> inputs, #RequestBody RequestInput requestInput) {
...
}
RequestInput.class looks like this
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
public class RequestInput {
#ApiModelProperty(notes = "", required = true)
#Getter private Double score1;
#ApiModelProperty(notes = "", required = true)
#Getter private Double score2;
}
I have 2 questions:
Isit possible to have
{
"info.score1":0,
"info.score2":0
}
How do I set the Example Value for the Responses part? Right now its reflecting what's on requestInput. I would like it to be
{
"finalScore":0
}
Regarding your first question: ApiModelProperty has "name" which can be set to display your desired property name.
For second: check io.swagger.annotations.ApiResponses and io.swagger.annotations.ApiResponse. ApiResponse has an example property which also can be set.
About part of your question that states that your api can sometimes be one value and sometimes another: to be honest I do not think that this is possible to get in default swagger behavior.
To handle dynamic attribute values in request, you can add setters setInfoScore1 & setInfoScore2 and mark them with #JsonProperty
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
public class RequestInput {
#ApiModelProperty(notes = "", required = true)
#Getter private Double score1;
#ApiModelProperty(notes = "", required = true)
#Getter private Double score2;
#JsonProperty("info.score1")
public void setInfoScore1(Double score) {
this.score = score;
}
#JsonProperty("info.score2")
public void setInfoScore2(Double score) {
this.score = score;
}
}
I have a #JsonView named BankAccountView.Public who help me to restrict some fields in BankAccount because I don't want to send all their attributes in a public get operation. My issue is when I try to specify it using swagger because if I specify BankAccount.class it shows the entire object instead of all the fields specify in my #JsonView, but if I specify BankAccount.Public.class it show me an empty object. Could you please tell me if it is possible that Swagger shows only the public fields?
Here is my code:
// BankAccount Json View
public class BankAccountView {
public static class Public {}
}
// BankAccount class
#ApiModel("BankAccount")
public class BankAccount {
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private Long accountId;
#ApiModelProperty
private Long owner;
#ApiModelProperty
#NotBlank
#JsonView(BankAccountView.Public.class)
private String currency;
#ApiModelProperty
#NotBlank
#JsonView(BankAccountView.Public.class)
private String bankName;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private BankAccountType accountType;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private BankAccountStatus status;
#ApiModelProperty
private Instant verificationDate;
#ApiModelProperty
#JsonView(BankAccountView.Public.class)
private String mask;
}
// BankAccountController class
#ApiOperation(value = "Fetch a list of all bank accounts")
#JsonView({BankAccountView.Public.class})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Bank accounts successfully retrieved", response = BankAccountView.Public.class, responseContainer = "List"),
#ApiResponse(code = 400, message = "Validation failed", response = ApiHttpClientErrorException.class),
#ApiResponse(code = 403, message = "User is not an employee", response = ResourceForbiddenException.class),
#ApiResponse(code = 404, message = "User not found", response = NoSuchElementException.class),
#ApiResponse(code = 500, message = "Internal server error", response = ApiHttpServerErrorException.class)
})
#GetMapping
public List<BankAccount> getAllBankAccounts() {
return service.getAll();
}
Thanks a lot! :)
If you're using Jackson, you can use #JsonIgnore.
else set hidden true for individual properties
#ApiModelProperty(position = 1, required = true, hidden=true, notes = "used to display user name")
how to define swagger annotation for this example post API.TenantConfiguration is getting as a json payload.
#Consumes({ "application/json", "application/xml" })
#POST
public Message configureSettings(TenantConfiguration configuration)
throws AndroidAgentException {
.....................
}
I found a solution to annotate json consuming Jax-rs Apis.It's working properly.
#POST
#ApiOperation(
consumes = MediaType.APPLICATION_JSON,
httpMethod = "POST",
value = "Configuring Android Platform Settings",
notes = "Configure the Android platform settings using this REST API"
)
#ApiResponses(value = {
#ApiResponse(code = 201, message = "Android platform configuration saved successfully"),
#ApiResponse(code = 500, message = "Internal Server Error")
})
Message configureSettings(#ApiParam(name = "configuration", value = "AndroidPlatformConfiguration")
TenantConfiguration configuration) throws AndroidAgentException;
Mapping class for the JSON object.
#XmlRootElement(
name = "tenantConfiguration"
)
#XmlAccessorType(XmlAccessType.NONE)
#ApiModel(
value = "TenantConfiguration",description = "This class carries all
information related to a Tenant configuration"
)
public class TenantConfiguration implements Serializable {
#XmlElement(
name = "type"
)
#ApiModelProperty(
name = "type",
value = "type of device",
required = true
)
private String type;
#ApiModelProperty(
name = "configuration",
value = "List of Configuration Entries",
required = true
)
#XmlElement(
name = "configuration"
)
private List<ConfigurationEntry> configuration;
public TenantConfiguration() {
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public List<ConfigurationEntry> getConfiguration() {
return this.configuration;
}
public void setConfiguration(List<ConfigurationEntry> configuration) {
this.configuration = configuration;
}
}