-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGlobalExceptionHandler.java
197 lines (161 loc) · 6.77 KB
/
GlobalExceptionHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package team05.integrated_feed_backend.exception;
import static org.springframework.http.HttpStatus.*;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import team05.integrated_feed_backend.common.BaseApiResponse;
import team05.integrated_feed_backend.exception.code.StatusCode;
import team05.integrated_feed_backend.exception.custom.BadRequestException;
import team05.integrated_feed_backend.exception.custom.BusinessException;
import team05.integrated_feed_backend.exception.custom.DataNotFoundException;
import team05.integrated_feed_backend.exception.custom.DuplicateResourceException;
import team05.integrated_feed_backend.exception.custom.ForbiddenException;
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 요청이 잘못된 경우
* ex) 필수 입력 값을 전달하지 않은 경우
**/
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler({BadRequestException.class})
public BaseApiResponse<Void> handleBadRequestException(BadRequestException e) {
log.warn(e.getMessage(), e);
return BaseApiResponse.of(getStatusCodeFromException(e));
}
/**
* 중복된 데이터 저장 요청을 정의한 에러인 경우
* ex) F 엔티티에 동일한 data를 넣은 경우
**/
@ResponseStatus(CONFLICT)
@ExceptionHandler({DuplicateResourceException.class})
public BaseApiResponse<Void> handleDuplicateResourceException(DuplicateResourceException e) {
log.warn(e.getMessage(), e);
return BaseApiResponse.of(getStatusCodeFromException(e));
}
/**
* 접근할 수 없는 데이터 요청을 정의한 에러인 경우
* ex) 사용자가 관리자 권한의 api 요청을 한 경우
**/
@ResponseStatus(FORBIDDEN)
@ExceptionHandler({ForbiddenException.class})
public BaseApiResponse<Void> handleForbiddenException(ForbiddenException e) {
log.warn(e.getMessage(), e);
return BaseApiResponse.of(getStatusCodeFromException(e));
}
/**
* 요청 시 필수 데이터이나, 전달하지 않은 요청을 정의한 에러인 경우
* ex) 게시물 id 전달했으나, 존재하지 않은 게시물인 경우
**/
@ResponseStatus(NOT_FOUND)
@ExceptionHandler({DataNotFoundException.class})
public BaseApiResponse<Void> handleDataNotFoundException(DataNotFoundException e) {
log.warn(e.getMessage(), e);
return BaseApiResponse.of(getStatusCodeFromException(e));
}
/**
* 비즈니스 로직 상 발생할 수 있는 정의한 에러인 경우
* ex) 하위 커스텀 외의 비즈니스 로직 예외인 경우
**/
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler({BusinessException.class})
public BaseApiResponse<Void> handleBusinessException(BusinessException e) {
log.warn(e.getMessage(), e);
return BaseApiResponse.of(getStatusCodeFromException(e));
}
/**
* 유효성 검사 실패 에러 잡는 경우
* ex) validation 으로 설정해둔 NotNull 필드가 null로 온 경우
**/
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public BaseApiResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.warn(e.getMessage(), e);
// validation 검사 실패 항목 보여주는 메세지
String errors = convertToErrorResponses(e);
return BaseApiResponse.of(BAD_REQUEST, errors);
}
/**
* 메서드 인자 타입이 맞지 않은 경우
* ex) Inteager type에 String type이 들어온 경우
**/
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public BaseApiResponse<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.warn(e.getMessage(), e);
// 타입이 맞지 않은 메서드 인자 보여주는 메세지
String message = formatMessageFrom(e);
return BaseApiResponse.of(BAD_REQUEST, message);
}
/**
* JSON 파싱 시, 잘못된 형식의 데이터를 넣은 경우
* ex) enumType 에 해당하지 않은 항목이 들어온 경우
**/
@ResponseStatus(BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public BaseApiResponse<Void> handleInvalidFormatException(InvalidFormatException e) {
log.warn(e.getMessage(), e);
// enum type 의 항목 보여주는 메세지
String message = formatMessageFrom(e);
return BaseApiResponse.of(BAD_REQUEST, message);
}
/**
* 정의하지 못한 내부 서버 에러인 경우
**/
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public BaseApiResponse<Void> handleUnknownException(Exception e) {
log.error(e.getMessage(), e);
StatusCode statusCode = StatusCode.findStatusCodeByNameSafe(e.getMessage(), StatusCode.INTERNAL_SERVER_ERROR);
return BaseApiResponse.of(statusCode);
}
/**
* validation 검사 실패한 항목 에러 메세지 만드는 메서드
*/
private String convertToErrorResponses(MethodArgumentNotValidException e) {
return e.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
}
/**
* 메서드 인자에 맞지 않은 자료형인 경우 에러 메세지 만드는 메서드
**/
private String formatMessageFrom(MethodArgumentTypeMismatchException e) {
String parameterName = e.getParameter().getParameterName();
return parameterName + "의 타입이 잘못되었습니다.";
}
/**
* enumType에 해당하는 항목이 없는 경우 에러 메세지 만드는 메서드
**/
private String formatMessageFrom(InvalidFormatException e) {
Class<?> targetType = e.getTargetType();
String enumTypeName = targetType.getSimpleName();
String validValues = Arrays.stream(targetType.getEnumConstants())
.map(enumConstant -> ((Enum<?>)enumConstant).name())
.collect(Collectors.joining(", "));
return enumTypeName + "는 [" + validValues + "] 중 하나여야 합니다.";
}
/**
* BusinessException 및 하위 커스텀 예외 클래스에서 StatusCode 내보내는 메서드
**/
private static StatusCode getStatusCodeFromException(BusinessException e) {
HttpStatus httpStatus = e.getStatusCode().getHttpStatus();
// 서버 에러인 경우 stack trace
if (httpStatus.value() == 500) {
e.printStackTrace();
}
return e.getStatusCode();
}
}