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

[CHORE] 커스텀 에러 및 핸들러 구현 #35

Merged
merged 20 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.debatetimer.controller.exception;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

@Schema(description = "예외 객체")
public record ErrorResponse(
@Schema(description = "사용자에게 보여줄 예외 메시지")
@NotBlank
String message
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.debatetimer.controller.exception.custom;

import com.debatetimer.controller.exception.errorcode.ClientErrorCode;

public class DTClientErrorException extends DTException {

public DTClientErrorException(ClientErrorCode clientErrorCode) {
super(clientErrorCode.getMessage(), clientErrorCode.getStatus());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.debatetimer.controller.exception.custom;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public abstract class DTException extends RuntimeException {

private final HttpStatus httpStatus;

public DTException(String message, HttpStatus httpStatus) {
super(message);
this.httpStatus = httpStatus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.debatetimer.controller.exception.custom;

import com.debatetimer.controller.exception.errorcode.ServerErrorCode;

public class DTServerErrorException extends DTException {

public DTServerErrorException(ServerErrorCode serverErrorCode) {
super(serverErrorCode.getMessage(), serverErrorCode.getStatus());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.debatetimer.controller.exception.errorcode;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum ClientErrorCode implements ErrorCode {

FIELD_ERROR(HttpStatus.BAD_REQUEST, "입력이 잘못되었습니다."),
URL_PARAMETER_ERROR(HttpStatus.BAD_REQUEST, "입력이 잘못되었습니다."),
METHOD_ARGUMENT_TYPE_MISMATCH(HttpStatus.BAD_REQUEST, "입력한 값의 타입이 잘못되었습니다."),
NO_RESOURCE_FOUND(HttpStatus.NOT_FOUND, "요청한 리소스를 찾을 수 없습니다."),
METHOD_NOT_SUPPORTED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."),
MEDIA_TYPE_NOT_SUPPORTED(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "허용되지 않은 미디어 타입입니다."),
ALREADY_DISCONNECTED(HttpStatus.BAD_REQUEST, "이미 클라이언트에서 요청이 종료되었습니다."),
;

private final HttpStatus status;
private final String message;

ClientErrorCode(HttpStatus status, String message) {
this.status = status;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.debatetimer.controller.exception.errorcode;

import org.springframework.http.HttpStatus;

public interface ErrorCode {

HttpStatus getStatus();

String getMessage();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.debatetimer.controller.exception.errorcode;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum ServerErrorCode implements ErrorCode {

INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류가 발생했습니다. 관리자에게 문의하세요."),
;

private final HttpStatus status;
private final String message;

ServerErrorCode(HttpStatus status, String message) {
this.status = status;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.debatetimer.controller.exception.handler;

import com.debatetimer.controller.exception.ErrorResponse;
import com.debatetimer.controller.exception.custom.DTClientErrorException;
import com.debatetimer.controller.exception.custom.DTServerErrorException;
import com.debatetimer.controller.exception.errorcode.ClientErrorCode;
import com.debatetimer.controller.exception.errorcode.ErrorCode;
import com.debatetimer.controller.exception.errorcode.ServerErrorCode;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorResponse> handleBindingException(BindException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.FIELD_ERROR);
}
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문] 전부 ResponseEntity를 사용하신 이유가 있나요?

Copy link
Contributor Author

@coli-geonwoo coli-geonwoo Dec 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 PR에서도 말했지만

  1. controller위 추상화 준위에 reponseentity가 올바르다고 생각하는 점
  2. 확장에 용이하다는 점
  3. 정적 메서드 제공으로 휴먼에러 방지에 용이하고 가독성이 좋다는 점

을 이유로 선호해요. 목요일 회의 이후에 명확히 컨벤션 정하고 반영하겠습니다!


@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.URL_PARAMETER_ERROR);
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.METHOD_ARGUMENT_TYPE_MISMATCH);
}

@ExceptionHandler(ClientAbortException.class)
public ResponseEntity<ErrorResponse> handleClientAbortException(ClientAbortException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.ALREADY_DISCONNECTED);
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException exception
) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.METHOD_NOT_SUPPORTED);
}

@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseEntity<ErrorResponse> handleHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException exception
) {
log.warn("message: {}", exception.getMessage());
return toResponse(ClientErrorCode.MEDIA_TYPE_NOT_SUPPORTED);
}

@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ErrorResponse> handleNoResourceFoundException(NoResourceFoundException exception) {
return toResponse(ClientErrorCode.NO_RESOURCE_FOUND);
}

@ExceptionHandler(DTClientErrorException.class)
public ResponseEntity<ErrorResponse> handleClientException(DTClientErrorException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(exception.getHttpStatus(), exception.getMessage());
}

@ExceptionHandler(DTServerErrorException.class)
public ResponseEntity<ErrorResponse> handleServerException(DTServerErrorException exception) {
log.warn("message: {}", exception.getMessage());
return toResponse(exception.getHttpStatus(), exception.getMessage());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception exception) {
log.error("exception: {}", exception);
return toResponse(ServerErrorCode.INTERNAL_SERVER_ERROR);
}

private ResponseEntity<ErrorResponse> toResponse(ErrorCode errorCode) {
return toResponse(errorCode.getStatus(), errorCode.getMessage());
}

private ResponseEntity<ErrorResponse> toResponse(HttpStatus httpStatus, String message) {
ErrorResponse errorResponse = new ErrorResponse(message);
return ResponseEntity.status(httpStatus)
.body(errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.debatetimer.swagger.annotation;

import com.debatetimer.controller.exception.ErrorResponse;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -8,14 +9,13 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.ProblemDetail;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(
responseCode = "400",
description = "클라이언트 입력 오류",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
public @interface ErrorCode400 {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.debatetimer.swagger.annotation;

import com.debatetimer.controller.exception.ErrorResponse;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -8,14 +9,13 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.ProblemDetail;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(
responseCode = "401",
description = "인증되지 않은 사용자",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
public @interface ErrorCode401 {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.debatetimer.swagger.annotation;

import com.debatetimer.controller.exception.ErrorResponse;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -8,13 +9,12 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.ProblemDetail;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(
responseCode = "404",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
public @interface ErrorCode404 {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.debatetimer.swagger.annotation;

import com.debatetimer.controller.exception.ErrorResponse;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -8,14 +9,13 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.http.ProblemDetail;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ApiResponse(
responseCode = "500",
description = "서버 오류",
content = @Content(schema = @Schema(implementation = ProblemDetail.class))
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
)
public @interface ErrorCode500 {

Expand Down
Loading