Swagger not working properly with SB 3 + SpringDoc 2 - java

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...

Related

Angular / Spring Boot | error: SyntaxError: "JSON.parse: unexpected character at line 1 column 1 of the JSON data"

I am currently working on a tutorial to get to know Spring Boot and currently facing the following problem.
During my registration process (works correctly -> user ends up in the database) I get the status code 200/OK in the browser console, but also an error message regarding an incorrect syntax:
My backend code looks like this:
AuthController:
#RestController
#RequestMapping(value = "/api/auth")
#AllArgsConstructor
public class AuthController {
private final AuthService authService;
private final RefreshTokenService refreshTokenService;
#PostMapping(value = "/signup")
public ResponseEntity<String> signup(#RequestBody RegisterRequest registerRequest) {
/*
* RegisterRequest: Through this class we are transferring the user details like username, password and email as part of the RequestBody (DTO)
* */
authService.signUp(registerRequest);
return new ResponseEntity<>("Registration Successful", null, OK);
}
....
AuthService:
#Transactional
public void signUp(RegisterRequest registerRequest) {
User user = new User();
user.setUsername(registerRequest.getUsername());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setCreated(now());
user.setEnabled(false);
userRepository.save(user);
String token = generateVerificationToken(user);
String message = mailContentBuilder.build("Thank you for signing up to Spring Reddit, please click on the below url to activate your account : "
+ ACTIVATION_EMAIL + "/" + token);
mailService.sendMail(new NotificationEmail("Please Activate your account", user.get
Email(), message));
}
Used DTO:
public class RegisterRequest {
private String email;
private String username;
private String password;
}
My frontend code looks like:
SignUpComponent:
signUp() {
this.signUpRequestPayload.email = this.signUpForm.get('email').value;
this.signUpRequestPayload.password = this.signUpForm.get('password').value;
this.signUpRequestPayload.username = this.signUpForm.get('username').value;
this.authService.signUp(this.signUpRequestPayload).subscribe((data) => {
console.log('Sign up successful', data);
});
}
AuthService:
#Injectable({
providedIn: 'root'
})
export class AuthService {
private headers = new HttpHeaders({ 'Content-Type': 'application/json' });
constructor(private http: HttpClient) { }
signUp(signUpRequestPayload: SignUpRequestPayload): Observable<SignUpRequestPayload> {
const body = JSON.stringify(signUpRequestPayload);
return this.http.post<SignUpRequestPayload>('http://localhost:8080/api/auth/signup', body, { headers: this.headers });
}
}
Used interface:
export class SignUpRequestPayload {
email: string;
password: string;
username: string;
}
What am I doing wrong here?
I solved it like this:
signUp(signUpRequestPayload: SignUpRequestPayload): Observable<string> {
const body = signUpRequestPayload;
return this.http.post('http://localhost:8080/api/auth/signup', body, { responseType: 'text', headers: this.headers });
}
I had to remove from the post method and set the responseType to 'text'. I also had to remove the JSON.stringify() method and set the return type to Observable.
As your response("Registration Successful") is not valid JSON.
So please remove <SignUpRequestPayload> from below line
return this.http.post<SignUpRequestPayload>

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.

Unable to access backbone Model properties from controller

I have faced an error regarding interaction between BackboneJS and spring mvc controller. I am unable to access model properties while adding model to collection list. The error prone part of my JS code is as follows:
var Task = Backbone.Model.extend({
defaults: {
taskName: '',
category:'',
completed: false,
dateCreated:0,
dateCompleted:0
}
});
var TaskList = Backbone.Collection.extend({
model: Task,
url : "/todoCollection"
});
// instance of the Collection
var taskList = new TaskList();
var TaskView = Backbone.View.extend({
tagName: 'div',
render: function(){
var itemHTML = _.template($('script.itemview').html());
this.$el.html(itemHTML(this.model.toJSON()));
return this; // enable chained calls
}
});
var TaskCreateView = Backbone.View.extend({
el : ".taskcreate",
initialize : function(){
this.render();
this.input = this.$('#taskInput');
this.categoryInput = this.$('#taskCategory');
taskList.on('add', this.addAll, this);
taskList.on('reset', this.addAll, this);
taskList.fetch();
},
render : function(){
var createListHTML = _.template($('script.create-task-view').html());
this.$el.append(createListHTML);
var createListHTML = _.template($('script.list-view').html());
this.$el.append(createListHTML);
},
events: {
'click button#createButton':'createTask'
},
createTask : function(e){
if(this.input.val() == ''){
alert("Task name expected");
return;
}
if(this.categoryInput.val() == 'None'){
alert("Enter valid category");
return;
}
var newTask = {
taskName: this.input.val().trim(),
completed: false,
category: this.categoryInput.val().trim()
};
taskList.create(newTask,{ wait: true });
this.input.val(''); // clean input box
this.categoryInput.val('None');
},
addOne: function(task){
var view = new TaskView({model: task});
$('#listDiv').append(view.render().el);
},
addAll: function(){
this.$('#listDiv').html(''); // clean the todo list
taskList.each(this.addOne, this);
}
});
var TodoAppView = Backbone.View.extend({
el: '#todoApp',
initialize : function(){
this.render();
},
render : function(){
var appHTML = _.template($('script.appview').html());
this.$el.append(appHTML);
var taskCreateView = new TaskCreateView();
}
});
var TodoApp1 = new TodoAppView();
The url /todoCollection in TaskList is mapped to a spring mvc controller which is defined as follows:
package com.glider.controller;
import com.glider.model.Todo;
import com.glider.service.TodoService;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Date;
import java.util.List;
#Controller
public class TodoCollectionController {
#Autowired
TodoService service;
#RequestMapping(value = "/todoCollection",method = RequestMethod.POST)
#ResponseBody
public String createTodo(#RequestParam(value = "taskName")String taskName,
#RequestParam(value = "category")String category){
System.out.println("Method working");
ObjectMapper objectMapper = new ObjectMapper();
try {
Todo todo = service.create(taskName,category);
String jsonInString = objectMapper.writeValueAsString(todo);
return jsonInString;
} catch (IOException e) {
e.printStackTrace();
}
return "error";
}
#RequestMapping(value = "/todoCollection",method = RequestMethod.GET)
#ResponseBody
public String getAllTodo(){
ObjectMapper objectMapper = new ObjectMapper();
try {
List<Todo> todoList = service.findAllTasks();
String jsonInString = objectMapper.writeValueAsString(todoList);
return jsonInString;
} catch (IOException e) {
e.printStackTrace();
}
return "error";
}
}
The controller method createTodo is expecting parameters like taskName and category. These properties are also mentioned while adding a new task to taskList. On executing above codes on server I am getting an error from browser console defined as follows:
jquery.min.js:4 POST http://localhost:8080/todoCollection 400 (Bad Request)
On Server side error exists as follows:
HTTP Status 400 - Required String parameter 'taskName' is not present.
I am unable to resolve this issue.
You need a Java class that represents the JSON object spring can map values to. #RequestParam is for mapping query string parameters from the request, which is not the case with REST and backbone.
Your code should look something like:
public String createTodo(#RequestBody Todo todo)){}
and spring will set the values in todo from the JSON request

How to annotate Play 2 webapp model for swagger?

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.

Categories

Resources