본문 바로가기
Spring boot

Error Response 작업

by Subi 2023. 9. 5.

ErrorResponse

@Getter
@EqualsAndHashCode
public class ErrorResponse<T> {

    private int code;

    @JsonIgnore
    private HttpStatus status;

    private String message;

    private T content;

    public ErrorResponse(ErrorCode errorCode, String status, T content) {
        this.code = errorCode.getCode();
        this.status = errorCode.getStatus();
        this.message = status;
        this.content = content;
    }

    public ErrorResponse(ErrorCode errorCode, T content) {
        this.code = errorCode.getCode();
        this.status = errorCode.getStatus();
        this.message = errorCode.getStatus().name();
        this.content = content;
    }

    public ErrorResponse(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.status = errorCode.getStatus();
        this.message = errorCode.getStatus().name();
        this.content = (T) errorCode.getContent();
    }
}

ErrorResponse는 기본적으로 ResponseEntity로 감싸서 출력 되게 된다.

  • 기본적인 포맷은 ResponseResult 와 동일하게 맞췄다.
  • code에 대한 값은 HttpStatus에 대한 status int 값으로 출력 되게 된다.
  • status에 대한 값은 ResponseEntity로 출력 될 때의 header값을 결정하게 된다.
    • 또한 에러에 대한 HttpStatus 출력 값을 표기해주게 된다.
    • JsonIgnore을 사용해 Response Body에서 Json 으로는 표시 되지 않는다.
  • message에 대한 값은 Vaild 에서 List 형태로 들어가기 때문에 T (제네릭) 의 형태로 들어가게 된다.
    • message를 따로 Parameter로 받지 않으면 기존 errorCode에 있는 Default Message 가 출력 되게 된다.
    • message를 따로 Parameter로 받는 경우는 Parameter의 message가 출력 되게 된다.

ErrorCode

@Getter
@AllArgsConstructor
public enum ErrorCode {

    /* 2xx (통신은 성공) : 통신은 성공했으나 데이터 상의 오류가 있습니다.  */
    NO_CONTENTS(204, HttpStatus.NO_CONTENT, "요청한 데이터가 존재하지 않습니다."),
    NO_RESISTER(250, HttpStatus.OK,"등록되지 않은 계정입니다."),            //다우 DB O , KeyClock X
    NO_RESISTER_DATA(260, HttpStatus.OK,"등록되지 않은 계정입니다."),       //다우 DB X , KeyClock X

    /* 3xx (리다이렉션) : 요청을 완료하기 위해 추가 작업이 필요합니다.  */
    MULTIPLE_CHOICES(300, HttpStatus.MULTIPLE_CHOICES, "여러 가능한 응답이 있습니다."),
    MOVED_PERMANENTLY(301, HttpStatus.MOVED_PERMANENTLY, "요청된 리소스에 대해 URI가 변경되었습니다."),
    NOT_MODIFIED(304, HttpStatus.NOT_MODIFIED, "리소스가 변경되지 않았습니다."),
    UPDATE_PASSWORD(304, HttpStatus.NOT_MODIFIED , "비밀번호 수정이 필요합니다."),

    /* 4xx (클라이언트 오류) : 클라이언트의 요청에 오류가 있습니다.  */
    BAD_REQUEST(400, HttpStatus.BAD_REQUEST, "서버가 요청을 이해할 수 없습니다."),
    MISSING_PARAMETER(400, HttpStatus.BAD_REQUEST, "파라미터가 잘못 되었습니다."),
    UNAUTHORIZED(401, HttpStatus.UNAUTHORIZED, "인증이 필요합니다."),
    LOGIN_FAIL(401, HttpStatus.UNAUTHORIZED, "로그인 정보가 올바르지 않습니다."),
    FORBIDDEN(403, HttpStatus.FORBIDDEN, "서버가 요청을 이해하였으나 그 요청을 수행하는 것을 거부합니다."),
    NOT_FOUND(404, HttpStatus.NOT_FOUND, "서버가 요청한 페이지를 찾을 수 없습니다."),
    METHOD_NOT_ALLOWED(405, HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 메서드입니다."),
    CONFLICT(409, HttpStatus.CONFLICT, "중복된 리소스가 존재합니다."),
    TOO_MANY_REQUESTS(429, HttpStatus.TOO_MANY_REQUESTS, "사용자가 일정 시간 동안 너무 많은 요청을 보냈습니다."),

    /* 5xx (서버 오류) : 서버가 유효한 요청을 충족시키지 못했습니다.*/
    INTERNAL_SERVER_ERROR(500, HttpStatus.INTERNAL_SERVER_ERROR, "서버에 오류가 발생하여 요청을 완료할 수 없습니다."),
    NOT_IMPLEMENTED(501, HttpStatus.NOT_IMPLEMENTED, "서버에 요청한 기능이 구현되어 있지 않습니다."),
    BAD_GATEWAY(502, HttpStatus.BAD_GATEWAY, "서버가 게이트웨이나 프록시 역할을 하면서 잘못된 응답을 받았습니다."),
    SERVICE_UNAVAILABLE(503, HttpStatus.SERVICE_UNAVAILABLE, "서버가 일시적으로 사용할 수 없습니다."),
    GATEWAY_TIMEOUT(504, HttpStatus.GATEWAY_TIMEOUT, "게이트웨이나 프록시 서버에서 요청을 기다리는 동안 시간이 초과되었습니다."),
    DATA_ACCESS(580, HttpStatus.INTERNAL_SERVER_ERROR, "사용자 설정 에러");

    private int code;
    private HttpStatus status;
    private String content;
}

1. 데이터가 존재하지 않을 때

  • NoContentsException
    • ErrorCode : ErrorCode.No_CONTENTS
@Getter
public class NoContentsException extends RuntimeException{

    private ErrorCode errorCode;

    public NoContentsException(String message){
        super(message);
        this.errorCode = ErrorCode.NO_CONTENTS;
    }

    public NoContentsException(){
        this.errorCode = ErrorCode.NO_CONTENTS;
    }
}
  • Exception Handler
/**
 * 204 : 요청한 데이터가 존재하지 않을시
 */
@ExceptionHandler(NoContentsException.class)
protected ResponseResult<Object> noContentsException(NoContentsException exception){
    ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode());
    if(StringUtils.hasText(exception.getMessage())){
        errorResponse = new ErrorResponse(exception.getErrorCode(), exception.getMessage());
    }
    return ResponseResult.response(errorResponse.getContent(),ResponseStatus.NO_CONTENTS);
}
  • Response
    • Header : 200
{
  "code": 204,
  "message": "NO_CONTENTS",
  "content": "요청한 데이터가 존재하지 않습니다."
}

2. 다우 DB 에는 존재하지만 KeyClock에는 등록되지 않은 계정

  • NoResisterException
    • ErrorCode : ErrorCode.NO_RESISTER
@Getter
public class NoResisterException extends RuntimeException{

    private ErrorCode errorCode;

    public NoResisterException(){
        this.errorCode = ErrorCode.NO_RESISTER;
    }

}
  • Exception Handler
/**
* 250 : 다우 O , KeyClock X 등록되지 않은 계정
*/
@ExceptionHandler(NoResisterException.class)
protected ResponseEntity<Object> noResisterException(NoResisterException exception){
  ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),"NO_RESISTER", exception.getErrorCode().getContent());
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 200
{
  "code": 250,
  "message": "NO_RESISTER",
  "content": "등록되지 않은 계정입니다."
}

3. 다우 DB 에는 존재하지않고 KeyClock에는 등록되지 않은 계정

  • NoResisterDataException
    • ErrorCode : ErrorCode.NO_RESISTER_DATA
@Getter
public class NoResisterDataException extends RuntimeException{

    private ErrorCode errorCode;

    public NoResisterDataException(){
        this.errorCode = ErrorCode.NO_RESISTER_DATA;
    }

}
  • Exception Handler
/**
 * 260 : 다우 X , KeyClock X 등록되지 않은 계정
 */
@ExceptionHandler(NoResisterDataException.class)
protected ResponseEntity<Object> noResisterDataException(NoResisterDataException exception){
    ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),"NO_RESISTER", exception.getErrorCode().getContent());
    return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 200
{
  "code": 260,
  "message": "NO_RESISTER",
  "content": "등록되지 않은 계정입니다."
}

4. 비밀번호 수정이 필요한 경우

  • UpdatePasswordException
    • ErrorCode : ErrorCode.UPDATE_PASSWORD
@Getter
public class UpdatePasswordException extends RuntimeException{

    private ErrorCode errorCode;

    public UpdatePasswordException(String message){
        super(message);
        this.errorCode = ErrorCode.UPDATE_PASSWORD;
    }
}
  • Exception Handler
/**
 * 304 : 비밀번호 수정 필요
 */
@ExceptionHandler(UpdatePasswordException.class)
protected ResponseEntity<Object> updatePasswordException(UpdatePasswordException exception){
    ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(), exception.getMessage());
    return new ResponseEntity<>(errorResponse, HttpStatus.OK);
}
  • Response
    • Header : 200
{
  "code": 304,
  "message": "UPDATE_PASSWORD",
  "content": "토큰 정보"
}

5. 올바르지 않은 Parameter

  • MissingServletRequestParameterException
    • ErrorCode : ErrorCode.MISSING_PARAMETER
  • Exception Handler
/**
* 400 : Parameter Error
*/
@Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.MISSING_PARAMETER);
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 400
{
  "code": 400,
  "message": "MISSING_PARAMETER",
  "content": "토큰 정보"
}

6. 로그인 정보가 다른 경우 (로그인 실패)

  • LoginFailException
    • ErrorCode : ErrorCode.LOGIN_FAIL
@Getter
public class LoginFailException extends RuntimeException{

    private ErrorCode errorCode;

    public LoginFailException(){
        this.errorCode = ErrorCode.LOGIN_FAIL;
    }
}
  • Exception Handler
/**
* 401 : 로그인 정보가 다른 경우 (로그인 실패)
*/
@ExceptionHandler(LoginFailException.class)
protected ResponseEntity<Object> loginFailException(LoginFailException exception){
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.LOGIN_FAIL);
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 401
{
  "code": 401,
  "message": "UNAUTHORIZED",
  "content": "로그인 정보가 올바르지 않습니다."
}

7. 서버가 요청한 자원을 찾지 못한 경우

  • NoHandlerFoundException
    • ErrorCode : ErrorCode.NOT_FOUND
  • Exception Handler
/**
* 404 : 서버가 요청한 자원을 찾지 못하는 경우
*/
@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.NOT_FOUND);
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 404
{
  "code": 404,
  "message": "NOT_FOUND",
  "content": "서버가 요청한 페이지를 찾을 수 없습니다."
}

8. 지원하지 않는 HTTP 메서드로 요청

  • HttpRequestMethodNotSupportedException
    • ErrorCode : ErrorCode.METHOD_NOT_ALLOWED
  • Exception Handler
/**
* 405 : 지원하지 않는 HTTP 메서드로 요청
*/
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {

  StringBuilder builder = new StringBuilder();
  builder.append(ex.getMethod());
  builder.append(" 는 지원하지 않는 메서드입니다. ");

  Set<HttpMethod> supportedHttpMethods = ex.getSupportedHttpMethods();
  if (supportedHttpMethods != null) {
      supportedHttpMethods.forEach(t -> builder.append(t).append(" "));
  }
  builder.append("으로 요청해주시기 바랍니다");

  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.METHOD_NOT_ALLOWED, builder.toString());
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 405
{
    "code": 405,
    "message": "METHOD_NOT_ALLOWED",
    "content": "POST 는 지원하지 않는 메서드입니다. GET 으로 요청해주시기 바랍니다"
}

9. 중복에 대한 에러

  • DuplicateException
    • ErrorCode : ErrorCode.CONFICT
@Getter
public class DuplicateException extends RuntimeException{

    private ErrorCode errorCode;

    public DuplicateException(String message){
        super(message);
        this.errorCode = ErrorCode.CONFLICT;
    }
}
  • Exception Handler
/**
* 409 : 중복에 대한 에러
*/
@ExceptionHandler(DuplicateException.class)
protected ResponseEntity<ErrorResponse> duplicateException(DuplicateException exception){
  ErrorResponse errorResponse = new ErrorResponse(exception.getErrorCode(),exception.getMessage());
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 409
{
  "code": 409,
  "message": "CONFLICT",
  "content": "TemplateName 이 중복되었습니다."
}

10. 유효성 검사 실패

  • ValidationResponse
    • ErrorCode : ErrorCode.VALIDATION
    • 유효성 검사에 실패한 객체를 List 에 담아 Content로 출력
@Getter
public class ValidationResponse {

    private String column;

    private ValidationType type;

    private String message;

    public ValidationResponse(String column, String type, String message) {
        this.column = column;
        this.type = getType(type);
        this.message = message;
    }

    private ValidationType getType(String type) {
        Map<String, ValidationType> typeMap = new HashMap<>();
        typeMap.put("NotNull", ValidationType.EMPTY);
        typeMap.put("NotBlank", ValidationType.EMPTY);
        typeMap.put("Pattern", ValidationType.PATTERN);
        typeMap.put("EnumValid" , ValidationType.ENUM_VALID);
        return typeMap.getOrDefault(type, ValidationType.EMPTY);
    }
}
public enum ValidationType {
    EMPTY , PATTERN , ENUM_VALID
}
  • Exception Handler
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
    BindingResult bindingResult = ex.getBindingResult();
    List<FieldError> fieldErrors = bindingResult.getFieldErrors();
    List<ValidationResponse> errorList = new ArrayList<>();
    for (FieldError fieldError : fieldErrors) {
        ValidationResponse validationResponse = new ValidationResponse(fieldError.getField(), fieldError.getCode(),fieldError.getDefaultMessage());
        errorList.add(validationResponse);
    }
    ErrorResponse errorResponse = new ErrorResponse(ErrorCode.VALIDATION,"VALIDATION_ERROR", errorList);
    return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 400
{
  "code": 410,
  "message": "BAD_REQUEST",
  "content": [
    {
      "column": "sendLocation",
      "type": "ENUM_VALID",
      "message": "유효하지 않은 값입니다 (유효한 값 :MEMBERSHIP, BILLING, CRUELLA, CS, OTHER)"
    },
    {
      "column": "dmCodeName",
      "type": "EMPTY",
      "message": "공백일 수 없습니다"
    },
    {
      "column": "dmCode",
      "type": "PATTERN",
      "message": "올바르지 않은 정규식 입니다."
    }
  ]
}

11. 특정하지 않은 모든 에러

  • Exception
    • ErrorCode : ErrorCode.INTERNAL_SERVER_ERROR
  • Exception Handler
    • Log 로 System Error 확인
    • 특정하지 않은 모든 에러 Handler
/**
* 500 : 특정하지 않은 모든 에러
*/
@ExceptionHandler(value = Exception.class)
protected ResponseEntity<Object> handleAll(Exception exception) {
  log.info("exception.getMessage : "+ exception.getMessage());
  log.info("exception.getLocalizedMessage : "+ exception.getLocalizedMessage());
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR);
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 500
{
    "code": 500,
    "message": "INTERNAL_SERVER_ERROR",
    "content": "서버에 오류가 발생하여 요청을 완료할 수 없습니다."
}

12. SQL에 대한 에러

  • DataAccessException
    • ErrorCode : ErrorCode.DATA_ACCESS
  • Exception Handler
/**
* 550 : SQL 에 대한 에러
*/
@ExceptionHandler(DataAccessException.class)
protected ResponseEntity<Object> dataAccessException(){
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.DATA_ACCESS);
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 500
{
    "code": 550,
    "message": "INTERNAL_SERVER_ERROR",
    "content": "SQL를 실행 하는 중 에러가 발생했습니다."
}

13. 사용자 설정 에러

  • IllegalArgumentException
    • ErrorCode : ErrorCode.CUSTOM_ERROR
  • Exception Handler
/**
* 580 : 사용자 설정 에러
*/
@ExceptionHandler(value = IllegalArgumentException.class)
protected ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException exception) {
  ErrorResponse errorResponse = new ErrorResponse(ErrorCode.CUSTOM_ERROR, exception.getMessage());
  return new ResponseEntity<>(errorResponse, errorResponse.getStatus());
}
  • Response
    • Header : 500
{
    "code": 580,
    "message": "INTERNAL_SERVER_ERROR",
    "content": "사용자 설정 에러"
}

'Spring boot' 카테고리의 다른 글

i18N 다국어 API 개발  (0) 2023.08.14
Request Test  (0) 2023.08.01
EnumVaild Anotation  (0) 2023.08.01
ResponseEntity Code Convention  (0) 2023.07.26
ResponseResult 개발 진행  (0) 2023.07.26

댓글