Skip to content

Commit

Permalink
feat: YSL-13 RootExceptionHandler 및 Problem 적용 (#10)
Browse files Browse the repository at this point in the history
* feat: 에러 공통화를 위한 problme 도입

* feat: 500 에러 추가

* feat: 유효성 검증에 실패한 내용 관련 400 에러 추가

* chore: 마지막 라인 추가

* chore: build.gradle 순서 정리

* refactor: Status import 제거

* refactor: foreach문 map으로 개선
  • Loading branch information
zinzoddari authored Jan 6, 2024
1 parent b3a7581 commit d383ecf
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 1 deletion.
8 changes: 7 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<Problem> 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<Problem> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error("[ 400 ERROR ] : ", e);

BindingResult bindingResult = e.getBindingResult();

List<InvalidResponse> 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<InvalidResponse> 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();
}
}
6 changes: 6 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit d383ecf

Please sign in to comment.