오류
모든 실패는 예외를 발생시킵니다. 거부에는 하나의 클래스, 응답 없음에는 또 하나의 클래스가 있으며, 모든 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 | type마다 하나씩 있는 ApiError의 하위 클래스입니다: 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`의 여덟 값 중 하나로, 이 집합은 동결되어 늘어나지 않습니다. 본문에 없으면 상태 코드에서 추론합니다.
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- 문제마다 하나의 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입니다. 키가 없거나, 자격 증명의 종류가 잘못되었거나, OpenEmail이 발급하지 않은 키입니다. |
| 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을 읽으며, 각 하위 클래스는 하나의 type을 나타냅니다. code는 API가 개방적이고 추가만 된다고 보장하는 값이므로 String으로 남아 있으며, 알아보지 못하는 값은 그 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 헤더에서 가져와 담고 있으며, 이 값만이 여러분의 실패를 서버 로그의 한 줄과 이어 줍니다. 성공하면 파싱된 본문만 반환되므로 거기서 읽을 요청 id는 없습니다.