From f0ab89c03bf84f1158ea794bbb26e4e89bfa4cd0 Mon Sep 17 00:00:00 2001
From: evilimkova <evilimkova@onpointserv.com>
Date: Sun, 30 Apr 2023 11:10:22 +0200
Subject: [PATCH] Adding extended error handling

---
 .../exceptions/ApiError.java                  | 69 ++++++-----------
 .../exceptions/ApiSubError.java               |  4 +
 .../exceptions/ApiValidationError.java        | 19 +++++
 .../CustomRestGlobalExceptionHandling.java    | 60 ---------------
 .../RestResponseEntityExceptionHandler.java   | 76 +++++++++++++++++++
 5 files changed, 121 insertions(+), 107 deletions(-)
 create mode 100644 application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiSubError.java
 create mode 100644 application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/ApiValidationError.java
 delete mode 100644 application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/CustomRestGlobalExceptionHandling.java
 create mode 100644 application/module-certificate/src/main/java/org/fuseri/modulecertificate/exceptions/RestResponseEntityExceptionHandler.java

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 9f8a19a7..617a0da1 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 00000000..f5ad2d92
--- /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 00000000..43b6acd7
--- /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 8284014b..00000000
--- 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 00000000..3161f0e1
--- /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());
+    }
+}
-- 
GitLab