From fc18a002128a1bd0b3aebdb3e1425f682baceab4 Mon Sep 17 00:00:00 2001 From: earlspilner Date: Sun, 11 Aug 2024 00:10:56 +0300 Subject: [PATCH 1/3] fix: correct 404 responses and refactor error information to use record --- .../advice/ExceptionControllerAdvice.java | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java b/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java index a49745d00..b1aa4600e 100644 --- a/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java +++ b/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -33,23 +34,41 @@ import static org.springframework.http.HttpStatus.BAD_REQUEST; /** + * Global Exception handler for REST controllers. + *

+ * This class handles exceptions thrown by REST controllers and returns + * appropriate HTTP responses to the client. + * * @author Vitaliy Fedoriv + * @author Alexander Dudkin */ - @ControllerAdvice public class ExceptionControllerAdvice { - @ExceptionHandler(Exception.class) - public ResponseEntity exception(Exception e) { - ObjectMapper mapper = new ObjectMapper(); - ErrorInfo errorInfo = new ErrorInfo(e); - String respJSONstring = "{}"; - try { - respJSONstring = mapper.writeValueAsString(errorInfo); - } catch (JsonProcessingException e1) { - e1.printStackTrace(); + /** + * Record for storing error information. + *

+ * This record encapsulates the class name and message of the exception. + * + * @param className The name of the exception class + * @param exMessage The message of the exception + */ + private record ErrorInfo(String className, String exMessage) { + public ErrorInfo(Exception ex) { + this(ex.getClass().getName(), ex.getLocalizedMessage()); } - return ResponseEntity.badRequest().body(respJSONstring); + } + + /** + * Handles all general exceptions by returning a 500 Internal Server Error status with error details. + * + * @param e The exception to be handled + * @return A {@link ResponseEntity} containing the error information and a 500 Internal Server Error status + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleGeneralException(Exception e) { + ErrorInfo info = new ErrorInfo(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(info); } /** @@ -73,13 +92,19 @@ public ResponseEntity handleMethodArgumentNotValidException(MethodArgument return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST); } - private class ErrorInfo { - public final String className; - public final String exMessage; - - public ErrorInfo(Exception ex) { - this.className = ex.getClass().getName(); - this.exMessage = ex.getLocalizedMessage(); - } + /** + * Handles {@link DataIntegrityViolationException} which typically indicates database constraint violations. + * This method returns a 404 Not Found status if an entity does not exist. + * + * @param ex The {@link DataIntegrityViolationException} to be handled + * @return A {@link ResponseEntity} containing the error information and a 404 Not Found status + */ + @ExceptionHandler(DataIntegrityViolationException.class) + @ResponseStatus(code = HttpStatus.NOT_FOUND) + @ResponseBody + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { + ErrorInfo errorInfo = new ErrorInfo(ex); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorInfo); } + } From 9fce1ecc3886c7d18092bb25517b9795de2bfda8 Mon Sep 17 00:00:00 2001 From: earlspilner Date: Sun, 11 Aug 2024 01:54:36 +0300 Subject: [PATCH 2/3] fix: updated a couple things in the tests to make them more truthful --- .../petclinic/rest/controller/PetRestControllerTests.java | 3 ++- .../petclinic/rest/controller/UserRestControllerTests.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java b/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java index 06f1b5a99..fe78a0c4b 100644 --- a/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/rest/controller/PetRestControllerTests.java @@ -241,7 +241,8 @@ void testAddPetError() throws Exception { String newPetAsJSON = mapper.writeValueAsString(newPet); given(this.clinicService.findPetById(999)).willReturn(null); this.mockMvc.perform(post("/api/pets") - .content(new String()).accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) + // empty content to force badRequest response + .content("{}").accept(MediaType.APPLICATION_JSON_VALUE).contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isBadRequest()); } } diff --git a/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java b/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java index 9c5f504d6..532ce94ee 100644 --- a/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java +++ b/src/test/java/org/springframework/samples/petclinic/rest/controller/UserRestControllerTests.java @@ -64,7 +64,7 @@ void testCreateUserSuccess() throws Exception { void testCreateUserError() throws Exception { User user = new User(); user.setUsername("username"); - user.setPassword("password"); + user.setPassword(""); // empty password to force badRequest Response user.setEnabled(true); ObjectMapper mapper = new ObjectMapper(); String newVetAsJSON = mapper.writeValueAsString(userMapper.toUserDto(user)); From 38178d9c33324ee1b49174ea230fec18675c0194 Mon Sep 17 00:00:00 2001 From: earlspilner Date: Sun, 11 Aug 2024 01:59:42 +0300 Subject: [PATCH 3/3] fix: update correct 404 responses handling --- .../advice/ExceptionControllerAdvice.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java b/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java index b1aa4600e..ef6f974c9 100644 --- a/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java +++ b/src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java @@ -16,8 +16,6 @@ package org.springframework.samples.petclinic.rest.advice; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -62,7 +60,7 @@ public ErrorInfo(Exception ex) { /** * Handles all general exceptions by returning a 500 Internal Server Error status with error details. * - * @param e The exception to be handled + * @param e The exception to be handled * @return A {@link ResponseEntity} containing the error information and a 500 Internal Server Error status */ @ExceptionHandler(Exception.class) @@ -107,4 +105,23 @@ public ResponseEntity handleDataIntegrityViolationException(DataInteg return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorInfo); } + /** + * Handles exception thrown by Bean Validation on controller methods parameters + * + * @param ex The thrown exception + * @return an empty response entity + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(BAD_REQUEST) + @ResponseBody + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + BindingErrorsResponse errors = new BindingErrorsResponse(); + BindingResult bindingResult = ex.getBindingResult(); + if (bindingResult.hasErrors()) { + errors.addAllErrors(bindingResult); + return ResponseEntity.badRequest().body(new ErrorInfo("MethodArgumentNotValidException", "Validation failed")); + } + return ResponseEntity.badRequest().build(); + } + }