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

feature/QFEED-55-exception-design #4

Merged
merged 3 commits into from
Nov 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.wsws.moduleapi.global.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.wsws.moduleapi.exception.ErrorInfo;
import com.wsws.moduleapi.exception.GlobalErrorCode;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@AllArgsConstructor
@JsonInclude(Include.NON_NULL)
public class ErrorResponse {

private String path;

private String errorCode;
private String message;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<FieldError> errors;


private LocalDateTime timeStamp;

private ErrorResponse(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}

public ErrorResponse(String path, String errorCode, String message) {
this.path = path;
this.errorCode = errorCode;
this.message = message;
this.timeStamp = LocalDateTime.now();
}

private ErrorResponse(String path, String errorCode, String message, List<FieldError> errors) {
this.path = path;
this.errorCode = errorCode;
this.message = message;
this.errors = errors;
this.timeStamp = LocalDateTime.now();
}

public static ErrorResponse of(ErrorInfo info, String path, String message) {
return new ErrorResponse(path, info.errorCode(), message);
}

public static ErrorResponse of(String errorCode, String message) {
return new ErrorResponse(errorCode, message);
}


public static ErrorResponse of(ErrorInfo info, String path, BindingResult bindingResult) {
return new ErrorResponse(path, info.errorCode(), info.message(), FieldError.of(bindingResult));
}

/**
* Validation ์—๋Ÿฌ์˜ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค
*/
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class FieldError {
private String field;
private String value;
private String reason;

private FieldError(final String field, final String value, final String reason) {
this.field = field;
this.value = value;
this.reason = reason;
}

private static List<FieldError> of(final BindingResult bindingResult) {
final List<org.springframework.validation.FieldError> fieldErrors = bindingResult.getFieldErrors();
return fieldErrors.stream().map(error -> new FieldError(
error.getField(),
error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(),
error.getDefaultMessage())).collect(Collectors.toList());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.wsws.moduleapi.global.exception;

import com.wsws.moduleapi.exception.CustomException;
import com.wsws.moduleapi.exception.ErrorInfo;
import com.wsws.moduleapi.exception.GlobalErrorCode;
import com.wsws.moduleapi.global.dto.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
@Slf4j
public class GlobalControllerAdvice {

/**
* Custom ์˜ˆ์™ธ
*/
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<?> customError(CustomException e, HttpServletRequest request) {
return ResponseEntity.status(e.getStatus())
.body(
ErrorResponse.of(
e.getErrorCode().getErrorInfo(),
request.getRequestURI(),
e.getMessage()));
}

/**
* javax.validation.Valid or @Validated ์œผ๋กœ binding error ๋ฐœ์ƒ์‹œ ๋ฐœ์ƒํ•œ๋‹ค.
* HttpMessageConverter ์—์„œ ๋“ฑ๋กํ•œ HttpMessageConverter binding ๋ชปํ• ๊ฒฝ์šฐ ๋ฐœ์ƒ
* ์ฃผ๋กœ @RequestBody, @RequestPart ์–ด๋…ธํ…Œ์ด์…˜์—์„œ ๋ฐœ์ƒ
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("handleMethodArgumentNotValidException", e);
return ResponseEntity.status(400)
.body(
ErrorResponse.of(
GlobalErrorCode.VALIDATION_ERROR.getErrorInfo(),
request.getRequestURI(),
e.getBindingResult()));
}

/**
* ๊ทธ๋ฐ–์— ๋ชจ๋“  ์˜ˆ์™ธ - 500์œผ๋กœ ์ฒ˜๋ฆฌ
*/
@ExceptionHandler(value = Exception.class)
public ResponseEntity<?> error(Exception e, HttpServletRequest request) {
log.error("error", e);
return ResponseEntity.status(500)
.body(
ErrorResponse.of(
ErrorInfo.of(500, "INTERNAL_SERVER_ERROR", "์•Œ์ˆ˜์—†๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ"),
request.getRequestURI(),
e.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wsws.moduleapi.constants;

public class ErrorCodeConstants {

public static final int BAD_REQUEST = 400;
public static final int UNAUTHORIZED = 401;
public static final int FORBIDDEN = 403;
public static final int NOT_FOUND = 404;
public static final int CONFLICT = 409;
public static final int INTERNAL_SERVER = 900;

public static final String BEARER = "Bearer ";

public static final String AUTHORIZATION_HEADER = "Authorization";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wsws.moduleapi.exception;

public class ApiException extends CustomException{
public ApiException(BaseErrorCode errorCode, String message) {
super(errorCode, message);
}

public ApiException(BaseErrorCode errorCode) {
super(errorCode, "Api Layer Exception");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wsws.moduleapi.exception;

public class ApplicationException extends CustomException{
public ApplicationException(BaseErrorCode errorCode, String message) {
super(errorCode, message);
}

public ApplicationException(BaseErrorCode errorCode) {
super(errorCode, "Application Layer Exception");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wsws.moduleapi.exception;

public interface BaseErrorCode {
ErrorInfo getErrorInfo();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.wsws.moduleapi.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public abstract class CustomException extends RuntimeException {

protected final BaseErrorCode errorCode;
protected final String sourceLayer;

public Integer getStatus() {
return errorCode.getErrorInfo().status();
}

public String getMessage() {
if (sourceLayer == null) {
return errorCode.getErrorInfo().message();
} else {
return String.format("%s - %s", sourceLayer, errorCode.getErrorInfo().message());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.wsws.moduleapi.exception;

public class DomainException extends CustomException {
public DomainException(BaseErrorCode errorCode, String message) {
super(errorCode, message);
}

public DomainException(BaseErrorCode errorCode) {
super(errorCode, "Domain Layer Exception");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wsws.moduleapi.exception;

public record ErrorInfo(Integer status, String errorCode, String message) {
public static ErrorInfo of(Integer status, String errorCode, String message) {
return new ErrorInfo(status, errorCode, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wsws.moduleapi.exception;

public class ExternalApiException extends CustomException{
public ExternalApiException(BaseErrorCode errorCode, String sourceLayer) {
super(errorCode, sourceLayer);
}

public ExternalApiException(BaseErrorCode errorCode) {
super(errorCode, "ExternalApi Layer Exception");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.wsws.moduleapi.exception;

import lombok.RequiredArgsConstructor;

import static com.wsws.moduleapi.constants.ErrorCodeConstants.*;

@RequiredArgsConstructor
public enum GlobalErrorCode implements BaseErrorCode {
VALIDATION_ERROR(400, "VALIDATION_ERROR_400", "์ž˜๋ชป๋œ ์ž…๋ ฅ ๊ฐ’์ž…๋‹ˆ๋‹ค."),
PERMISSION_DENIED(403, "PERMISSION_DENIED_401", "ํ•ด๋‹น API ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"),
FILE_NOT_FOUND(404, "FILE_NOT_FOUND_404", "ํ•ด๋‹น ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค"),
OTHER_SERVER_BAD_REQUEST(BAD_REQUEST, "AUTH_OTHER_400", "Other server bad request"),
OTHER_SERVER_UNAUTHORIZED(UNAUTHORIZED, "AUTH_OTHER_401", "Other server unauthorized"),
OTHER_SERVER_FORBIDDEN(FORBIDDEN, "AUTH_OTHER_403", "Other server forbidden"),
OTHER_SERVER_EXPIRED_TOKEN(BAD_REQUEST, "AUTH_OTHER_400", "Other server expired token"),
OTHER_SERVER_NOT_FOUND(BAD_REQUEST, "AUTH_OTHER_400", "Other server not found error"),
OTHER_SERVER_INTERNAL_SERVER_ERROR(
BAD_REQUEST, "FEIGN_400_6", "Other server internal server error");

private final Integer status;
private final String errorCode;
private final String message;

@Override
public ErrorInfo getErrorInfo() {
return ErrorInfo.of(status, errorCode, message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wsws.moduleapi.exception;

public class InfraException extends CustomException{
public InfraException(BaseErrorCode errorCode, String message) {
super(errorCode, message);
}

public InfraException(BaseErrorCode errorCode) {
super(errorCode, "Infra Layer Exception");
}
}
Loading