From d383ecf85cdf0988ae16a6d42bc52292e8434804 Mon Sep 17 00:00:00 2001 From: Zinzo <42106799+zinzoddari@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:45:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20YSL-13=20RootExceptionHandler=20?= =?UTF-8?q?=EB=B0=8F=20Problem=20=EC=A0=81=EC=9A=A9=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 에러 공통화를 위한 problme 도입 * feat: 500 에러 추가 * feat: 유효성 검증에 실패한 내용 관련 400 에러 추가 * chore: 마지막 라인 추가 * chore: build.gradle 순서 정리 * refactor: Status import 제거 * refactor: foreach문 map으로 개선 --- build.gradle.kts | 8 +- .../global/exception/InvalidResponse.java | 19 ++++ .../exception/RootExceptionHandler.java | 93 +++++++++++++++++++ src/main/resources/application.yml | 6 ++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/idiot/yesslave/global/exception/InvalidResponse.java create mode 100644 src/main/java/org/idiot/yesslave/global/exception/RootExceptionHandler.java diff --git a/build.gradle.kts b/build.gradle.kts index 92d88c4..a48692c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-validation") + + developmentOnly("org.springframework.boot:spring-boot-devtools") //swagger implementation("org.springdoc:springdoc-openapi-ui:1.7.0") @@ -38,7 +41,10 @@ dependencies { compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") - developmentOnly("org.springframework.boot:spring-boot-devtools") + //problem + implementation("org.zalando:problem-spring-web-starter:0.27.0") + + //test testImplementation("org.springframework.boot:spring-boot-starter-test") } diff --git a/src/main/java/org/idiot/yesslave/global/exception/InvalidResponse.java b/src/main/java/org/idiot/yesslave/global/exception/InvalidResponse.java new file mode 100644 index 0000000..53ee574 --- /dev/null +++ b/src/main/java/org/idiot/yesslave/global/exception/InvalidResponse.java @@ -0,0 +1,19 @@ +package org.idiot.yesslave.global.exception; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "입력된 값에 대한 유효성 검사에 실패했을 경우의 응답값") +public class InvalidResponse { + @Schema(description = "오류가 발생한 field명", example = "verificationCode") + private String field; + + @Schema(description = "오류 메세지", example = "확인번호를 입력해주세요.") + private String message; + + @Schema(description = "잘못 입력된 field 입력 값", example = "ABCDE") + private Object rejectValue; +} diff --git a/src/main/java/org/idiot/yesslave/global/exception/RootExceptionHandler.java b/src/main/java/org/idiot/yesslave/global/exception/RootExceptionHandler.java new file mode 100644 index 0000000..457afb0 --- /dev/null +++ b/src/main/java/org/idiot/yesslave/global/exception/RootExceptionHandler.java @@ -0,0 +1,93 @@ +package org.idiot.yesslave.global.exception; + +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.zalando.problem.Problem; +import org.zalando.problem.Status; + +import javax.validation.ConstraintViolationException; +import java.util.List; + +@Slf4j +@ControllerAdvice +@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class) +public class RootExceptionHandler { + @ExceptionHandler(Exception.class) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = { + @Content(schema = @Schema(implementation = Problem.class)) + }) + public ResponseEntity exceptionHandler(Exception e) { + + log.error("[ 500 ERROR ] : ", e); + + Problem problem = Problem.builder() + .withStatus(Status.INTERNAL_SERVER_ERROR) + .withTitle(Status.INTERNAL_SERVER_ERROR.getReasonPhrase()) + .withDetail(e.getMessage()) + .build(); + + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(problem); + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + @ApiResponse(responseCode = "400", description = "Bad Request", content = { + @Content(schema = @Schema(implementation = InvalidResponse.class)) + }) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { + log.error("[ 400 ERROR ] : ", e); + + BindingResult bindingResult = e.getBindingResult(); + + List responses = bindingResult.getFieldErrors().stream() + .map(fieldError -> new InvalidResponse(fieldError.getField() + , fieldError.getDefaultMessage() + , fieldError.getRejectedValue()) + ).toList(); + + Problem problem = Problem.builder() + .withStatus(Status.BAD_REQUEST) + .withTitle(Status.BAD_REQUEST.getReasonPhrase()) + .with("parameters", responses) + .build(); + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(problem); + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + @ApiResponse(responseCode = "400", description = "Bad Request", content = { + @Content(schema = @Schema(implementation = InvalidResponse.class)) + }) + @ExceptionHandler(ConstraintViolationException.class) + public Problem constraintViolationExceptionHandler(ConstraintViolationException e) { + log.error("[ 400 ERROR ] : ", e); + + List responses = e.getConstraintViolations().stream() + .map(fieldError -> new InvalidResponse(fieldError.getPropertyPath().toString() + , fieldError.getMessage() + , fieldError.getInvalidValue()) + ).toList(); + + return Problem.builder() + .withStatus(Status.BAD_REQUEST) + .withTitle(Status.BAD_REQUEST.getReasonPhrase()) + .with("parameters", responses) + .build(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2308b03..f3f9fb7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -15,3 +15,9 @@ spring: format_sql: true show_sql: true dialect: org.hibernate.dialect.MySQL8Dialect + web: + resources: + add-mappings: false + mvc: + throw-exception-if-no-handler-found: true + \ No newline at end of file