はじめに
サロゲートペア(4バイトの文字)を含む文字列のValidationをしようとしたときに、サロゲートペアだけ2文字カウントされてうまくいかなかったので、これも1文字カウントするカスタムアノテーションを作ってみました。
コード
Constraint(validatedBy = [CustomSizeValidator::class])
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@ReportAsSingleViolation
annotation class CustomSize(
val message: String = "Size is invalid",
val groups: Array<KClass<out Any>> = [],
val payload: Array<KClass<out Payload>> = [],
val max: Int = Int.MAX_VALUE,
val min: Int = 0
)
class CustomSizeValidator : ConstraintValidator<CustomSize, String> {
private var maxSize: Int by Delegates.notNull()
private var minSize: Int by Delegates.notNull()
override fun initialize(customSize: CustomSize) {
maxSize = customSize.max
minSize = customSize.min
}
override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
return (value?.codePointCount(0, value.length) in minSize..maxSize)
}
}
annotation class
のところはお作法?にしたがいます。
max
, min
のデフォルト値はinitialize
で初期化するため、初期化遅延する必要があります。
しかしプリミティブ型の変数はlateinit
が使えないので、
private var maxSize: Int by Delegates.notNull()
private var minSize: Int by Delegates.notNull()
としています。
codePointCount
を用いることで、サロゲートペアも1文字としてカウントできます。
@CustomSize(min = 1, max = 10)
などと指定するとサロゲートペアでも1文字扱いでValidationをかけることができます。
使用例
data class Hoge(
@field:NotNull
val id: Int?
@field:NotEmpty
@field:CustomSize(max = 20)
val name: String?,
)
...
fun hogeController(@RequestBody @Valid book : Book){
...
}
細かなところは省きますが、これでhogeControllerに𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋𠀋
こんなサロゲートペアの文字20個を含むHogeを渡しても大丈夫です!