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..a01b1ebc8 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,53 +34,76 @@ 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); + } + + /** + * 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); } /** * Handles exception thrown by Bean Validation on controller methods parameters * * @param ex The thrown exception - * @param request the current web request + * * @return an empty response entity */ @ExceptionHandler(MethodArgumentNotValidException.class) - @ResponseStatus(code = BAD_REQUEST) + @ResponseStatus(BAD_REQUEST) @ResponseBody - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) { + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { BindingErrorsResponse errors = new BindingErrorsResponse(); BindingResult bindingResult = ex.getBindingResult(); - HttpHeaders headers = new HttpHeaders(); if (bindingResult.hasErrors()) { errors.addAllErrors(bindingResult); - headers.add("errors", errors.toJSON()); + return ResponseEntity.badRequest().body(new ErrorInfo("MethodArgumentNotValidException", "Validation failed")); } - return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST); + return ResponseEntity.badRequest().build(); } - private class ErrorInfo { - public final String className; - public final String exMessage; - - public ErrorInfo(Exception ex) { - this.className = ex.getClass().getName(); - this.exMessage = ex.getLocalizedMessage(); - } - } } diff --git a/src/main/resources/openapi.yml b/src/main/resources/openapi.yml index 162ef2754..86699c034 100755 --- a/src/main/resources/openapi.yml +++ b/src/main/resources/openapi.yml @@ -74,7 +74,7 @@ paths: required: true responses: 201: - description: The pet owner was sucessfully added. + description: The pet owner was successfully added. content: application/json: schema: @@ -314,7 +314,7 @@ paths: required: true responses: 201: - description: The pet was sucessfully added. + description: The pet was successfully added. content: application/json: schema: @@ -326,7 +326,7 @@ paths: schema: $ref: '#/components/schemas/RestError' 404: - description: Pet not found. + description: Pet or Owner not found. content: application/json: schema: @@ -489,7 +489,7 @@ paths: required: true responses: 201: - description: The vet visit was sucessfully added. + description: The vet visit was successfully added. content: application/json: schema: @@ -1849,7 +1849,7 @@ components: readOnly: true timestamp: title: Timestamp - description: The time the error occured. + description: The time the error occurred. type: string format: date-time example: '2019-08-21T21:41:46.158+0000' @@ -1886,7 +1886,7 @@ components: properties: message: title: Message - description: The valiation message. + description: The validation message. type: string example: "[Path '/lastName'] Instance type (null) does not match any allowed primitive type (allowed: [\"string\"])" readOnly: true 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..a4bce2b9f 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)) + // set empty JSON to force 400 error + .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..ed65ee184 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 @@ -63,7 +63,7 @@ void testCreateUserSuccess() throws Exception { @WithMockUser(roles = "ADMIN") void testCreateUserError() throws Exception { User user = new User(); - user.setUsername("username"); + user.setUsername(""); // set empty username to force 400 error user.setPassword("password"); user.setEnabled(true); ObjectMapper mapper = new ObjectMapper();