본 글은 글쓴이의 개인적인 생각이 담겨있을 수 있습니다.
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는 작동을 하지 않는다.
이를 위해 나는 스택오버플로우에 찾아보았다.
내가 겪은 문제는 다른 사람이 이미 겪은 문제라는 말이 있듯이 당연히 같은 문제를 겪은 사람들이 있었다.
여기서 말하길 현재 코틀린은 리스트안의 엘리먼트에 대한 유효성 체크를
기존에 존재하는 어노테이션으로 할 수 없다고 한다.
그리고 이걸 해결하기 위해서 사용자 지정 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
만 아니면 되니까 제네릭 타입으로 여러 타입에 용이하도록 만들었다.
프로퍼티의 타입이 List<String>
인 non-null
타입의 엘리먼트라서
발생한 에러인줄 알았는데 확인해보니 null
이 들어간 배열이 존재하고 있었다.
이 말은 non-null
타입인데도 불구하고 null
이 들어갈 수 있었다는 것인데,
이를 확인하기 위해서 테스트를 해봤으나 아무 단서도 찾지 못했다...
혹시 아는 사람은 연락바라겠습니다!!