Module: Onetime::Errors

Defined in:
lib/onetime/errors.rb

Overview

Builds the appropriate APIError subclass from an HTTP response.

Constant Summary collapse

ACCOUNT_REQUIRED_MESSAGE =

Used only when the server sends no message of its own.

"This operation requires an authenticated account: send your " \
"customer extid and API token (see Onetime::Client.new)."
ERROR_TYPE_MAP =

Maps the ADR-013 error_type (the machine-readable class name the server sends) to a client exception class. Falls through to status code mapping when the type is absent or unrecognized.

{
  "FormError"           => BadRequestError,
  "RequiresAccount"     => AccountRequiredError,
  "AccountRequired"     => AccountRequiredError,
  "RecordNotFound"      => NotFoundError,
  "NotFound"            => NotFoundError,
  "Forbidden"           => ForbiddenError,
  "GuestRoutesDisabled" => ForbiddenError,
  "EntitlementRequired" => EntitlementError,
  "LimitExceeded"       => RateLimitError,
  "ServerError"         => ServerError,
}.freeze
ACCOUNT_REQUIRED_KEYS =

The account requirement can arrive in any of several slots depending on the endpoint and API version: as the FormError field, as the i18n error_key, or as a code. Matching all of them keeps the mapping stable across server versions.

%w[error_type field error_key code].freeze
ACCOUNT_REQUIRED_PATTERN =

Matches the bare token as well as prefixed codes such as "GUEST_CONCEAL_REQUIRES_ACCOUNT" (underscores are word characters, so \b is no help here).

/(?:\A|[^a-z0-9])(requires[_-]?account|account[_-]?required)(?:\z|[^a-z0-9])/i
STATUS_MAP =
{
  400 => BadRequestError,
  401 => AuthenticationError,
  403 => ForbiddenError,
  404 => NotFoundError,
  409 => ConflictError,
  422 => BadRequestError,
  429 => RateLimitError,
}.freeze

Class Method Summary collapse

Class Method Details

.account_required?(body) ⇒ Boolean

True when the body says the operation needs an authenticated account, in whichever slot this server version reports it.

Returns:

  • (Boolean)


136
137
138
139
140
# File 'lib/onetime/errors.rb', line 136

def (body)
  ACCOUNT_REQUIRED_KEYS.any? do |key|
    ACCOUNT_REQUIRED_PATTERN.match?(body[key].to_s)
  end
end

.default_message(status) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/onetime/errors.rb', line 148

def default_message(status)
  case status
  when 400 then "Bad request"
  when 401 then "Authentication failed"
  when 403 then "Forbidden"
  when 404 then "Not found"
  when 429 then "Rate limit exceeded"
  when 500..599 then "Server error (#{status})"
  else "Request failed (#{status})"
  end
end

.error_class(error_type, status) ⇒ Object



142
143
144
145
146
# File 'lib/onetime/errors.rb', line 142

def error_class(error_type, status)
  ERROR_TYPE_MAP[error_type] ||
    STATUS_MAP[status] ||
    (status.to_i >= 500 ? ServerError : APIError)
end

.from_response(response) ⇒ Onetime::APIError

Parameters:

Returns:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/onetime/errors.rb', line 111

def from_response(response)
  body   = response.data.is_a?(Hash) ? response.data : {}
  status = response.http_status

   = (body)
  klass   =  ? AccountRequiredError : error_class(body["error_type"], status)
  message = body["error"] || body["message"] ||
            ( ? ACCOUNT_REQUIRED_MESSAGE : default_message(status))

  klass.new(
    message,
    http_status: status,
    error_type:  body["error_type"],
    code:        body["code"],
    field:       body["field"],
    error_key:   body["error_key"],
    retry_after: body["retry_after"],
    entitlement: body["entitlement"],
    body:        response.data,
    response:    response,
  )
end