my_api_client v0.12.0 をリリースしました🚀
my_api_client v0.12.0 に含まれる PR の内容について解説していきます。 より詳しい使い方は README.jp.md をご参照ください。
#173 Avoid sleep on testing
my_api_client では以下のように書くと、任意の例外を補足して自動的に API リクエストをリトライしてくれます。 ネットワーク系のエラーとか、 API Rate Limit に引っかかった時とかに便利なやつですね。
ActiveJob の retry_on
とほぼ同じ使い方になっています。
class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' retry_on MyApiClient::ApiLimitError, wait: 1.minute, attempts: 3 error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError # GET https://example.com/users def get_users get 'users' end end
それは良いんですが、 rspec 上で wait
が効いてしまっていたので、上記のコードだとリトライ3回分 wait するので、合計 3 分も待たされてしまっていました。
一応 rspec で sleep
を stub するとかやれば回避できますが、そもそもテストでは wait を無視して欲しいですよね。
my_api_client では be_handled_as_an_error
という rspec の matcher を用意しているのですが、今回の対応で、この macher を経由してリトライが実行された場合は wait を無視するようになりました。
RSpec.describe ExampleApiClient, type: :api_client do let(:api_client) { described_class.new } # NOTE: レスポンスで `{ "errors": { "code": 20 } }` を受診した際、3 回リトライが実行された後に `MyApiClient::ApiLimitError` として例外処理される。 it do expect { api_request! } .to be_handled_as_an_error(MyApiClient::ApiLimitError) .after_retry(3).times .when_receive(body: { errors: { code: 20 } }.to_json) end end
また、 ExampleApiClient
は stub_api_client
や stub_api_client_all
を使用するとスタブ化できます。
スタブ化した状態だと任意の API レスポンスを返すか、任意の例外を発生させる、という動作になってリトライが発生しなくなるので、上記の問題はありませんでした。
stub_api_client_all(ExampleApiClient, get_users: { users: [{ id: 1 }, { id: 2 }, { id: 3 }] }) response = ExampleApiClient.new.get_users response.users # => [{ id: 1 }, { id: 2 }, { id: 3 }]
#175 Verify arguments on error handling definition
error_handling
の定義でレスポンスのステータスコードを指定することができるんですが、このオプション名が status_code
なのか status
なのかをよく間違える、という問題がありました。$ rails g api_client
を使用するとテンプレが作成されるので、そこからエラーハンドリングの定義を行うと間違えにくいのですが、後からエラーハンドリングを追加する時とかにやらかします。作者自身もたまにやらかしてました😇
# 正解 error_handling status_code: 400..499, raise: MyApiClient::ClientError # 間違い error_handling status: 400..499, raise: MyApiClient::ClientError
この PR の対応で間違ったオプションを指定すると以下のような例外が発生するようになりました。
RuntimeError: Specified an incorrect option: `status` You can use options that: [:response, :status_code, :json, :with, :raise, :block]
#176 Provides a syntax sugar of retry_on
on error_handling
最初の PR でも出てきた retry_on
ですが、 error_handling raise: MyApiClient::ApiLimitError
でも同じ例外を指定していて DRY な感じじゃなかったり、retry_on
と error_handling
をそれぞれ定義してるとお互いの関連が実感しづらい、という不満がありました。
class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com' retry_on MyApiClient::ApiLimitError, wait: 1.minute, attempts: 3 error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError # GET https://example.com/users def get_users get 'users' end end
この PR
では retry
というオプションを error_handling
に追加しています。これにより、以下の 2 つのコードは等価になります。
retry_on MyApiClient::ApiLimitError, wait: 1.minute, attempts: 3 error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError, retry: { wait: 1.minute, attempts: 3 }
retry_on
にオプションを指定する必要がなければ retry: true
と書けば OK です。
error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError, retry: true
ただし、 retry
オプションを使用する際は以下の点に注意が必要です。
error_handling
にraise
オプションの指定が必須となります。- Block を使った
error_handling
の定義は禁止されます。