Added custom exception handlers

This commit is contained in:
Marco Cetica 2024-01-22 16:10:26 +01:00
parent 813b38aed1
commit 0757c02db0
Signed by: marco
GPG Key ID: 45060A949E90D0FD
9 changed files with 113 additions and 67 deletions

View File

@ -14,6 +14,14 @@ import java.util.HashMap;
@ControllerAdvice
public class CustomExceptionsHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(GenericErrorException.class)
public ResponseEntity<String> genericErrorException(GenericErrorException ex) {
var error = new JsonEmitter<>(ex.getMessage()).emitJsonKey(ex.getKey());
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {

View File

@ -0,0 +1,21 @@
package com.ceticamarco.bits.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Bad Request")
public class GenericErrorException extends RuntimeException {
private final String key;
public GenericErrorException(String message, String key) {
super(message);
this.key = key;
}
public GenericErrorException() {
super("Bad Request");
this.key = "error";
}
public String getKey() { return key; }
}

View File

@ -3,7 +3,7 @@ package com.ceticamarco.bits.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value= HttpStatus.UNAUTHORIZED, reason="Unauthorized user")
@ResponseStatus(value = HttpStatus.UNAUTHORIZED, reason = "Unauthorized User")
public class UnauthorizedUserException extends RuntimeException {
public UnauthorizedUserException(String message) {
super(message);

View File

@ -1,5 +1,6 @@
package com.ceticamarco.bits.post;
import com.ceticamarco.bits.exception.GenericErrorException;
import com.ceticamarco.bits.exception.UnauthorizedUserException;
import com.ceticamarco.bits.json.JsonEmitter;
import com.ceticamarco.bits.user.User;
@ -28,14 +29,14 @@ public class PostController {
public ResponseEntity<List<Post>> getPosts(@RequestBody User user) {
// Check if email and password are specified
if(user.getPassword() == null || user.getEmail() == null) {
throw new UnauthorizedUserException("Specify both email and password");
throw new GenericErrorException("Specify both email and password", "error");
}
// Get post list
var res = postService.getPosts(user);
// Check if user is authorized
if(res.isLeft()) { // TODO: implement proper generic exception handler
if(res.isLeft()) {
throw new UnauthorizedUserException(res.getLeft().getMessage());
}
@ -50,15 +51,13 @@ public class PostController {
*/
@GetMapping("/api/posts/{postId}")
public ResponseEntity<String> getPostById(@PathVariable("postId") String postId) {
var res = postService.getPostById(postId)
.map(post -> new JsonEmitter<>(post).emitJsonKey())
.swap()
.map(error -> new JsonEmitter<>(error.getMessage()).emitJsonKey("error"))
.swap();
var res = postService.getPostById(postId);
if(res.isLeft()) {
throw new GenericErrorException(res.getLeft().getMessage(), "error");
}
return res.isRight()
? new ResponseEntity<>(res.get(), HttpStatus.OK)
: new ResponseEntity<>(res.getLeft(), HttpStatus.BAD_REQUEST);
var jsonOutput = new JsonEmitter<>(res.get()).emitJsonKey();
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
/**
@ -72,14 +71,14 @@ public class PostController {
public ResponseEntity<List<Post>> getPostByTitle(@RequestBody Post req) {
// Check if email and password are specified
if(req.getUser() == null || req.getUser().getPassword() == null || req.getUser().getEmail() == null) {
throw new UnauthorizedUserException("Specify both email and password");
throw new GenericErrorException("Specify both email and password", "error");
}
// Get post by title
var res = postService.getPostByTitle(req);
// Check if user is authorized
if(res.isLeft()) { // TODO: implement proper generic exception handler
if(res.isLeft()) {
throw new UnauthorizedUserException(res.getLeft().getMessage());
}
@ -94,15 +93,13 @@ public class PostController {
*/
@PostMapping("/api/posts/new")
public ResponseEntity<String> submitPost(@Valid @RequestBody Post post) {
var res = postService.addNewPost(post)
.map(postId -> new JsonEmitter<>(postId).emitJsonKey("post_id"))
.swap()
.map(error -> new JsonEmitter<>(error.getMessage()).emitJsonKey("error"))
.swap();
var res =postService.addNewPost(post);
if(res.isLeft()) {
throw new GenericErrorException(res.getLeft().getMessage(), "error");
}
return res.isRight()
? new ResponseEntity<>(res.get(), HttpStatus.OK)
: new ResponseEntity<>(res.getLeft(), HttpStatus.BAD_REQUEST);
var jsonOutput = new JsonEmitter<>(res.get()).emitJsonKey("post_id");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
/**
@ -114,15 +111,14 @@ public class PostController {
*/
@PutMapping("/api/posts/{postId}")
public ResponseEntity<String> updatePost(@Valid @RequestBody Post post, @PathVariable("postId") String postId) {
// Update post
var res = postService.updatePost(post, postId);
if(res.isPresent()) {
throw new GenericErrorException(res.get().getMessage(), "error");
}
return res.map(error -> {
var jsonOutput = new JsonEmitter<>(res.get().getMessage()).emitJsonKey("error");
return new ResponseEntity<>(jsonOutput, HttpStatus.BAD_REQUEST);
}).orElseGet(() -> {
var jsonOutput = new JsonEmitter<String>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
});
var jsonOutput = new JsonEmitter<>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
/**
@ -142,12 +138,11 @@ public class PostController {
// Delete the post
var res = postService.deletePost(user, postId);
return res.map(error -> {
var jsonOutput = new JsonEmitter<>(error.getMessage()).emitJsonKey("error");
return new ResponseEntity<>(jsonOutput, HttpStatus.BAD_REQUEST);
}).orElseGet(() -> {
var jsonOutput = new JsonEmitter<>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
});
if(res.isPresent()) {
throw new GenericErrorException(res.get().getMessage(), "error");
}
var jsonOutput = new JsonEmitter<>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
}

View File

@ -1,5 +1,6 @@
package com.ceticamarco.bits.user;
import com.ceticamarco.bits.exception.GenericErrorException;
import com.ceticamarco.bits.exception.UnauthorizedUserException;
import com.ceticamarco.bits.json.JsonEmitter;
import jakarta.validation.Valid;
@ -29,14 +30,14 @@ public class UserController {
public ResponseEntity<List<User>> getUsers(@RequestBody User user) {
// Check if email and password are specified
if(user.getPassword() == null || user.getEmail() == null) {
throw new UnauthorizedUserException("Specify both email and password");
throw new GenericErrorException("Specify both email and password", "error");
}
// Get post list
var res = userService.getUsers(user);
// Check if user is authorized
if(res.isLeft()) { // TODO: implement proper generic exception handler
if(res.isLeft()) {
throw new UnauthorizedUserException(res.getLeft().getMessage());
}
@ -51,15 +52,13 @@ public class UserController {
*/
@PostMapping("/api/users/new")
public ResponseEntity<String> submitUser(@Valid @RequestBody User user) {
var res = userService.addNewUser(user)
.map(userId -> new JsonEmitter<>(userId).emitJsonKey("user_id"))
.swap()
.map(error -> new JsonEmitter<>(error.getMessage()).emitJsonKey("error"))
.swap();
var res = userService.addNewUser(user);
if(res.isLeft()) {
throw new GenericErrorException(res.getLeft().getMessage(), "error");
}
return res.isRight()
? new ResponseEntity<>(res.get(), HttpStatus.OK)
: new ResponseEntity<>(res.getLeft(), HttpStatus.BAD_REQUEST);
var jsonOutput = new JsonEmitter<>(res.get()).emitJsonKey("user_id");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
/**
@ -72,17 +71,15 @@ public class UserController {
public ResponseEntity<String> deleteUser(@RequestBody User user) {
// Check if email and password are specified
if(user.getPassword() == null || user.getEmail() == null) {
var res = new JsonEmitter<>("Specify both email and password").emitJsonKey("error");
return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
throw new GenericErrorException("Specify both email and password", "error");
}
// Delete the user
var res = userService.deleteUser(user);
return res.map(error -> {
var jsonOutput = new JsonEmitter<>(error.getMessage()).emitJsonKey("error");
return new ResponseEntity<>(jsonOutput, HttpStatus.BAD_REQUEST);
}).orElseGet(() -> {
var jsonOutput = new JsonEmitter<>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
});
if(res.isPresent()) {
throw new GenericErrorException(res.get().getMessage(), "error");
}
var jsonOutput = new JsonEmitter<>("OK").emitJsonKey("status");
return new ResponseEntity<>(jsonOutput, HttpStatus.OK);
}
}

View File

@ -6,7 +6,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

View File

@ -1,14 +1,14 @@
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
# Adjust these values in production
#server.port=3000
#spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/bit
#spring.datasource.username=postgres
#spring.datasource.password=toor
#spring.security.user.name=admin
#spring.security.user.password=admin
server.port=3000
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/bit
spring.datasource.username=postgres
spring.datasource.password=toor
spring.security.user.name=admin
spring.security.user.password=admin
# For unit tests only
#spring.datasource.url=jdbc:h2:mem:testdb

View File

@ -41,14 +41,18 @@ public class PostControllerTests {
post.setTitle("test");
post.setContent("This is a test");
when(postService.getPosts()).thenReturn(List.of(post));
var user = new User();
user.setEmail("john@example.com");
user.setPassword("qwerty");
when(postService.getPosts(any(User.class))).thenReturn(Either.right(List.of(post)));
mockMvc.perform(MockMvcRequestBuilders.get("/api/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(post)))
.content(objectMapper.writeValueAsString(user)))
.andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(postService, Mockito.times(1)).getPosts();
Mockito.verify(postService, Mockito.times(1)).getPosts(any(User.class));
}
@Test
@ -71,18 +75,22 @@ public class PostControllerTests {
@Test
public void getPostByTitle() throws Exception {
var post = new Post();
var user = new User();
user.setEmail("john@example.com");
user.setPassword("qwerty");
post.setId("abc123");
post.setTitle("test");
post.setContent("This is a test");
post.setUser(user);
when(postService.getPostByTitle(anyString())).thenReturn(List.of(post));
when(postService.getPostByTitle(any(Post.class))).thenReturn(Either.right(List.of(post)));
mockMvc.perform(MockMvcRequestBuilders.get("/api/posts/bytitle")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(post)))
.andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(postService, Mockito.times(1)).getPostByTitle(anyString());
Mockito.verify(postService, Mockito.times(1)).getPostByTitle(any(Post.class));
}
@Test

View File

@ -15,6 +15,7 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
@ -31,6 +32,23 @@ class UserControllerTest {
@MockBean
private UserService userService;
@Test
public void getUsers() throws Exception {
var user = new User();
user.setUsername("john");
user.setEmail("john@example.com");
user.setPassword("qwerty");
when(userService.getUsers(any(User.class))).thenReturn(Either.right(List.of(user)));
mockMvc.perform(MockMvcRequestBuilders.get("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(MockMvcResultMatchers.status().isOk());
Mockito.verify(userService, Mockito.times(1)).getUsers(any(User.class));
}
@Test
public void addNewUser() throws Exception {
var user = new User();