Sinatraでエラーハンドリングをしてみよう

## 概要
Sinatraをちょこちょこ使っていたのですがエラーハンドリングについて真面目に考えたことがなかったことがなく、これを機会にちゃんと学ぼうと思った。

## 目標
入力値エラーや、予期せぬエラーをAPIレスポンスとして返却する。

## 言語/フレームワーク
- ruby
- Sinatra

https://qiita.com/itaya/items/3626f79495dd05b83b4b
環境構築に関してはこちらの記事でまとめてるのでこれ参考にしてくださいー

ライブラリとして`active_support`を使ってます。
理由は後で使う「空の判定」を楽に済ませるためです。必須ではないので使わない人はその部分は読み替えてください。
## 実装

### 前準備
まずベースとなるコードを紹介します。
```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'

get '/' do
  data = {result: 'success'}
  json data
end  
```
実行すると`{result: 'success'}`が返ってくるコードとなってます。

### 例外処理
ここに、入力値エラーを追加したいと思います。

#### エラーページ設定をOFFに
まず、現行のままだとエラーが発生した時点で標準のエラーページ
![スクリーンショット 2022-12-10 23.27.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83409/b6ef0517-1584-6143-d266-f508e8beaa17.png)
が表示されてしまうのでそれをOFFにします。
具体的にはshow_exceptionsというオプションをoffにします。

```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

get '/' do
  data = {result: 'success'}
  json data
end  
```

#### 例外の定義
既存の例外を使う場合は定義しなくてよいのですが、独自の例外を使いたい場合はそもそも定義する必要があります。(当たり前かもですけど自分は少しつまづきました...)
今回は入力値不正ように下記のような例外定義を追記します。
```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

class InvalidError < StandardError; end

get '/' do
  data = {result: 'success'}
  json data
end  
```

#### 例外を発生
そして、上記で定義した例外を発生させます。
今回はパラメータとして`keyword`というものがなかった場合にエラーとします。
```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

class InvalidError < StandardError; end

get '/' do
  raise InvalidError if params['keyword'].blank?
  data = {result: 'success'}
  json data
end  
```

この状態でパラメータを指定しないでアクセスすると

![スクリーンショット 2022-12-10 23.33.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83409/6c6270b4-e95c-d233-c59d-d26caa809474.png)

こんな感じの初期エラーページとは違うものが表示されます。

#### 例外をハンドリング
では、上記のエラーをハンドリングするようにしましょう。
例外のハンドリングは`error XXX do`の形式で書くことでハンドリングできます。
```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

class InvalidError < StandardError; end

get '/' do
  raise InvalidError if params['keyword'].blank?
  data = {result: 'success'}
  json data
end

error InvalidError do
  status 400
  data = {result: '入力値不正'}
  json data
end
```
このように書くことで、パラメータなしでアクセスすると`{result: "入力値不正"}`このようにエラーのレスポンスが返ってきて、またステータスコードの方も400で返ってきてます！素晴らしい！
![スクリーンショット 2022-12-10 23.37.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83409/d6054a46-0d7c-5080-e37e-41e86e7899f0.png)

#### その他の例外をハンドリング
その他の例外をハンドリングしたい場合なのですが、いろいろな記事を見ると`error do`といった形で例外を指定しないと良いというように見えるのですが、**これではExceptionしか拾ってくれません。**
どういうことかというと
```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

class InvalidError < StandardError; end

get '/' do
  nil.aaaa #エラーになるコード
  raise InvalidError if params['keyword'].blank?
  data = {result: 'success'}
  json data
end

error InvalidError do
  status 400
  data = {result: '入力値不正'}
  json data
end

error do
  data = {result: '予期せぬエラー'}
  json data
end
```

このようなコードを書いても、`nil.aaaa`はキャッチしてもらえず、エラーのレスポンスではなくエラー画面が返ってしまいます。
そのため想定外のエラーを考慮するには

```rb
require 'sinatra'
require 'sinatra/json'
require 'active_support/all'
set :show_exceptions, false

class InvalidError < StandardError; end

get '/' do
  nil.aaaa #エラーになるコード
  raise InvalidError if params['keyword'].blank?
  data = {result: 'success'}
  json data
end

error InvalidError do
  status 400
  data = {result: '入力値不正'}
  json data
end

error 500 do
  data = {result: '予期せぬエラー'}
  json data
end
```
このように500のステータスコードを指定すると良いでしょう。