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

feat: YSL-13 RootExceptionHandler 및 Problem 적용 #10

Merged
merged 7 commits into from
Jan 6, 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
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")

//flyway
implementation("org.flywaydb:flyway-core")
Expand All @@ -42,7 +45,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