Exception: FulfilApi::HttpError

Inherits:
Error
  • Object
show all
Defined in:
lib/fulfil_api/http_error.rb

Overview

Note:

HttpError inherits from Error, so any code that already rescues Error keeps catching these exceptions.

The HttpError is raised whenever a request to an API endpoint of Fulfil fails.

Every HTTP status code Fulfil can respond with has a dedicated subclass, named after the status code it represents. A 429 response raises a TooManyRequests, a 422 an UnprocessableEntity, and so on. See STATUS_CODES for the full list. This lets callers rescue the exact failure they care about instead of rescuing everything and inspecting the status code themselves.

Requests that never reached Fulfil — a connection reset, a DNS failure, a timeout — have no status code to name and raise a HttpError.

Examples:

rescuing one specific HTTP status code

begin
  FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
rescue FulfilApi::HttpError::TooManyRequests => exception
  sleep exception.response_headers["retry-after"].to_i
  retry
end

rescuing any failed request

begin
  FulfilApi::Resource.set(model_name: "sale.sale").find_by(["id", "=", 100])
rescue FulfilApi::HttpError => exception
  Rails.logger.error("Fulfil responded with #{exception.status_code}: #{exception.message}")
end

Constant Summary collapse

MESSAGE_KEYS =

The keys of an error response of Fulfil that can hold the human readable message, in the order they're preferred.

Fulfil is not consistent in how it reports failures: HTTP level errors carry a description, application level errors a message, and a handful of endpoints only return an error.

%w[description message error].freeze
STATUS_CODES =

Maps an HTTP status code onto the name of the FulfilApi::HttpError subclass representing it.

{
  400 => :BadRequest,
  401 => :Unauthorized,
  402 => :PaymentRequired,
  403 => :Forbidden,
  404 => :NotFound,
  405 => :MethodNotAllowed,
  406 => :NotAcceptable,
  407 => :ProxyAuthenticationRequired,
  408 => :RequestTimeout,
  409 => :Conflict,
  410 => :Gone,
  411 => :LengthRequired,
  412 => :PreconditionFailed,
  413 => :PayloadTooLarge,
  414 => :UriTooLong,
  415 => :UnsupportedMediaType,
  416 => :RangeNotSatisfiable,
  417 => :ExpectationFailed,
  418 => :ImATeapot,
  421 => :MisdirectedRequest,
  422 => :UnprocessableEntity,
  423 => :Locked,
  424 => :FailedDependency,
  425 => :TooEarly,
  426 => :UpgradeRequired,
  428 => :PreconditionRequired,
  429 => :TooManyRequests,
  431 => :RequestHeaderFieldsTooLarge,
  451 => :UnavailableForLegalReasons,
  500 => :InternalServerError,
  501 => :NotImplemented,
  502 => :BadGateway,
  503 => :ServiceUnavailable,
  504 => :GatewayTimeout,
  505 => :HttpVersionNotSupported,
  506 => :VariantAlsoNegotiates,
  507 => :InsufficientStorage,
  508 => :LoopDetected,
  510 => :NotExtended,
  511 => :NetworkAuthenticationRequired
}.freeze
CLASSES_BY_STATUS_CODE =

Defines a dedicated exception class for every status code in STATUS_CODES and indexes them by their status code, so for_status_code can look one up without going through Module#const_get.

STATUS_CODES.transform_values do |class_name|
  const_set(class_name, Class.new(self))
end.freeze

Instance Attribute Summary

Attributes inherited from Error

#details

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Error

#initialize

Constructor Details

This class inherits a constructor from FulfilApi::Error

Class Method Details

.for_status_code(status_code) ⇒ Class<FulfilApi::HttpError>

Looks up the FulfilApi::HttpError subclass representing the given HTTP status code.

Parameters:

  • status_code (Integer, nil)

    The HTTP status code of the response.

Returns:



102
103
104
# File 'lib/fulfil_api/http_error.rb', line 102

def for_status_code(status_code)
  CLASSES_BY_STATUS_CODE.fetch(status_code, HttpError)
end

.from_faraday_error(exception) ⇒ FulfilApi::HttpError

Builds the most specific FulfilApi::HttpError for the given Faraday exception.

Parameters:

  • exception (Faraday::Error)

    Any error raised by Faraday during the execution of the HTTP request to the API endpoint.

Returns:



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/fulfil_api/http_error.rb', line 111

def from_faraday_error(exception)
  details = {
    response_body: exception.response_body,
    response_headers: exception.response_headers,
    response_status: exception.response_status
  }

  for_status_code(exception.response_status).new(
    message_from(exception.response_body) || exception.message, details: details
  )
end

Instance Method Details

#messageString

Note:

StandardError#message delegates to StandardError#to_s, which still holds the message passed to the constructor.

Unlike Error, the message is returned as-is. The name of the exception class already tells you what went wrong, so prefixing it only gets in the way of the description reported by Fulfil.

Returns:

  • (String)


158
159
160
# File 'lib/fulfil_api/http_error.rb', line 158

def message
  to_s
end

#response_bodyString, ...

Returns The raw response body of the API endpoint of Fulfil.

Returns:

  • (String, Hash, nil)

    The raw response body of the API endpoint of Fulfil.



163
164
165
# File 'lib/fulfil_api/http_error.rb', line 163

def response_body
  details&.dig(:response_body)
end

#response_headersHash?

Returns The response headers of the API endpoint of Fulfil.

Returns:

  • (Hash, nil)

    The response headers of the API endpoint of Fulfil.



168
169
170
# File 'lib/fulfil_api/http_error.rb', line 168

def response_headers
  details&.dig(:response_headers)
end

#status_codeInteger?

Returns The HTTP status code of the response, or nil when the request never reached Fulfil.

Returns:

  • (Integer, nil)

    The HTTP status code of the response, or nil when the request never reached Fulfil.



174
175
176
# File 'lib/fulfil_api/http_error.rb', line 174

def status_code
  details&.dig(:response_status)
end