Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: ErrorResponse 응답 구조 변경 #62

Merged
merged 7 commits into from
Oct 1, 2024
9 changes: 6 additions & 3 deletions src/main/java/org/chzz/market/common/error/ErrorResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import lombok.Builder;

@Builder
public record ErrorResponse(String message,
public record ErrorResponse(String name,
String[] message,
int status) {
public static ErrorResponse from(final ErrorCode errorCode) {
return ErrorResponse.builder()
.name(errorCode.name())
.status(errorCode.getHttpStatus().value())
.message(errorCode.getMessage())
.message(new String[]{errorCode.getMessage()})
.build();
}

public static ErrorResponse of(final ErrorCode errorCode, final String detailedErrorMessage) {
public static ErrorResponse of(final ErrorCode errorCode, final String[] detailedErrorMessage) {
return ErrorResponse.builder()
.name(errorCode.name())
.status(errorCode.getHttpStatus().value())
.message(detailedErrorMessage)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum GlobalErrorCode implements ErrorCode {
INVALID_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid request parameter"),
UNSUPPORTED_PARAMETER_TYPE(HttpStatus.BAD_REQUEST, "Unsupported type of parameter included"),
UNSUPPORTED_PARAMETER_NAME(HttpStatus.BAD_REQUEST, "Unsupported name of parameter included"),
VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "Validation failed"),
AUTHENTICATION_REQUIRED(HttpStatus.UNAUTHORIZED, "Authentication is required"),
ACCESS_DENIED(HttpStatus.FORBIDDEN, "Access is denied"),
UNSUPPORTED_SORT_TYPE(HttpStatus.BAD_REQUEST,"Unsupported type of sort" ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import static org.chzz.market.common.error.GlobalErrorCode.EXTERNAL_API_ERROR;

import java.util.stream.Collectors;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.chzz.market.common.error.ErrorCode;
import org.chzz.market.common.error.ErrorResponse;
Expand All @@ -14,7 +14,6 @@
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand All @@ -31,92 +30,119 @@
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String LOG_FORMAT = "\nException Class = {}\nResponse Code = {}\nMessage = {}";

// 1. 커스텀 예외 핸들러

/**
* 비즈니스 로직에서 정의한 예외가 발생할 때 처리
*/
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<?> handleBusinessException(BusinessException e) {
ErrorCode errorCode = e.getErrorCode();
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

/**
* 시스템 전역에서 발생하는 예외를 처리
*/
@ExceptionHandler(GlobalException.class)
protected ResponseEntity<?> handleGlobalException(GlobalException e) {
GlobalErrorCode errorCode = e.getErrorCode();
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

/**
* 외부 API 호출 시 발생하는 WebClient 관련 예외 처리
*/
@ExceptionHandler(WebClientResponseException.class)
protected ResponseEntity<?> handleWebClientResponseException(final WebClientResponseException exception) {
logException(exception, EXTERNAL_API_ERROR, exception.getResponseBodyAsString());
return handleExceptionInternal(EXTERNAL_API_ERROR);
}

/**
* 요청 매개변수의 타입이 잘못된 경우 발생
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
GlobalErrorCode errorCode = GlobalErrorCode.UNSUPPORTED_PARAMETER_TYPE;
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

@Nullable
// 2. ResponseEntityExceptionHandler에서 오버라이드된 핸들러

/**
* 필수 요청 매개변수가 누락된 경우 발생
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(
final MissingServletRequestParameterException e,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
@NonNull final MissingServletRequestParameterException e,
@NonNull final HttpHeaders headers,
@NonNull final HttpStatusCode status,
@NonNull final WebRequest request) {
GlobalErrorCode errorCode = GlobalErrorCode.UNSUPPORTED_PARAMETER_NAME;
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

/**
* 존재하지 않는 URL 요청 시 발생
*/
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException e,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
protected ResponseEntity<Object> handleNoHandlerFoundException(@NonNull final NoHandlerFoundException e,
@NonNull final HttpHeaders headers,
@NonNull final HttpStatusCode status,
@NonNull final WebRequest request) {
final GlobalErrorCode errorCode = GlobalErrorCode.RESOURCE_NOT_FOUND;
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

@Nullable
/**
* 멀티파트 요청의 필수 파트가 누락된 경우 발생
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex,
HttpHeaders headers, HttpStatusCode status,
WebRequest request) {
protected ResponseEntity<Object> handleMissingServletRequestPart(@NonNull MissingServletRequestPartException ex,
@NonNull HttpHeaders headers,
@NonNull HttpStatusCode status,
@NonNull WebRequest request) {
final GlobalErrorCode errorCode = GlobalErrorCode.INVALID_REQUEST_PARAMETER;
logException(ex, errorCode);
return handleExceptionInternal(errorCode);
}

/**
* @param e 비즈니스 로직상 발생한 커스텀 예외
* @return 커스텀 예외 메세지와 상태를 담은 {@link ResponseEntity}
* 요청의 유효성 검사 실패 시 발생
*/
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<?> handleBusinessException(BusinessException e) {
ErrorCode errorCode = e.getErrorCode();
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

@ExceptionHandler(GlobalException.class)
protected ResponseEntity<?> handleGlobalException(GlobalException e) {
GlobalErrorCode errorCode = e.getErrorCode();
logException(e, errorCode);
return handleExceptionInternal(errorCode);
}

@ExceptionHandler(WebClientResponseException.class)
protected ResponseEntity<?> handleWebClientResponseException(final WebClientResponseException exception) {
logException(exception, EXTERNAL_API_ERROR, exception.getResponseBodyAsString());
return handleExceptionInternal(EXTERNAL_API_ERROR);
}

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatusCode status,
WebRequest request) {
protected ResponseEntity<Object> handleMethodArgumentNotValid(@NonNull MethodArgumentNotValidException ex,
@NonNull HttpHeaders headers,
@NonNull HttpStatusCode status,
@NonNull WebRequest request) {

GlobalErrorCode errorCode = GlobalErrorCode.INVALID_REQUEST_PARAMETER;
String detailedErrorMessage = ex.getBindingResult().getFieldErrors()
GlobalErrorCode errorCode = GlobalErrorCode.VALIDATION_FAILED;
String[] detailedErrorMessages = ex.getBindingResult().getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining("; "));
logException(ex, errorCode, detailedErrorMessage);
.toArray(String[]::new);
logException(ex, errorCode, detailedErrorMessages);

ErrorResponse errorResponse = ErrorResponse.of(errorCode, detailedErrorMessage);
ErrorResponse errorResponse = ErrorResponse.of(errorCode, detailedErrorMessages);
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(errorResponse);
}

/**
* 읽을 수 없는 HTTP 메시지가 수신된 경우 발생
*/
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
protected ResponseEntity<Object> handleHttpMessageNotReadable(@NonNull HttpMessageNotReadableException ex,
@NonNull HttpHeaders headers,
@NonNull HttpStatusCode status,
@NonNull WebRequest request) {

GlobalErrorCode errorCode = GlobalErrorCode.INVALID_REQUEST_PARAMETER;
logException(ex, errorCode, ex.getMessage());
Expand All @@ -127,23 +153,44 @@ protected ResponseEntity<Object> handleHttpMessageNotReadable(
.body(errorResponse);
}

/**
* 예외 처리 응답 생성 메서드
*/
private ResponseEntity<Object> handleExceptionInternal(ErrorCode errorCode) {
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(ErrorResponse.from(errorCode));
}

// 3. 예외 로깅 메서드들

/**
* 예외 정보를 로깅 (ErrorCode 메시지 사용)
*/
private void logException(final Exception e, final ErrorCode errorCode) {
log.error(LOG_FORMAT,
e.getClass(),
errorCode.getHttpStatus().value(),
errorCode.getMessage());
}

/**
* 예외 정보를 로깅 (단일 메시지 사용)
*/
private void logException(final Exception e, final ErrorCode errorCode, final String message) {
log.error(LOG_FORMAT,
e.getClass(),
errorCode.getHttpStatus().value(),
message);
}

/**
* 예외 정보를 로깅 (다중 메시지 사용)
*/
private void logException(final Exception e, final ErrorCode errorCode, final String[] message) {
log.error(LOG_FORMAT,
e.getClass(),
errorCode.getHttpStatus().value(),
String.join("; ", message));
}
}

This file was deleted.

Loading
Loading