@RequestBody 의 필드에 대한 validation 핸들링을 진행 중이였다.
/**
* validation
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<CustomErrorResponseDto> handleValidationException(MethodArgumentNotValidException e) {
return CustomErrorResponseDto.valid(e);
}
body가 아닌
PathVariable과 같은 파라미터로 넘어오는 값은 어떻게 검증하고 핸들링 할 수 있을까 알아보았습니다.
/**
* validation param
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException e) {
return CustomErrorResponseDto.validParam(e);
}
이런 형식으로 ConstraintViolationException.class 를 이용하여 검증 핸들링을 진행 할 수 있습니다.
@Tag(name = "Users")
@Validated
public interface UserControllerDocs {
/* Create: 회원 가입 */
@Operation(summary = "회원가입 요청", description = "**성공 응답 데이터:** 사용자의 `user_id` ")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "회원 가입 완료"),
@ApiResponse(responseCode = "400", description = "잘못된 입력 데이터"),
@ApiResponse(responseCode = "409", description = "아이디/이메일 중복")
})
ResponseEntity<?> join(JoinReqDto joinReqDto);
/* 아이디 중복 확인 */
@Operation(summary = "아이디 중복 확인", description = "**성공 응답 데이터:** true")
@Parameter(name = "loginId", description = "검증할 아이디", example = "carumuch1234")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "사용 가능한 아이디"),
@ApiResponse(responseCode = "400", description = "잘못된 입력 데이터"),
@ApiResponse(responseCode = "409", description = "아이디 중복")
})
ResponseEntity<?> checkLoginId(
@Pattern(regexp = "^[a-z0-9]{4,20}$",
message = "아이디는 영어 소문자와 숫자만 사용하여 4~20자리여야 합니다.") String loginId);
/* 이메일 중복 확인 */
@Operation(summary = "이메일 중복 확인", description = "**성공 응답 데이터:** true")
@Parameter(name = "email", description = "검증할 이메일", example = "carumuch@gmail.com")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "사용 가능한 이메일"),
@ApiResponse(responseCode = "400", description = "잘못된 입력 데이터"),
@ApiResponse(responseCode = "409", description = "이메일 중복")
})
ResponseEntity<?> checkEmail(
@Pattern(regexp = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$",
message = "이메일 형식에 맞지 않습니다.") String email);
}
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController implements UserControllerDocs{
private final UserService userService;
/**
* CREATE: 회원 가입
*/
@PostMapping
public ResponseEntity<?> join(@Validated(ValidationSequence.class) @RequestBody JoinReqDto joinReqDto) {
return ResponseEntity.status(CREATED)
.body(ResponseDto.success(CREATED, userService.join(joinReqDto)));
}
/**
* 아이디 중복 확인
*/
@GetMapping("/check-login-id/{loginId}")
public ResponseEntity<?> checkLoginId(@PathVariable String loginId) {
userService.checkLoginIdDuplicate(loginId);
return ResponseEntity.status(OK)
.body(ResponseDto.success(OK, true));
}
/**
* 이메일 중복 확인
*/
@GetMapping("/check-email/{email}")
public ResponseEntity<?> checkEmail(@PathVariable String email) {
userService.checkEmailDuplicate(email);
return ResponseEntity.status(OK)
.body(ResponseDto.success(OK, true));
}
}
기존의 validation 어노테이션을 검증을 하고 싶은 파라미터에 값에 작성해줍니다.
controller와 docs 와 분리 되며 body와 파라미터를 나누어 검증 할 때
valid중복이 일어날 수 있기 때문에@Valided같은 경우에는 docs 최상단에 작성해줍니다.
@ControllerAdvice
public class CustomExceptionHandler {
/**
* validation param
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException e) {
return CustomErrorResponseDto.validParam(e);
}
}
@Slf4j
@Getter
public class CustomErrorResponseDto {
private final boolean success;
private final int status;
private final String code;
private final String message;
@Builder
private CustomErrorResponseDto(boolean success, int status, String code, String message) {
this.success = success;
this.status = status;
this.code = code;
this.message = message;
}
/**
* validation param
*/
public static ResponseEntity<CustomErrorResponseDto> validParam(ConstraintViolationException e) {
String errorMessage = e.getMessage().substring(e.getMessage().indexOf(":") + 1).trim();
log.error(400 +"에러 발생 -> " + errorMessage);
return ResponseEntity
.status(BAD_REQUEST)
.body(CustomErrorResponseDto.builder()
.success(false)
.status(400)
.code(BAD_REQUEST.name())
.message(errorMessage)
.build());
}
private static String toString(Set<? extends ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream()
.map( cv -> cv == null ? "null" : cv.getPropertyPath() + ": " + cv.getMessage() )
.collect( Collectors.joining( ", " ) );
}
ConstraintViolationException 클래스에 들어가서 확인해보면 : 앞에부분은 응답 데이터로 넣기에 불필요한 요소이기 때문에 : 다음 값을 응답 하기로 결정했습니다.
