[Spring boot] PathVariable 값 validation 핸들러

JO Yeongmu·2024년 5월 7일
0

Spring Boot

목록 보기
4/8

📌상황

@RequestBody 의 필드에 대한 validation 핸들링을 진행 중이였다.

 /**
     * validation
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    ResponseEntity<CustomErrorResponseDto> handleValidationException(MethodArgumentNotValidException e) {
        return CustomErrorResponseDto.valid(e);
    }

body가 아닌 PathVariable 과 같은 파라미터로 넘어오는 값은 어떻게 검증하고 핸들링 할 수 있을까 알아보았습니다.



📌 ConstraintViolationException.class 이용

/**
     * validation param
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException e) {
        return CustomErrorResponseDto.validParam(e);
    }

이런 형식으로 ConstraintViolationException.class 를 이용하여 검증 핸들링을 진행 할 수 있습니다.



전체 과정


controller 에서의 validation


controllerDocs

@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);
}

controller

@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);
    }
}

핸들링 응답 dto 자성

@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());
    }

" : " 기준 다음으로 string 을 정의한 이유

private static String toString(Set<? extends ConstraintViolation<?>> constraintViolations) {
		return constraintViolations.stream()
			.map( cv -> cv == null ? "null" : cv.getPropertyPath() + ": " + cv.getMessage() )
			.collect( Collectors.joining( ", " ) );
	}

ConstraintViolationException 클래스에 들어가서 확인해보면 : 앞에부분은 응답 데이터로 넣기에 불필요한 요소이기 때문에 : 다음 값을 응답 하기로 결정했습니다.



🎉 결과

profile
도전해 보는 것이 성장의 첫걸음입니다 :)

0개의 댓글