클라이언트에서 Body를 잘못 보냈을 때, 필드 네임이 담긴 메시지를 보내보자
public record AuthenticateEmailRequest(
@Email(message = "올바른 이메일을 입력해주세요.")
@NotBlank(message = "이메일 입력은 필수입니다.")
String email,
@Range(min = 100000, max = 999999, message = "6자리 숫자를 입력해주세요.")
Integer verificationCode
) {
}
public void authenticateEmailForSignup(@RequestBody @Valid AuthenticateEmailRequest request) {
emailAuthService.authenticateEmailForSignup(request.email(), request.verificationCode());
}
"verificationCode 필드가 잘못되었습니다"라는 내용의 커스텀 메시지를 반환하고 싶었습니다
{
"email":"test@gmail.com",
"verificationCode":"asdf"
}
Nestjs로 개발을 할 때는 이게 너무 당연하게 처리를 할 수 있어서 비슷한 처리가 스프링에 당연히 있겠지 라는 생각을 했었는데요
생각보다 잘 정리된 곳도 없었고, 어떤 식으로 메시지를 만들 수 있는지에 대한 정리가 되어있는 경우가 거의 없어서 직접 찾던 과정을 말씀드리려고 합니다
1. 에러를 핸들링하는 ExceptionHandler를 추가해 보자
이런 에러 메시지가 뜨면서, Internal Server Error 가 발생했는데요
핸들링이 전혀 되지 않는 것을 확인할 수 있었습니다
HttpMessageNotReadableException을 핸들링하는 핸들러를 만들어보면 되지 않을까?
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponseDto> onHttpMessageNotReadable(HttpMessageNotReadableException e) {
System.out.println(e);
}
가장 처음 시도했던 방법입니다
어마어마하게 긴 에러 메시지가 출력되었습니다 가장 마지막을 보면 ["verificationCode"]라고 하는 문자열이 있는 것을 보아, 이 에러를 잘 뒤지다 보면 어떻게 하면 가져올 수 있겠다는 생각을 갖게 되었습니다
2. getCause로 예외가 발생한 지점을 더 정확하게 특정하자
예외가 reference chain이라고 적혀있는 것을 보아 한번 변환되었을 가능성이 크다는 생각을 하게 되었습니다
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponseDto> onHttpMessageNotReadable(HttpMessageNotReadableException e) {
System.out.println(e.getCause());
}
이렇게 조금 더 구체적인 데이터를 가져오려고 했더니
나름 예외 메시지가 줄어들었고, 아직도 데이터가 정확하게 남아있는 것을 볼 수 있습니다
3. 저 에러 타입을 조금 디테일하게 알아보고, 어떤 데이터가 있는지 찾아보자
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponseDto> onHttpMessageNotReadable(HttpMessageNotReadableException e) {
if (e.getCause() instanceof JsonMappingException) {
JsonMappingException jsonMappingException = (JsonMappingException) e.getCause();
String errorMessage = jsonMappingException.getPathReference();
int startIndex = errorMessage.indexOf('[');
int endIndex = errorMessage.indexOf(']', startIndex);
String errorField = errorMessage.substring(startIndex + 1, endIndex);
return ResponseEntity.badRequest()
.body(new ErrorResponseDto(HttpStatus.BAD_REQUEST.value(),
errorField + " 필드의 값이 잘못되었습니다."));
}
return ResponseEntity.badRequest().build();
}
JsonMappingException이라는 것까지는 볼 수 있었기 때문에, 이를 isntanceOf를 통해서 확인하고, 가져올 수 있는 모든 데이터들을 다 가져와봤습니다
이를 통해서 가장 적합한 경로는 getPathReference였다는 것을 알 수 있었고
jsonMapingException.getPathReference()를 통해서 출력해 본 결과
com.sparcs.teamf.api.emailauth.dto.AuthenticateEmailRequest ["verificationCode"]
까지 나와서, 이를 파싱 해서 1차적으로 문제를 해결했었습니다
4. 이 코드를 개선해 보자
우테코 동료 말랑의 도움을 받아서 디버깅을 하게 된 결과
고마워요 말랑!!
디버깅을 통해서 확인해 본 결과, e.cause.path.fieldName을 통해서 확인할 수 있는 것을 알 수 있었습니다
또한 타입도 명확하게 MismatchedInputException이라는 것을 알 수 있었고요
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponseDto> onHttpMessageNotReadable(HttpMessageNotReadableException e) {
if (e.getCause() instanceof MismatchedInputException mismatchedInputException) {
return ResponseEntity.badRequest()
.body(new ErrorResponseDto(
HttpStatus.BAD_REQUEST.value(),
mismatchedInputException.getPath().get(0).getFieldName() + " 필드의 값이 잘못되었습니다."));
}
return ResponseEntity.badRequest()
.body(new ErrorResponseDto(HttpStatus.BAD_REQUEST.value(), "확인할 수 없는 형태의 데이터가 들어왔습니다"));
}
이를 통해서 나온 마지막 결과입니다
이를 통해서 출력하게 된다면
와 같은 정말 명확한 느낌의 메시지를 가질 수 있다는 것을 볼 수 있었는데요
3번에서 4번으로 오는 과정이 생각보다 어려웠지만, 도움을 받아서 해결하기도 했고, 이 과정을 기억하고 싶어서 이렇게 기록하려고 합니다
'Spring' 카테고리의 다른 글
Test 의 db 롤백 어디까지 알아보셨나요? (1) | 2023.04.24 |
---|---|
DispatcherServlet 알아보기 - Servlet 편 (0) | 2023.04.22 |
DispatcherServlet 알아보기 - ServletConfig 편 (0) | 2023.04.19 |
DispatcherServlet 알아보기 - Aware 편 (6) | 2023.04.18 |
모든 객체를 스프링 빈으로 등록해도 괜찮나? (3) | 2023.04.14 |