How to annotate Play 2 webapp model for swagger? - java

I'm an android/java developer new to Play2 framework. I'm trying to generate documentation to my RESTful API with swagger.
I've managed to include swagger into my Play2 webapp and generate simple api-docs.json. The only part I am missing is model description. I have User controller and User model in /controllers and /models accordingly.
#Api(value = "/user", listingPath = "/api-docs.{format}/user", description = "User registration and authorisation")
public class User extends Controller {
#POST
#ApiOperation(value = "Create user", notes = "Used to register new user.")
#ApiParamsImplicit(#ApiParamImplicit(name = "body", value = "Created user object", required = true, dataType = "User", paramType = "body"))
#BodyParser.Of(BodyParser.Json.class)
public static Result createUser() {
JsonNode json = request().body().asJson();
ObjectNode result = Json.newObject();
JsonNode body = json.findPath("body");
if(body.isMissingNode()) {
result.put("status", "KO");
result.put("message", "Missing parameter [body]");
return badRequest(result);
}
JsonNode name = body.get("name");
if(name == null) {
result.put("status", "KO");
result.put("message", "Missing parameter [body.name]");
return badRequest(result);
}
result.put("status", "OK");
result.put("message", "Hello " + name.getTextValue());
return ok(result);
}
}
I've tried to annotate model exactly as in an example
#XmlRootElement(name = "User")
public class User {
public String name;
#XmlElement(name = "name")
public String getName() {
return name;
}
}
The result is:
{
apiVersion: "beta",
swaggerVersion: "1.1",
basePath: "http://localhost:9000",
resourcePath: "/user",
apis: [
{
path: "/user",
description: "User registration and authorisation",
operations: [
{
httpMethod: "POST",
summary: "Create user",
notes: "Used to register new user.",
responseClass: "void",
nickname: "createUser",
parameters: [
{
name: "body",
description: "Created user object",
paramType: "body",
required: true,
allowMultiple: false,
dataType: "User"
}
]
}
]
}
]
}
Any ideas ?

I've found the answer myself.
It appears that swagger acknowledges a model when it is being used as a return value, ie responseClass:
#ApiOperation( value = "Find quiz by ID",
notes = "Returns a quiz with given ID",
responseClass = "models.Quiz" )
#ApiErrors( value = {
#ApiError(code = 400, reason = "Invalid ID supplied"),
#ApiError(code = 404, reason = "Quiz not found") })
public static Result getQuizById(
#ApiParam(value = "ID of question that needs to be fetched", required = true) #PathParam("quizId")
String quizId) {
ObjectNode result = Json.newObject();
return ok(result);
}
Simply adding method like this makes corresponding model appear in api-docs.json.

Related

Swagger not working properly with SB 3 + SpringDoc 2

I have been developing new APIs in Springboot 3 and it has been more a headache than something good, but finally I'm able to do something. Issue is that I was able to add Swagger to it, with OpenAPI from Spring-doc release 2. but the configuration file is not reading my properties. Also I have troubles trying to set up my bearer authentication....
This is my actual swagger: Swagger + spring-doc
And third issue related to this is... I keep can't make the swagger to read the default responses... even I configured like in the old versions, but I couldn't make it work...
For properties, I have tried to add them before the config class, and ad a Bean in methods.
For bearer, I was following Baeldung JWT Swagger guide, but It confuses me a little, tried to run but didnt work.
This is my OpenApiConfig class (commented lines are because they are not compatible with tag declaration):
package info.peluka.csaread.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.servers.Server;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
#Configuration
#OpenAPIDefinition(
info =#Info(
title = "${module-name}",
version = "${api-version}",
contact = #Contact(
name = "Joseph", email = "CSA_Read_API#peluka.info", url = "https://www.peluka.info"
),
license = #License(
name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0"
),
description = "${module-description}"
),
servers = #Server(
url = "${api.server.url}",
description = "Production"
)
)
public class OpenApiConfig {
private final String moduleName;
private final String apiVersion;
private final String moduleDescription;
public OpenApiConfig(
#Value("${module-name}") String moduleName,
#Value("${api-version}") String apiVersion,
#Value("${module-description}") String moduleDescription) {
this.moduleName = moduleName;
this.apiVersion = apiVersion;
this.moduleDescription = moduleDescription;
}
/**
* Configure the OpenAPI components.
*
* #return Returns fully configure OpenAPI object
* #see OpenAPI
*/
#Bean
public OpenAPI customizeOpenAPI() {
//#formatter:off
final String securitySchemeName = "bearerAuth";
return new OpenAPI()
.addSecurityItem(new SecurityRequirement()
.addList(securitySchemeName))
.components(new Components()
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.description(
"Provide the JWT token. JWT token can be obtained from the /token endpoint. If need to create an user, contact Griffith.")
.bearerFormat("JWT")));
//#formatter:on
}
// #Bean
// public OpenAPI customOpenAPI(#Value("${application-description}")
// String appDesciption,
// #Value("${application-version}")
// String appVersion) {
// return new OpenAPI()
// .info(new Info()
// .title("CSA Read API - Swagger")
// .version(appVersion)
// .description(appDesciption)
// .termsOfService("http://swagger.io/terms/")
// .license(new License().
// name("Apache 2.0").
// url("http://springdoc.org")));
// }
// #Bean
// public OpenAPI customOpenAPI() {
// final String securitySchemeName = "bearerAuth";
// return new OpenAPI()
// .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
// .components(
// new Components()
// .addSecuritySchemes(securitySchemeName,
// new SecurityScheme()
// .name(securitySchemeName)
// .type(SecurityScheme.Type.HTTP)
// .scheme("bearer")
// .bearerFormat("JWT")
// )
// )
// .info(new Info().title(moduleName).version(apiVersion).description(moduleDescription));
// }
}
Inside my controller, I have this (It's just a code block of two endpoints) :
(...)
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
(...)
#RestController
#RequestMapping("/csa/api")
#Tag(name = "Users & Clan Controller", description = "This Endpoint manages Users and CSA Members")
public class ClanController extends Helper {
(...)
#PostMapping("/token")
#Operation(summary = "Request a token", description = "Return a new token" )
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = TOKEN_GENERATED_SUCCESSFULLY, content = #Content),
#ApiResponse(responseCode = "400", description = EMAIL_OR_PASSWORD_WRONG, content = #Content),
#ApiResponse(responseCode = "500", description = INTERNAL_SERVER_ERROR, content = #Content) })
public ResponseEntity<Object> token(#RequestParam("email") String email, #RequestParam("password") String password) {
try {
if(!isValidEmail(email))
return ResponseHandler.generateResponse(EMAIL_OR_PASSWORD_WRONG, HttpStatus.BAD_REQUEST, EMPTY);
var optionalUsers = usersRepository.findByEmailAndPassword(email, password);
if (!optionalUsers.isPresent())
return ResponseHandler.generateResponse(EMAIL_OR_PASSWORD_WRONG, HttpStatus.BAD_REQUEST, EMPTY);
var token = getJWTToken(email);
optionalUsers.get().setToken(token);
optionalUsers.get().setLastLogin(LocalDate.now());
usersRepository.save(optionalUsers.get());
return ResponseHandler.generateResponse(TOKEN_GENERATED_SUCCESSFULLY, HttpStatus.OK, new Token(token));
} catch (Exception e){
return ResponseHandler.generateResponse(INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
#PostMapping("/updatePW")
#Operation(summary = "Update user password", description = "Return successful if all validations were OK." )
#ApiResponses(value = {
#ApiResponse(responseCode = "201", description = PASSWORD_CHANGED_SUCCESSFULLY, content = #Content),
#ApiResponse(responseCode = "400", description = EMAIL_OR_PASSWORD_WRONG, content = #Content),
#ApiResponse(responseCode = "406", description = NEW_PASSWORD_ERROR, content = #Content),
#ApiResponse(responseCode = "500", description = INTERNAL_SERVER_ERROR, content = #Content) })
#SecurityRequirement(name = "Bearer Authentication")
public ResponseEntity<Object> updatePassword(#RequestBody OldUser oldUser){
Users userSaved;
try {
if(!isValidEmail(oldUser.getEmail()))
return ResponseHandler.generateResponse(EMAIL_OR_PASSWORD_WRONG, HttpStatus.BAD_REQUEST, oldUser);
if(!oldUser.getNewPassword().isEmpty() && !isValidPassword(oldUser))
return ResponseHandler.generateResponse(NEW_PASSWORD_ERROR, HttpStatus.NOT_ACCEPTABLE, oldUser);
var init = usersRepository.findAll();
var user = usersRepository.findByEmailAndPassword(oldUser.getEmail(), oldUser.getOldPassword());
if(!user.isPresent())
return ResponseHandler.generateResponse(EMAIL_OR_PASSWORD_WRONG, HttpStatus.BAD_REQUEST, oldUser);
user.get().setPassword(oldUser.getNewPassword());
if(!oldUser.getNewPassword().isEmpty()){
userSaved = usersRepository.save(user.get());
} else {
userSaved = usersRepository.save(new Users(user.get()));
}
emailService.sendMail(userSaved, EMAIL_CHANGE_PASSWORD);
return ResponseHandler.generateResponse(PASSWORD_CHANGED_SUCCESSFULLY, HttpStatus.CREATED, userSaved);
} catch (Exception exception) {
return ResponseHandler.generateResponse(INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage());
}
}
As you can see in the first image.... For some reasons my "tags" of spring-doc are not working. I have no descriptions, error responses, any definition at all.
I'm working with SpringBoot 3.0.0 and spring-doc version 2.0.0, I have in my pom the following related imported artifacts to spring-doc: springdoc-openapi-starter-webmvc-ui, springdoc-openapi-starter-common, springdoc-openapi-starter-webflux-ui
I'm using also Java 17, and recently I started to use Dockerfile (This is the only way I have to make Swagger works without asking me credentials)
Please, any help with this will be very useful. I have been trying to figure out what to do for several weeks now, and the final users need Swagger implemented for easier access....
PS1: The response of api has this format:
{
"data": {
"name": "TEST NAME",
"email": "TEST.EMAIL#EMAIL.io",
"password": "TEST_PASSWORD",
"dateCreated": "2022-12-13",
"dateModified": "2022-12-13",
"lastLogin": "2022-12-13",
"token": "Bearer TOKEN",
"active": true
},
"message": "User Created Successfully",
"status": 201
}
Basically is:
{
"data" : Object
"message" : String
"status" : Int
}
Where data is the object created in most of cases. Message, just a typo message. status, HTTP Code with the status of operation...

Why does the string returned as "4-5 u043Bu0435u0442 103-112 u0441u043C"?

I am developing backend which collect data from different sources (REST APIs) and return summarized data.
So I requesting data from one of APIs. Response from that API is (all Cyrillic characters looks good) described as #Data class ProductSku {private string label; private boolean isInStock;}:
[
{
//... omit lot of unused fields
"label": "1 год 73-75 см",
"isInStock": true
},
{
//... omit lot of unused fields
"label": "2 года 82-88 см",
"isInStock": true
}
]
Then I allow to request it among other datas from my backend endpoint:
#GetMapping(path = "products/{prodId}", produces = {"application/json; charset=UTF-8","*/*;charset=UTF-8"})
public ProductEntity getProduct(#PathVariable("prodId") int prodId) {
return catalogService.getProduct(prodId);
}
class CatalogService {
public ProductEntity getProduct(int prodId) {
// ...
// ommited fill up result from other APIs
List<ProductData> productSkus = stockClient.getProductSkus(prodId);
productEntity.addSkus(productSkus);
return productEntity;
}
}
class StockClient {
public List<ProductSku> getProductSkus(int prodId) {
// using restTemplate
// querying by param productId
ProductData[] productsData;
try {
var headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
headers.set(HttpHeaders.PRAGMA, "no-cache");
headers.set(HttpHeaders.CACHE_CONTROL, "no-cache");
var uri = UriComponentsBuilder.fromHttpUrl(apiUrl).queryParam("productId", prodId).build().toUri();
var requestEntity = RequestEntity.get(uri).headers(headers).build();
productsData = restTemplate.exchange(requestEntity, ProductData[].class).getBody();
} catch (...) {
//...
}
// convert to list here
return new Arrays.asList(productData);
}
}
// ...
So it respond me as:
{
// ...
"skus": [
{
"size": "1 u0433u043Eu0434 73-75 u0441u043C",
"inStock": true
},
{
"size": "2 u0433u043Eu0434u0430 82-88 u0441u043C",
"inStock": true
}
]
// ...
}
As you can see instead of "2 года 82-88 см" i get "2 u0433u043Eu0434u0430 82-88 u0441u043C"
Cyrillic symbols from other APIs appears normally.
I am hope for your help

Java Swagger not generating service where endpoint response type is List<T>

I am trying to use swagger with java.
Using NSwag studio I am able to generate all my endpoints except one that returns a list of objects.
Here is my action in controller:
#ApiOperation(value = "getAll", nickname = "getAll", responseContainer = "List", response = DiakEntity.class)
#GetMapping("/api/diakok")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<DiakEntity> GetDiakok() throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return request.getObject();
}
I am using swagger-annotations 1.5.23, springfox-swagger-ui 2.9.2, springfox-swagger2 2.9.2.
If I test from Postman it works.
Also tried like this:
#ApiOperation(value = "getAll", nickname = "getAll")
#ApiResponse(code = 200, responseContainer="List", response=DiakEntity.class, message = "Gets all diak objects")
#GetMapping("/api/diakok")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public ResponseEntity<List<DiakEntity>> GetDiakok() throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return new ResponseEntity<>(request.getObject(), HttpStatus.OK);
}
thnx
Please try with the following annotation for swagger.
#ApiOperation(value = "getAll", nickname = "getAll")
#ApiResponse(code = 200, responseContainer="List", response=DiakEntity.class)
At the end I changed my action as below, and it started to work
#ApiOperation(value = "all", nickname = "all")
#PostMapping("/api/diak/all")
#ResponseBody
#PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<DiakEntity> GetAll(#RequestBody #Valid RequestDiakByName data) throws Exception
{
ServiceObjectResponse<List<DiakEntity>> request = _diakService.getAll();
if(!request.getIsSuccess())
{
throw new Exception(request.getMessage());
}
return request.getObject();
}

HttpMediaTypeNotSupportedException

I have this class in my .NET application to send some data from client(.NET) to server(Spring) :
class NetworkController
{
private static readonly HttpClient client = new HttpClient();
public static async Task SendUserDataAsync()
{
var values = new Dictionary<string, string>
{
{ "firstName", "sunny" },
{ "lastName", "leone" },
{ "timeStamp", "test" }
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("http://localhost:8080/user", content);
var responseString = await response.Content.ReadAsStringAsync();
}
}
Reference
And in my Spring Boot application, I a class called User :
#Entity
public class User
{
#Id
private String firstName;
private String lastName;
private String timeStamp;
public User(){}
#Override
public String toString() {
return "firstName : "+this.firstName + "\n"+"lastName : " + this.lastName;
}
}
In my rest-controller I have this method to insert User :
#PostMapping("/user")
User addUser(#RequestBody User user)
{
System.out.println(user);//this always prints an empty line, maybe receiving nothing
return userRepository.save(user);
}
I get this warning Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported]
I have created this class(with the concept of Spring) in .NET, but it seems no use :
class User
{
String firstName;
String lastName;
String timeStamp;
public User()
{
firstName = "1"
lastName = "2"
timeStamp = "test"
}
}
Wouldn't sending an object instead of dictionary be more gentle and tidy ? How to do so ?
How can I resolve this problem ?
In your .NET application, the line var content = new FormUrlEncodedContent(values); indicates that the request will have a HTTP header Content-Type set to application/x-www-form-urlencoded.
It means the data stored in var values = new Dictionary... will be formatted by .NET as a query string such as firstName=sunny&lastName=leone&timeStamp=test.
That is what your Sprint server receives. However it wants to receive JSON data, not a query string. So it complains.
In order to get rid of the miscommunication, your .NET application should send JSON data, such as
{"firstName": "sunny", "lastName": "leone", "timeStamp": "test"},
as expected by the Spring server.
Here is an example code:
HttpClient client = new HttpClient();
object anonymousObject = new
{
firstName = "sunny",
lastName = "leone",
timeStamp = "test"
};
string jsonContent = JsonConvert.SerializeObject(anonymousObject);
var request = new HttpRequestMessage(HttpMethod.Post, "http://127.0.0.1:8080/user");
request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
You need to install the package Newtonsoft.Json in order to call JsonConvert.SerializeObject(anonymousObject), as pointed by this SO answer mentionned by #alfcope.

Swagger datatype not generating docs

I have the below code in Swagger,
#Path("/v1")
#ApiOperation(value = "POST - Some Value", nickname = "post-funtion", consumes = "application/json", produces = "text/html; charset=UTF-8", tags = {
"Some Controller" })
#ApiImplicitParams({
#ApiImplicitParam(name = "Authorization", paramType = "header", dataType = "string", format = "JWT", required = false, value = "A User Service JWT"),
#ApiImplicitParam(name = "Request", value = "Request Object", paramType = "body", dataType = "org.pkg.SomeRequest", required = true) })
#ApiResponses({
#ApiResponse(code = 200, message = "Value Added", response = SomeResponse.class) })
private Object retrieveByName(Request request, Response response)
{
return new RetrieveByNameRqstHandler(catalogService, request, response).handle();
}
The code is supposed to automatically generate default json request depending upon the datatype which in this case is "org.pkg.SomeRequest" but there is nothing generated. On the contrary if I change the "org.pkg.SomeRequest" with "org.pkg.SomeResponse" there is a default JSON generated for this. Can anybody help me please?
Consider both classes SomeRequest,SomeResponse have the same code.
This is the image where I use "org.pkg.SomeRequest" in the dataType
This is the image where I use "org.pkg.SomeResponse" in the dataType
According to this GitHub issue on Swagger core project, if you add the annotation #ApiImplicitParam should resolve your problem.
#ApiImplicitParams({
#ApiImplicitParam(
required = true,
dataType = "com.example.SomeObjectDto",
paramType = "body"
)
})
But normally if you just add the class on your method signature it'll work.
private Object retrieveByName(SomeObjectDto someObjectDto) {
someCode();
}
Also SomeObjectDto class should contain "get" methods for your variables like.
class SomeObjectDto {
private String info;
getInfo(){
return info;
}
}
Will produce the following JSon.
{ info: "string" }
ApiImplicitParam can map a parameter to a correct type, but the type must be detected by swagger, so must be a valid reference.
The only way I could make this working is by using additionalModels method.
Example in spring-boot:
configure swagger
import springfox.documentation.spring.web.plugins.Docket;
import com.fasterxml.classmate.TypeResolver;
...
#Bean
public Docket api(TypeResolver typeResolver) {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("your-group-rest-api")
.select()
.apis(RequestHandlerSelectors.basePackage("your.package"))
.paths(PathSelectors.any())
.build()
.additionalModels(typeResolver.resolve(YourModel.class))
.apiInfo(apiInfo());
}
controller
#ApiOperation...
#ApiImplicitParams(
#ApiImplicitParam(dataType = "YourModel", name = "requestJson", paramType = "body"))
#ApiResponses...
#RequestMapping...
public void yourMethod(#RequestBody String requestJson,...)
Of course, you could have an InputStream parameter for the request and map that to your model.

Categories

Resources