2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-07-09

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

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


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でハンドリングすることもできた。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?