独自 例外ハンドリング (基本）

PHONEAPPLIアドベントカレンダー1回目です。（早速、投稿が遅れました。すみません・・・・）
今回は、例外処理の基本的な部分を投稿したいと思います。

# きっかけ

- 独自例外処理の知識を整理をしたかったから
- 基本的な部分の理解が出来ていない気がするから

# 前提

- `Spring Boot` + `kotlin` で行う
- タスク作成　ツールの開発を想定します。

## 扱う例外

- HTTP method が違う
- アノテーションによる例外
    - Null　+ 空文字

これらを、HTTPステータスと例外をレスポンスとして出力できるようにする。

## やり方の種類

- `ResponseEntityExceptionHandler`　を継承している場合は、`override fun handleHttpRequestMethodNotSupported` を使う（今回のHTTP methodが異なる場合に使用）
- `ResponseEntityExceptionHandler`　を継承しない場合は、以下を使いたい関数の上におく
    - `@ExceptionHandler(HttpRequestMethodNotSupportedException::class)`（今回のアノテーションエラーで使用）

## 共通レスポンス
レスポンスとしての例外フォーマットは、独自フォーマット にします。

```json
{
   "message": "Request method 'POST' not supported",
   "status": 405
}

```

## 実装

### HTTP method が違う 場合

※ `ResponseEntityExceptionHandler`　を継承しているパターンで行う

1. ControllerAdvice クラスを作成
2. ControllerAdvice クラスに @RestControllerAdvice をつける（**必須**）
3. `ResponseEntityExceptionHandler` を継承
4. `handleHttpRequestMethodNotSupported ` をオーバーライドする

※ `ResponseEntityExceptionHandler` は、例外時の基本的な処理が記載されている。エラーパターンも網羅されているため、エラーメッセージを表示したい時にデフォルトが表示したくない場合に使えると思う。

`TaskControllerAdvice.kt`

```kotlin:TaskControllerAdvice.kt
@RestControllerAdvice
class TaskControllerAdvice(): ResponseEntityExceptionHandler() {
    
    override fun handleHttpRequestMethodNotSupported(
        ex: HttpRequestMethodNotSupportedException,
        headers: HttpHeaders,
        status: HttpStatus,
        request: WebRequest
    ): ResponseEntity<Any> {
        return super.handleExceptionInternal(
            ex,
            createErrorResponse(ex, status), //-> ここで、JSONで返す値を加える
            httpheaderJson,
            status,
            request
        )
    }

    private fun createErrorResponse(
        ex: Exception,
        status: HttpStatus
    ): ErrorResponse {
        return ErrorResponse(
            message = ex.message,
            status = status.value()
        )
    }
}

class ErrorResponse(
    val message: String?,
    val status: Int
)
```

#### レスポンス

```JSON
{
    "message": "Request method 'POST' not supported",
    "status": 405
}
```

### アノテーションエラーの場合

※ `ResponseEntityExceptionHandler`　を継承していないパターンで行う

1. `TaskController.kt` ： エラーチェックをしたいクラスに @Valid をつける
2. `TaskRequest.kt`： 実際に、@NotBlank などのアノテーションをつける
3. `TaskControllerAdvice.kt`： クラス・ファイルを作成後、実際の例外処理を記載する（アノテーションでのエラーは、`MethodArgumentNotValidException` がキャッチしてくれると思う）

 
`TaskController.kt`

```kotlin:TaskController.kt
@PostMapping
    fun create(
        @RequestBody @Valid taskRequest: TaskRequest
    ) {
        // 省略
    }
```

`TaskRequest.kt`

```kotlin:TaskRequest.kt
data class TaskRequest (
    val id: Int,

    @field: NotBlank
    val content: String,
)
```

`TaskControllerAdvice.kt`

```kotlin:
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodNotValidException(
   ex: MethodArgumentNotValidException
): ErrorResponse{
   return (
     ErrorResponse(
       message = ex.bindingResult.fieldError?.defaultMessage,
       status = HttpStatus.BAD_REQUEST.value()
     )
  )
}

class ErrorResponse(
    val message: String?,
    val status: Int
)

```

#### レスポンス

```json
{
    "message": "空白は許可されていません",
    "status": 400
}
```

### まとめ

- 今回は、HTTPメソッドが違う場合、アノテーションエラーに引っかかった場合での例外処理を書いてみました。
- `ResponseEntityExceptionHandler`　を継承すれば、引数にも多くの情報が入っているので例外として出力したいことを表示したい時に、使えるかなと思いました。