diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java index 9f8a19a73a22196f1c7252f83bb51a5c031e5114..617a0da1509d27121fb234e12f901547a20fafb7 100644 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiError.java @@ -1,67 +1,42 @@ package org.fuseri.modulecertificate.exceptions; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.ToString; import org.springframework.http.HttpStatus; +import java.time.Clock; import java.time.LocalDateTime; +import java.util.List; -/** - * @author Michal Badin - Formula 1 team - */ +@Getter +@ToString +class ApiError { -public class ApiError { - - private LocalDateTime timestamp; private HttpStatus status; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private LocalDateTime timestamp; private String message; + private List<ApiSubError> subErrors; private String path; - public ApiError(LocalDateTime timestamp, HttpStatus status, String message, String path) { - this.timestamp = timestamp; - this.status = status; - this.message = message; - this.path = path; - } - - public LocalDateTime getTimestamp() { - return timestamp; - } - - public void setTimestamp(LocalDateTime timestamp) { - this.timestamp = timestamp; - } - - public HttpStatus getStatus() { - return status; + private ApiError() { + timestamp = LocalDateTime.now(Clock.systemUTC()); } - public void setStatus(HttpStatus status) { + ApiError(HttpStatus status, Throwable ex, String path) { + this(); this.status = status; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { this.path = path; + this.message = ex.getLocalizedMessage(); } - @Override - public String toString() { - return "ApiError{" + - "timestamp=" + timestamp + - ", status=" + status + - ", message='" + message + '\'' + - ", path='" + path + '\'' + - '}'; + ApiError(HttpStatus status, List<ApiSubError> subErrors, Throwable ex, String path) { + this(); + this.status = status; + this.subErrors = subErrors; + this.path = path; + this.message = ex.getLocalizedMessage(); } } diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..f5ad2d920a83bfeb8157083b809af4cf0de3cc75 --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java @@ -0,0 +1,4 @@ +package org.fuseri.modulecertificate.exceptions; + +public interface ApiSubError { +} diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java new file mode 100644 index 0000000000000000000000000000000000000000..43b6acd7e2baba228cdcbaf0a4e13f4b59cfaf76 --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java @@ -0,0 +1,19 @@ +package org.fuseri.modulecertificate.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Data +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@Getter +@ToString +class ApiValidationError implements ApiSubError { + private String object; + private String field; + private Object rejectedValue; + private String message; +} diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/CustomRestGlobalExceptionHandling.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/CustomRestGlobalExceptionHandling.java deleted file mode 100644 index 8284014b3b7a8ed7d0955a699c65b633b32bf8e9..0000000000000000000000000000000000000000 --- a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/CustomRestGlobalExceptionHandling.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.fuseri.modulecertificate.exceptions; - -/** - * @author Michal Badin - Formula 1 team - * modified by Ester VilĂmková - */ - -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.util.UrlPathHelper; - -import java.time.Clock; -import java.time.LocalDateTime; - -@RestControllerAdvice -public class CustomRestGlobalExceptionHandling { - - private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); - - @ExceptionHandler({ResponseStatusException.class}) - public ResponseEntity<ApiError> handleResponseStatus( - final ResponseStatusException ex, final HttpServletRequest request) { - final ApiError apiError = new ApiError( - LocalDateTime.now(Clock.systemUTC()), - HttpStatus.NOT_FOUND, - ex.getLocalizedMessage(), - URL_PATH_HELPER.getRequestUri(request)); - return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus()); - } - - /** - * Handle all the exceptions not matched by above-mentioned definitions. - * - * @param ex the ex - * @param request the request - * @return the response entity - */ - @ExceptionHandler({Exception.class}) - public ResponseEntity<ApiError> handleAll(final Exception ex, HttpServletRequest request) { - final ApiError apiError = new ApiError( - LocalDateTime.now(Clock.systemUTC()), - HttpStatus.INTERNAL_SERVER_ERROR, - getInitialException(ex).getLocalizedMessage(), - URL_PATH_HELPER.getRequestUri(request)); - return new ResponseEntity<>(apiError, new HttpHeaders(), apiError.getStatus()); - } - - private Exception getInitialException(Exception ex) { - while (ex.getCause() != null) { - ex = (Exception) ex.getCause(); - } - return ex; - } -} - diff --git a/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..3161f0e18cc5be5e005110b135161879910a6b8b --- /dev/null +++ b/application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java @@ -0,0 +1,76 @@ +package org.fuseri.modulecertificate.exceptions; + +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.util.UrlPathHelper; + +import java.util.List; + +@ControllerAdvice +public class RestResponseEntityExceptionHandler { + private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper(); + + /** + * Handle ResourceNotFoundException exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {EntityNotFoundException.class}) + public ResponseEntity<ApiError> handleNotFoundError(EntityNotFoundException ex, HttpServletRequest request) { + ApiError error = new ApiError( + HttpStatus.NOT_FOUND, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle Validation exceptions + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler(value = {MethodArgumentNotValidException.class, HttpMessageNotReadableException.class}) + public ResponseEntity<ApiError> handleValidationErrors(MethodArgumentNotValidException ex, HttpServletRequest request) { + List<ApiSubError> subErrors = ex.getBindingResult().getFieldErrors() + .stream() + .map(e -> (ApiSubError) new ApiValidationError(e.getObjectName(), e.getField(), e.getRejectedValue(), e.getDefaultMessage())) + .toList(); + ApiError error = new ApiError( + HttpStatus.BAD_REQUEST, + subErrors, + ex, + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + /** + * Handle exceptions not matched by above handler methods + * + * @param ex exception + * @param request request + * @return response entity + */ + @ExceptionHandler({Exception.class}) + public ResponseEntity<ApiError> handleAll(final Exception ex, HttpServletRequest request) { + final ApiError error = new ApiError( + HttpStatus.INTERNAL_SERVER_ERROR, + ExceptionUtils.getRootCause(ex), + URL_PATH_HELPER.getRequestUri(request)); + return buildResponseEntity(error); + } + + private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) { + return new ResponseEntity<>(apiError, apiError.getStatus()); + } +}