エラー
すべての失敗は例外を送出します。拒否用のクラスと応答なし用のクラスがあり、すべての API エラーにリクエスト id が付きます。
エラーを捕捉する
message = {from: "[email protected]", to: "[email protected]", subject: "Your September invoice", text: "Invoice attached."} begin client.emails.send(message)rescue OpenEmail::ApiError => error warn "#{error.code} #{error.param} #{error.message}" if error.validation? warn client.addresses.list_all.addresses.inspect if error.permission? warn "try again in #{error.retry_after_seconds} seconds" if error.rate_limited? warn "#{error.status} #{error.request_id}" raiserescue OpenEmail::NetworkError => error warn "no answer in time" if error.timeout? raiseend送信時の permission_error は、たいていワークスペースではなくキーの送信範囲、つまりキーに与えられていないドメインやアドレスが原因です。このサンプルが、このキーで送信できるアドレスとして addresses.list_all が返すものを出力しているのはそのためです。
拒否の種類ごとに専用のサブクラスがあるため、rescue は処理するものをクラスで選び、残りは上位へ伝播させることができます。
message = {from: "[email protected]", to: "[email protected]", subject: "Your September invoice", text: "Invoice attached."} begin client.emails.send(message)rescue OpenEmail::ValidationError => error warn "#{error.param}: #{error.message}"rescue OpenEmail::AuthenticationError, OpenEmail::PermissionError => error warn "the key cannot do this: #{error.code}" raiserescue OpenEmail::Error => error warn "#{error.class}: #{error.message}" raiseendエラークラス
| クラス | 発生条件 |
|---|---|
| OpenEmail::Error | gem が定義するすべてのエラーの基底クラスなので、rescue OpenEmail::Error でそのすべてを捕捉できます。ArgumentError は捕捉しません。 |
| OpenEmail::ApiError | API が応答したものの、成功ではなかった場合。status、type、code、param、doc_url、request_id、retry_after_seconds、fields、body を持ちます。サーバー障害のように type が api_error の場合はこのクラスのまま送出され、それ以外の場合は type に対応するサブクラスとして送出されます。 |
| OpenEmail::InvalidRequestError、AuthenticationError、PermissionError、NotFoundError、ConflictError、ValidationError、RateLimitError | ApiError のサブクラスで、type ごとに 1 つあります:invalid_request_error、authentication_error、permission_error、not_found_error、conflict_error、validation_error、rate_limit_error。 |
| OpenEmail::NetworkError | 応答が届かなかった場合。DNS、TLS、拒否または切断された接続、あるいはタイムアウトです。その下にある例外 original を持ち、これは cause でもあります。タイムアウトが原因のときは timeout? が true です。 |
| OpenEmail::WebhookSignatureError | OpenEmail.verify_webhook_signature が配信を拒否した場合。 |
| ArgumentError | 何かが送信される前に送出されます:キーがない、または形式が不正、使えない base_url:、空の id。呼び出しそのものが誤っていることを意味するため、OpenEmail::Error ではなく Ruby の標準クラスです。 |
ApiError が持つもの
messageString- API 自身の文章で、人が読むために書かれ、問題の値があればそれを示します。安定した識別子ではないので、分岐は `code` で行ってください。
statusInteger- 応答の HTTP ステータス。
typeString- `OpenEmail::ERROR_TYPES` の 8 つの値のいずれか。この集合は凍結されており、増えることはありません。ボディが何も示さない場合はステータスから推定されます。
codeString- 具体的な失敗内容で、`from_address_forbidden` や `invalid_email_address` などです。拡張に開かれていて追加されていくため、知らないものはその `type` として扱ってください。ボディが API のエラーエンベロープでなかった場合は `unrecognised_response` になります。
paramString or nil- 失敗がフィールドを示す場合の、拒否されたフィールド。`to.0` のようなドット区切りのパスです。
doc_urlString or nil- API が示す場合の、この失敗に関するページ。
request_idString or nil- サーバーがリクエストを記録した id。ボディまたは `x-request-id` ヘッダーから取得します。
retry_after_secondsInteger, Float or nil- サーバーが `Retry-After` で求めた待機時間の秒数。数値と日付のどちらで送られても秒に換算されます。送られなかった場合は nil。
fieldsArray<Hash> or nil- 問題ごとに 1 つの Hash で、それぞれ `key` と `error` を持ちます。たとえば `forms.subscribe` が回答を 422 `invalid_form_submission` で拒否したときの `{key: "email", error: "email"}` です。エラーが何も挙げない場合は nil。
bodyHash or nil- パース済みのエラーレスポンス全体で、Symbol キーを持ちます。空だったか JSON でなかった場合は nil。
| 述語メソッド | true になる条件 |
|---|---|
| auth? | type が authentication_error、すなわち 401 の場合。キーがない、資格情報の種類が違う、あるいは当方が発行していないキーであることを意味する。 |
| permission? | permission_error、すなわち 403 の場合。キー自体は有効だが、必要なスコープまたは From アドレスを持っていない。 |
| scope_missing? | code が insufficient_scope の場合。不足しているスコープを名指しする 403 である。 |
| invalid_request? | invalid_request_error、つまり 400:理解できなかったリクエスト。サイズ上限を超えるメッセージは 422 message_too_large として返るため、それを捕まえる述語メソッドは validation? です。 |
| validation? | validation_error、すなわち 422 の場合。スキーマが拒否したことを表し、param が該当フィールドを示す。 |
| not_found? | not_found_error、すなわち 404 の場合。該当するリソースが存在しない。 |
| conflict? | conflict_error、すなわち 409 の場合。対象のリソースは、その操作を行える段階をすでに過ぎている。 |
| rate_limited? | rate_limit_error、つまり 429。サーバーが待機時間を示した場合は retry_after_seconds に入ります。 |
| server_error? | status が 500 以上の場合。サポートに問い合わせる際は request_id を伝えてください。 |
| retryable? | status が 408、429、500、502、503、504 のいずれかの場合。 |
| step_up_required? | code が step_up_required の場合。本人がコードを確認するまで、重要な変更の前に OAuth アクセストークンが受け取る 403 です。 |
ほとんどの述語メソッドは、エンベロープのうち凍結された側である type を読み、各サブクラスは 1 つの type に対応します。code は String のままです。API はそれが拡張に開かれていて追加されていくと保証しているので、知らないものはその type として扱ってください。閉じたリストにすると、新しい失敗の種類を読むために gem のアップグレードが必要になってしまいます。
API のエラーエンベロープではないボディでも OpenEmail::ApiError を送出し、type はステータスから推定され、code は unrecognised_response になります。ボディが JSON ではない成功レスポンスも、同様に送出します。
retryable? が表すのはステータスであり、あなたの呼び出しではありません。繰り返しても安全な呼び出しは、例外を送出する時点ですでにリトライ済みです。また send_quota_exceeded や ai_quota_exceeded のような 429 は、上限がリセットされるまで同じように失敗するため、ループで再試行せずに人に知らせてください。
アダプターが送出した例外は、その呼び出しに許されたリトライを使い切ると OpenEmail::NetworkError になり、元の例外は original と cause に入ります。ただし NameError、TypeError、ArgumentError は別扱いです:アダプターのバグを意味するため、そのまま送出され、リトライされることはありません。
request_id
すべての OpenEmail::ApiError は、サーバーが送ったリクエスト id をエラーボディまたは x-request-id ヘッダーから受け取って保持しており、これがあなたの失敗をサーバーのログの 1 行と結びつける唯一の手がかりです。成功時はパース済みのボディだけを返すため、成功レスポンスで読めるリクエスト id はありません。