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)))
Related
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.
I'm setting up a P.O.C. using Neo4j, and technically have everything I need working but would like it set up properly.
As a quick overview - I can create nodes and relationships, and traverse the graph (i.e. return all features available in a specific market) so I know these nodes/relationships have been created.
However, when I query to simply return a Node based on ID, it returns ONLY the data for that node - and not any relationships or connected nodes, for example, the markets its available in.
I've looked various places online that have not only a Node returned but also the subsequent nodes - though I follow what they're doing I cant seem to get it to work with mine.
Feature Repository:
#Repository
public interface FeatureRepository<T extends Feature> extends Neo4jRepository<T, Long> {
...
}
Colour Repository:
#Repository
public interface ColourRepository extends FeatureRepository<Colour>{
#Query("CREATE(feat:Colour:Feature {marketingDesc:{marketing}, engineeringDesc:{engineering}, code:{code}})")
Colour createColour(#Param("marketing") String marketingDesc, #Param("engineering") String engineeringDesc, #Param("code") String code);
#Query("MATCH (c:Colour {code:{colourCode}}) MATCH (c)-[:AVAILABLE_IN]->(market) RETURN c AS colour, COLLECT(market) AS markets")
Colour getColourByCode(#Param("colourCode") String colourCode);
Colour findByCode(#Param("code") String code);
}
Feature Entity:
#NodeEntity(label = "Feature")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Feature {
#Id
#GeneratedValue
private Long id;
private String marketingDesc;
private String engineeringDesc;
#Index(unique = true)
private String code;
#Relationship(type = "HAS_OPTION", direction = Relationship.INCOMING)
private List<Option> options = new ArrayList<>();
#Relationship(type = "AVAILABLE_IN")
private List<Market> markets = new ArrayList<>();
#Relationship(type = "HAS_PREREQUISITE", direction = Relationship.UNDIRECTED)
private List<Prerequisite> prerequisites = new ArrayList<>();
}
Colour Entity:
#AllArgsConstructor
#NodeEntity(label = "Colour")
public class Colour extends Feature {
}
Market Entity:
#NodeEntity(label = "Market")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Market {
#Id
#GeneratedValue
private Long id;
#Index(unique = true)
private String code;
private String market;
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
private List<Option> features = new ArrayList<>();
}
Relationship Entity (for features to be connected to markets they can be bought in):
#RelationshipEntity(type = "AVAILABLE_IN")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Available {
#Id
#GeneratedValue
private Long Id;
private List<String> availableIn = new ArrayList<>();
#StartNode
private Feature feature;
#EndNode
private Market market;
}
Controller:
#RestController
public class ConfigController {
private final Handler configHandler;
public ConfigController(Handler configHandler) {
this.configHandler = configHandler;
}
#PostMapping(path = "/create/colour", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createColour(#RequestBody Colour request) {
ColourService service = new ColourService(configHandler);
Colour created = service.createColour(request);
return SimpleResponse.builder().result("Created:", created).build();
}
#PostMapping(path = "/create/market", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createMarket(#RequestBody Market request) {
MarketService service = new MarketService(configHandler);
Market created = service.createMarket(request);
return SimpleResponse.builder().result("Created", created).build();
}
#PostMapping(path = "/create/relationship/availableIn", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public SimpleResponse createAvailableInRelationship(#RequestBody OptionAvailInRequest request){
RelationshipService service = new RelationshipService(configHandler);
Object result = service.createAvailableInRelationship(request);
return SimpleResponse.builder().result("Result:", result).build();
}
#GetMapping(path = "/colour/{code}")
public SimpleResponse getColourByCode(#PathVariable(value = "code") String code) {
ColourService service = new ColourService(configHandler);
Colour colour = service.getColourByCode(code);
return SimpleResponse.builder().result("Colour:", colour).build();
}
#GetMapping(path = "/features/available/{mrktCode}")
public SimpleResponse getFeaturesInMarket(#PathVariable(value = "mrktCode") String mrktCode){
RelationshipService service = new RelationshipService(configHandler);
Collection<Feature> features = service.getFeaturesInMarket(mrktCode);
return SimpleResponse.builder().result("Features:", features).build();
}
}
Neo4jConfig file:
#Configuration
#EnableNeo4jRepositories(basePackages = "package.location")
#EnableTransactionManagement
public class Neo4jConfig {
#Bean
public org.neo4j.ogm.config.Configuration configuration() {
org.neo4j.ogm.config.Configuration configuration =
new org.neo4j.ogm.config.Configuration.Builder().build();
return configuration;
}
#Bean
public SessionFactory sessionFactory(org.neo4j.ogm.config.Configuration configuration) {
return new SessionFactory(configuration,"package.location");
}
#Bean
public Neo4jTransactionManager transactionManager(SessionFactory sessionFactory) {
return new Neo4jTransactionManager(sessionFactory);
}
}
So, for example, here I can create a Colour Node:
Example value:
{
"code": "string",
"engineeringDesc": "string",
"id": 0,
"marketingDesc": "string",
"markets": [
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
],
"options": [
{}
],
"prerequisites": [
{}
]
}
What I send:
{
"code": "BLU",
"engineeringDesc": "Blue engineering",
"marketingDesc": "Blue marketing"
}
And this creates a Colour Node successfully:
{
"result": {
"Created:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
I can create a Market Node:
Example Value:
{
"code": "string",
"features": [
{}
],
"id": 0,
"market": "string"
}
What I send:
{
"code": "UB",
"market": "England"
}
Which creates a Market Node successfully:
{
"result": {
"Created": {
"id": 1,
"code": "UB",
"market": "England",
"features": []
}
},
"error": null
}
I can then create a relationship between the two, to say that colour is available in that market:
{
"featureCode": "BLU",
"marketCode": "UB"
}
Which I can verify has been created by hitting:
localhost:8080/features/available/UB
{
"result": {
"Features:": [
{
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
]
},
"error": null
}
However when I then go to return the Colour Node itself:
localhost:8080/colour/BLU
{
"result": {
"Colour:": {
"id": 0,
"marketingDesc": "Blue marketing",
"engineeringDesc": "Blue engineering",
"code": "BLU",
"options": [],
"markets": [],
"prerequisites": []
}
},
"error": null
}
The 'markets' option is always null. I have tried custom queries and building queries using the neo4j helper (e.g. findByCode etc.), and every example I can find will sucessfully return the related nodes, but I cant seem to get mine to.
Can anyone help?
P.S. Please let me know if there is anything else that would be helpful for you to see. Been trying to get this sorted for days....
Got the answer to this question...
Feature Entity should have been:
#Relationship(type = "AVAILABLE_IN")
#ApiModelProperty(hidden = true)
private Set<Available> markets = new HashSet<>();
Market Entity should have been:
#Relationship(type = "AVAILABLE_IN", direction = Relationship.INCOMING)
#ApiModelProperty(hidden = true)
private Set<Available> features = new HashSet<>();
Which gets the markets section of the feature JSON no longer null...
Now I have the problem that there's an infinite recursion loop between the two classes, with a feature displaying the markets and the markets displaying the features
EDIT:
For anyone else with this/similar issues, I've found a really good github resource.
GitHub neo4j ogm walkthrough
Helped a lot.
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")
I am using Swagger Core 2.0.2 for Java to generate an OpenAPI documentation. Among others, I have the following class SomeDTO:
#Schema(name = "SomeDTO", description = "some description")
public class SomeDTO {
#Schema(description = "description of name")
private String name;
#Schema(required = true, description = "description of OtherDTO")
private OtherDTO otherDTO;
}
OtherDTO is described as follows:
public class OtherDTO {
#Schema(required = true)
private String someField;
private String someOtherField;
}
My problem is that neither the description nor the required field above the otherDTO field has any effect.
The resulting openapi.json looks like this:
"components": {
"schemas": {
"SomeDTO" : {
"type": "object",
"properties": {
"name": {
"type" : "string"
}
"otherDTO" : {
"$ref": "#/components/schemas/OtherDTO"
}
},
"description": "some description"
},
"OtherDTO": {
"required": ["someField"],
"type": "object",
"properties": {
"somefield": {
"type": "string"
},
"someOtherField": {
"type": "string"
}
}
}
}
}
I was expecting the SomeDTO schema to have a required array containing OtherDTO, which it does not. The description is also lost.
I have tried numerous combinations of Schema settings, to no avail. I would highly appreciate any help to understand what I am doing wrong.
Thanks in advance.
I have found a solution to part of my problem.
The problem is caused by the fact that when using a $ref element, sibling elements are ignored. So elements (description, name etc.) related to the referenced element need to be specified as a #Schema in the referenced object itself (OtherDTO in the example above). Specifying these elements in the parent object (e.g. SomeDTO) will leave them ignored.
However, the schema elements in the referenced element do not seem to propagate up to the parent object. So, to make otherDTO a required field in SomeDTO, I need to add requiredProperties = { "OtherDTO" }) to SomeDTO's schema.
Here is the updated code:
SomeDTO
#Schema(name = "SomeDTO", description = "some description",
requiredProperties = { "OtherDTO" })
public class SomeDTO {
#Schema(description = "description of name")
private String name;
private OtherDTO otherDTO;
}
OtherDTO
#Schema(name = "OtherDTO", description = "Description of OtherDTO")
public class OtherDTO {
#Schema(required = true)
private String someField;
private String someOtherField;
}
However, it does not solve my problem completely, as I still can't figure out how to set the description of otherDTO in SomeDTO. But it gets me a step closer.
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;
}
}