Spring BootをKotlinで書いていてNon-Nullableなプロパティに対するJSONフィールドが無い場合の例外ハンドリング

RequestbodyをJsonで受けて、Kotlinのdata classにセットするケースで、data classのプロパティがNon-Nullableだと、マッピングするJSONフィールドが無いとエラーになります。（当たり前ですが）
data classのプロパティに Bean validation`@NotNull(message="")` を付けても検証されない。

NotNullチェックのために、data class のプロパティを Nullable にするのも違和感があったので、 例外ハンドリングで対応してみた。

```kotlin

import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.http.converter.HttpMessageNotReadableException
import org.springframework.validation.FieldError
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.context.request.WebRequest
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler

@RestControllerAdvice
class CustomeExceptionHandler : ResponseEntityExceptionHandler() {
    
    /**
     * Non-Nullableなプロパティにマッピングするパラメータが不正だった場合のエラーメッセージを作成する
     * */
    override fun handleHttpMessageNotReadable(
        ex: HttpMessageNotReadableException,
        headers: HttpHeaders,
        status: HttpStatus,
        request: WebRequest
    ): ResponseEntity<Any> {
        val body = when (val cause = ex.cause) {
            is MissingKotlinParameterException -> {
                cause.path.joinToString(",") { it.fieldName }
                    .let { mapOf(Pair("入力パラメータがありません", it)) }
            }
            is InvalidFormatException -> {
                cause.path.joinToString(",") {
                    "type of ${it.fieldName} should be ${cause.targetType}. but value was ${cause.value}"
                }.let { mapOf(Pair("入力パラメータの型が一致しません", it)) }
            }
            else -> mapOf(Pair("予期せぬエラー ${cause?.javaClass?.name}", ex.localizedMessage))
        }
        return handleExceptionInternal(ex, body, headers, status, request)
    }

    /**
     * RequestBodyのValidationメッセージをレスポンスに出力するためにフィールドとメッセージをマッピングします
     * */
    override fun handleMethodArgumentNotValid(
        ex: MethodArgumentNotValidException,
        headers: HttpHeaders,
        status: HttpStatus,
        request: WebRequest
    ): ResponseEntity<Any> {
        val body = ex.bindingResult.allErrors.associateBy({ it.let { it as FieldError }.field }, { it.defaultMessage })
        return handleExceptionInternal(ex, body, headers, status, request)
    }

    override fun handleExceptionInternal(
        ex: Exception,
        body: Any?,
        headers: HttpHeaders,
        status: HttpStatus,
        request: WebRequest
    ): ResponseEntity<Any> {
        val responseBody = body ?: CustomeErrorResponse(ex.message ?: ex.localizedMessage)
        return super.handleExceptionInternal(ex, responseBody, headers, status, request)
    }
}

data class CustomeErrorResponse(val message: String)
```

余計な関数も記載してるが、 `ResponseEntityExceptionHandler`を継承した、例外ハンドリングクラスで `handleHttpMessageNotReadable`関数をオーバーライドしている。
このメソッドがハンドリングしている、`HttpMessageNotReadableException`は、Non-Nullableプロパティにセット出来なった場合に発生する。

更にこの例外の原因は `MissingKotlinParameterException`であり、この例外オブジェクトからセットされていないJsonフィールドが分かるので、この情報をレスポンスに含めるようにしている。

このハンドリング処理は未検証な点もあるが、Non-NullableプロパティとマッピングされていなJSONフィールドが複数有っても最初の1つのフィールドしか取れない。
`MissingKotlinParameterException.path`は、リスト型だったので複数のパラメータを持っていると考えて、`joinToString` でまとめて出力したかったが、実装方法が不明。

とにかく、これでNon-Nullableなプロパティに対する例外ハンドリングも出来るようになった。

更に、Jsonフィールドが、マッピングするdata classの型と一致しない場合は、`InvalidFormatException`でハンドリングすることもできた。