Kotlin
spring-boot
BeanValidation

【Kotlin】バリデーションの書き方の基礎【SpringBoot】

この記事はMicroAd Advent Calendarの5日目の記事です。

TL;DR

Kotlinでバリデーションをやります。主に書くのは以下2点についてです。

  1. アノテーションを使ったバリデーション
  2. AssertTrueを使ったバリデーション

他の書き方もありますが、とりあえず自分が知っているのがこの2つなのでまとめます。
当初アノテーション自作まで書いてましたが、長くなりすぎたので分割し、7日目に回しました。

前書き

この記事は以下の記事を元に書いています。

コントローラー

コントローラーは以下を用います。
受け取ったmyModelに対してバリデーションを行い、引っかかればエラーを、引っかからなければpostした内容を返しています。

MyController.kt
import com.wrongwrong.modeltest.model.MyModel
import org.springframework.validation.BindingResult
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("my")
class MyController{

    @PostMapping
    fun myPostTest(
            @RequestBody @Validated myModel: MyModel,
            bindingResult: BindingResult
    ): String {
        //エラーがあればエラーを文字列にして返す
        if(bindingResult.hasErrors()){
            return bindingResult.allErrors.toString()
        }
        //受け取った内容を返却
        return "post:" + myModel.toString()
    }
}

エラーの無い状態で叩くと以下のようになります。

エラー無し
$ curl -X POST -H "Content-Type: application/json" -d '{"id":1, "name":"wrong wrong", "create":"2018-11-01", "update":"2018-11-02"}' localhost:8080/my
post:MyModel(id=1, name=wrong wrong, create=Thu Nov 01 09:00:00 JST 2018, update=Fri Nov 02 09:00:00 JST 2018)

モデル

今回バリデーションを行う上で、元にするモデルは以下です。
全てNullableとしなければ正常にバリデーションが行えないので、必ずNullableで宣言してください(説明は本題と外れるので省略します)。
バリデーションしたい内容はコメントの通りです。

MyModel.kt
import java.util.*

data class MyModel(
        // nullを禁止したい
        val id: Long?,
        // 名前がスペース区切りになっていることを確認したい
        val name: String?,
        // createはupdateと同じか過去にしたい
        val create: Date?,
        val update: Date?
)

アノテーションを使ったバリデーション

まずidのバリデーションを、アノテーションとして実装されたバリデーターを使ってやっていきます。
data classでは、@field:NotNullというようにアノテーションを付与します。
Javaではfield:無しでもバリデーションが行えますが、Data Classではこれを付けなければ正常にバリデーションができません。
アノテーションを付与すると以下のようになります。

MyModel.kt
import java.util.*
import javax.validation.constraints.NotNull

data class MyModel(
        @field:NotNull(message = "idはnull不許可")
        val id: Long?,
        // 名前がスペース区切りになっていることを確認したい
        val name: String?,
        // createはupdateと同じか過去にしたい
        val create: Date?,
        val update: Date?
)

この状態でエラー有り/無しをそれぞれCurlで叩いた結果が以下です。

エラー無し
$ curl -X POST -H "Content-Type: application/json" -d '{"id":1}' localhost:8080/my
post:MyModel(id=1, name=null, create=null, update=null)
エラー有り
$ curl -X POST -H "Content-Type: application/json" -d '{}' localhost:8080/my
[Field error in object 'myModel' on field 'id': rejected value [null]; codes [NotNull.myModel.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myModel.id,id]; arguments []; default message [id]]; default message [idはnull不許可]]

利用できるアノテーション

javax.validation.constraintsクラスや、org.hibernate.validator.constraintsクラスを見ておくと、既存で利用できるアノテーションは大体まとまっています。
javax.validation.constraints (Java(TM) EE 8 Specification APIs)
org.hibernate.validator.constraints (Hibernate Validator 5.1.3.Final)

AssertTrueを使ったバリデーション

次にcreateとupdateの相関チェックをやっていきます。
相関チェックはJavaと同様、@AssertTrue(または@AssertFalse)を付与したBool型の関数で行います。
今回は相関チェックにのみ利用していますが、アノテーションが用意されていないような単体チェックにも利用できます。
相関チェックを加えると以下のようになります。

MyModel.kt
import java.util.*
import javax.validation.constraints.AssertTrue
import javax.validation.constraints.NotNull

data class MyModel(
        @field:NotNull(message = "idはnull不許可")
        val id: Long?,
        // 名前がスペース区切りになっていることを確認したい
        val name: String?,
        val create: Date?,
        val update: Date?
) {
    @AssertTrue(message = "updateがcreateより過去")
    fun isLater(): Boolean {
        if(create == null || update == null) return true
        return create.before(update) || create == update
    }
}

この状態でエラー有り/無しをそれぞれCurlで叩いた結果が以下です。

エラー有り
curl -X POST -H "Content-Type: application/json" -d '{"id":1, "create":"2018-11-03", "update":"2018-11-02"}' localhost:8080/my
[Field error in object 'myModel' on field 'later': rejected value [false]; codes [AssertTrue.myModel.later,AssertTrue.later,AssertTrue.boolean,AssertTrue]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myModel.later,later]; arguments []; default message [later]]; default message [updateがcreateより過去]]

後書き

記事は7日目へ続きます。

参考にさせていただいた記事