RestApiExceptionHandlerConfiguration.java

package com.tdmconsult.ete.exceptions;

import java.time.LocalDateTime;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException;
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.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;

@RestControllerAdvice
@Slf4j
public class RestApiExceptionHandlerConfiguration {

    static final String X_WITH_STACKTRACE = "X-With-Stacktrace";
    private static final String LOG_EXCEPTION_MESSAGE = "Handling API exception: ";

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleAllExceptions(final Exception ex, final WebRequest request) {
        final RestApiErrorResponse error = createErrorReturn(ex, request);

        return switch (ex) {
            case ResourceNotFoundException specificException -> {
                log.debug(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotFoundStatusResponse(error);
            }
            case IllegalAccessException specificException -> {
                log.debug(LOG_EXCEPTION_MESSAGE, specificException);
                yield getForbiddenStatusResponse(error);
            }
            case IllegalArgumentException specificException -> {
                log.error(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            case IllegalStateException specificException -> {
                log.error(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            case MissingServletRequestParameterException specificException -> {
                log.debug(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            case DataIntegrityViolationException specificException -> {
                log.error(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            case MethodArgumentNotValidException specificException -> {
                log.debug(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            case HttpMessageNotReadableException specificException -> {
                log.debug(LOG_EXCEPTION_MESSAGE, specificException);
                yield getNotAcceptableStatusResponse(error);
            }
            default -> {
                log.error(LOG_EXCEPTION_MESSAGE, ex);
                yield ResponseEntity
                        .status(HttpStatus.INTERNAL_SERVER_ERROR)
                        .body(error);
            }
        };
    }

    private RestApiErrorResponse createErrorReturn(final Exception ex, final WebRequest request) {
        final boolean showStrackTrace = showStackTrace(request);

        final List<String> details = ExceptionUtils.getThrowableList(ex).stream()
                .map(ExceptionUtils::getMessage)
                .toList();
        return RestApiErrorResponse.builder()
                .message(ex.getMessage())
                .type(ex.getClass().getSimpleName())
                .details(details)
                .stacktrace(showStrackTrace ? ExceptionUtils.getStackTrace(ex) : null)
                .timestamp(LocalDateTime.now())
                .build();
    }

    private boolean showStackTrace(final WebRequest request) {
        return StringUtils.isNotEmpty(request.getHeader(X_WITH_STACKTRACE))
                && Boolean.parseBoolean(request.getHeader(X_WITH_STACKTRACE));
    }

    private ResponseEntity<Object> getNotAcceptableStatusResponse(final RestApiErrorResponse error) {
        return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE)
                .body(error);
    }

    private ResponseEntity<Object> getForbiddenStatusResponse(final RestApiErrorResponse error) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(error);
    }

    private ResponseEntity<Object> getNotFoundStatusResponse(final RestApiErrorResponse error) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(error);
    }
}