List Element를 Validation 하는 법

Jaychy·2021년 4월 1일
0

프로젝트 PICK

목록 보기
6/8
post-thumbnail

본 글은 글쓴이의 개인적인 생각이 담겨있을 수 있습니다.

PICK 프로젝트 - pick-server-Saturn
https://github.com/DSM-PICK/pick-server-Saturn

사건의 발단

프론트엔드를 개발하던 친구가 500이 발생했다고 말했다.

문제

사실 문제를 확인해보니 클라이언트 측에서 리스트 안에 null이 들어가 있던 것이 원흉이었다.
클라이언트 로직적으로도 리스트 안에 null이 들어갈 수 없었지만
결론적으로 500이 발생하여 에러 핸들링을 적절히 하지 못했기 때문에
서버의 잘못도 있다고 할 수 있다.

해결

그래서 문제는 Request Body의 리스트 안에 null이 들어가는 것인데,
이를 해결하기 위해서 validation annotation을 작성해보았다.

프로퍼티가 여러 개였지만 편의를 위해 리스트 프로퍼티만 남기도록 하겠다.

data class StudentModificationRequest(
    @get:NotEmpty
    val numbers: List<@NotBlank String>,
)

StudentModificationRequest 클래스의 numbers 프로퍼티는
빈 리스트일 수 없으며, 그 안의 엘리먼트들은 null, 공백, only 띄워쓰기일 수 없다.
빈 리스트일 수 없도록 하는 @get:NotEmpty는 당연히 작동할 것이라는 것을 알고
실제로도 작동하지만 리스트 안의 @NotBlank는 작동을 하지 않는다.

이를 위해 나는 스택오버플로우에 찾아보았다.
내가 겪은 문제는 다른 사람이 이미 겪은 문제라는 말이 있듯이 당연히 같은 문제를 겪은 사람들이 있었다.

스택오버플로우
https://stackoverflow.com/questions/51085138/kotlin-data-class-and-bean-validation-with-container-element-constraints

여기서 말하길 현재 코틀린은 리스트안의 엘리먼트에 대한 유효성 체크를
기존에 존재하는 어노테이션으로 할 수 없다고 한다.

그리고 이걸 해결하기 위해서 사용자 지정 validation annotation을 만들라고 한다.
그래서 만들어보았다.

Custom Validation Annotation 구현

Custom Validation Annotation을 만들기 위해서는 두 개의 파일이 필요하다.
붙일 어노테이션 클래스와, 이 어노테이션 클래스가 동작하는 기능을 담은 클래스이다.

붙일 어노테이션 클래스는 다음과 같이 간단하게 만들 수 있다.

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [NotNullElementListValidator::class])
annotation class NotNullElementList(
    val message: String = "리스트 안에 null 값이 존재합니다.",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<*>> = [],
)

@Target의 설정 범위를 위처럼 필드에만 한정하면 JVM이 코틀린을 자바로 바꿀 때
생성자의 매개변수가 아닌 필드에만 붙일 수 있도록 강요할 수 있다.
따라서 @NotNullElementListValidator 어노테이션을 붙일 때는 :get 또는 :field를 붙일 필요가 없다.

그리고 이 기능을 구현할 NotNullElementListValidator 클래스는 다음과 같다.

class NotNullElementListValidator<T> : ConstraintValidator<NotNullElementList, List<T>> {
    override fun isValid(value: List<T>, context: ConstraintValidatorContext?) =
        value.filter { it == null }.count() <= 0
}

엘리먼트가 무슨 속성이든 간에 null만 아니면 되니까 제네릭 타입으로 여러 타입에 용이하도록 만들었다.

그런데? 왜 500이지?

프로퍼티의 타입이 List<String>non-null 타입의 엘리먼트라서
발생한 에러인줄 알았는데 확인해보니 null이 들어간 배열이 존재하고 있었다.
이 말은 non-null 타입인데도 불구하고 null이 들어갈 수 있었다는 것인데,
이를 확인하기 위해서 테스트를 해봤으나 아무 단서도 찾지 못했다...
혹시 아는 사람은 연락바라겠습니다!!

profile
아름다운 코드를 꿈꾸는 백엔드 주니어 개발자입니다.

0개의 댓글