Erreurs
Chaque échec lève une erreur. Une classe pour un refus, une pour l'absence de réponse, et un request id sur chaque erreur d'API.
En attraper une
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? raiseendUn permission_error sur un envoi vient généralement de la portée d'envoi de la CLÉ, un domaine ou une adresse qui ne lui a pas été accordé, plutôt que de l'espace de travail, et c'est pourquoi l'exemple affiche les adresses depuis lesquelles addresses.list_all indique que cette clé peut envoyer.
Chaque type de refus a sa propre sous-classe : un rescue peut donc choisir par classe ceux qu'il traite et laisser remonter les autres.
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}" raiseendLes classes
| Classe | Quand |
|---|---|
| OpenEmail::Error | La base de toutes les erreurs que définit la gem : rescue OpenEmail::Error les intercepte donc toutes. Elle n'intercepte pas ArgumentError. |
| OpenEmail::ApiError | L'API a répondu, et pas par un succès. Porte status, type, code, param, doc_url, request_id, retry_after_seconds, fields et body. Levée telle quelle quand type vaut api_error, comme pour une panne du serveur, et sous la sous-classe de son type sinon. |
| OpenEmail::InvalidRequestError, AuthenticationError, PermissionError, NotFoundError, ConflictError, ValidationError et RateLimitError | Sous-classes d'ApiError, une pour chaque type : invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, validation_error et rate_limit_error. |
| OpenEmail::NetworkError | Aucune réponse n'est arrivée : DNS, TLS, une connexion refusée ou coupée, ou le délai dépassé. Porte original, l'exception sous-jacente, qui est aussi sa cause, et timeout? vaut true quand le délai était la raison. |
| OpenEmail::WebhookSignatureError | OpenEmail.verify_webhook_signature a refusé une livraison. |
| ArgumentError | Levée avant tout envoi : une clé manquante ou mal formée, une base_url: inutilisable, un id vide. C'est la classe Ruby ordinaire, pas une OpenEmail::Error, car elle signifie que l'appel lui-même est erroné. |
Ce que porte une ApiError
messageString- La phrase de l'API elle-même, écrite pour un humain, qui nomme la valeur fautive quand il y en a une. Ce n'est pas un identifiant stable : branchez-vous donc sur `code`.
statusInteger- Le statut HTTP de la réponse.
typeString- L'une des huit valeurs de `OpenEmail::ERROR_TYPES`, un ensemble figé qui ne s'agrandira pas. Quand le corps n'en nomme aucune, elle est déduite du statut.
codeString- L'échec précis, comme `from_address_forbidden` ou `invalid_email_address`. Ouvert et additif : traitez donc un code que vous ne reconnaissez pas comme son `type`. Il vaut `unrecognised_response` quand le corps n'était pas l'enveloppe d'erreur de l'API.
paramString or nil- Le champ refusé, sous forme de chemin pointé comme `to.0`, quand l'échec en nomme un.
doc_urlString or nil- Une page sur cet échec, quand l'API en nomme une.
request_idString or nil- L'id sous lequel le serveur a journalisé la requête, tiré du corps ou de l'en-tête `x-request-id`.
retry_after_secondsInteger, Float or nil- L'attente demandée par le serveur dans `Retry-After`, en secondes, qu'il ait envoyé un nombre ou une date. nil quand il n'en a envoyé aucune.
fieldsArray<Hash> or nil- Un Hash par problème, chacun avec `key` et `error`, comme `{key: "email", error: "email"}` quand `forms.subscribe` a refusé les réponses avec un 422 `invalid_form_submission`. nil quand l'erreur n'en liste aucun.
bodyHash or nil- La réponse d'erreur entière, analysée, avec des clés Symbol. nil quand elle était vide ou n'était pas du JSON.
| Prédicat | Vrai quand |
|---|---|
| auth? | type vaut authentication_error, un 401 : aucune clé, un type d'identifiant erroné, ou une clé que nous n'avons pas émise. |
| permission? | permission_error, un 403 : une vraie clé à laquelle manque la portée ou l'adresse From dont elle a besoin. |
| scope_missing? | code vaut insufficient_scope, le 403 qui nomme une portée manquante. |
| invalid_request? | invalid_request_error, un 400 : une requête qui n'a pas pu être comprise. Un message dépassant le plafond de taille revient en 422 message_too_large : c'est donc le prédicat validation? qui l'attrape. |
| validation? | validation_error, un 422 : le schéma l'a refusée, et param nomme le champ. |
| not_found? | not_found_error, un 404 : la ressource n'existe pas. |
| conflict? | conflict_error, un 409 : la ressource a dépassé le stade où cette opération pouvait encore lui être appliquée. |
| rate_limited? | rate_limit_error, un 429. retry_after_seconds contient le délai d'attente quand le serveur en a indiqué un. |
| server_error? | status vaut 500 ou plus. Citez request_id si vous contactez le support. |
| retryable? | status vaut 408, 429, 500, 502, 503 ou 504. |
| step_up_required? | code vaut step_up_required, le 403 qu'un jeton d'accès OAuth reçoit avant un changement sensible tant que la personne n'a pas vérifié de code. |
La plupart des prédicats lisent type, la moitié figée de l'enveloppe, et chaque sous-classe correspond à un type. code reste une String, car l'API garantit qu'il est ouvert et additif : traitez donc un code que vous ne reconnaissez pas comme son type. Une liste fermée ferait d'une mise à jour de la gem le prix à payer pour lire un nouveau mode d'échec.
Un corps qui n'est pas l'enveloppe d'erreur de l'API lève malgré tout une OpenEmail::ApiError, avec type déduit du statut et code fixé à unrecognised_response. Une réponse en succès dont le corps n'est pas du JSON en lève une également.
retryable? décrit le statut, pas votre appel. Un appel qui peut être répété sans risque a déjà été réessayé au moment où il lève l'erreur, et un 429 comme send_quota_exceeded ou ai_quota_exceeded échoue de la même façon tant que son quota n'est pas réinitialisé : montrez-le donc à une personne plutôt que de boucler dessus.
Une exception levée par votre adaptateur devient une OpenEmail::NetworkError une fois épuisés les réessais que permet l'appel, avec l'originale dans original et cause. NameError, TypeError et ArgumentError font exception : elles signalent un bug dans l'adaptateur, et sont donc relevées telles quelles et jamais réessayées.
request_id
Chaque OpenEmail::ApiError porte l'id de requête envoyé par le serveur, issu du corps de l'erreur ou de l'en-tête x-request-id, et c'est la seule chose qui relie votre échec à une ligne du journal du serveur. Une réponse en succès renvoie le seul corps analysé : il n'y a donc pas d'id de requête à y lire.