Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions lib/http/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,52 @@ def initialize(response)
end
end

# Client errors 4xx
class ClientError < StatusError; end
class BadRequestError < ClientError; end
class UnauthorizedError < ClientError; end
class PaymentRequiredError < ClientError; end
class ForbiddenError < ClientError; end
class NotFoundError < ClientError; end
class MethodNotAllowedError < ClientError; end
class NotAcceptableError < ClientError; end
class ProxyAuthenticationRequiredError < ClientError; end
class RequestTimeoutError < ClientError; end
class ConflictError < ClientError; end
class GoneError < ClientError; end
class LengthRequiredError < ClientError; end
class PreconditionFailedError < ClientError; end
class ContentTooLargeError < ClientError; end
class UriTooLongError < ClientError; end
class UnsupportedMediaTypeError < ClientError; end
class RangeNotSatisfiableError < ClientError; end
class ExpectationFailedError < ClientError; end
class ImATeapotError < ClientError; end
class MisdirectedRequestError < ClientError; end
class UnprocessableContentError < ClientError; end
class LockedError < ClientError; end
class FailedDependencyError < ClientError; end
class TooEarlyError < ClientError; end
class UpgradeRequiredError < ClientError; end
class PreconditionRequiredError < ClientError; end
class TooManyRequestsError < ClientError; end
class RequestHeaderFieldsTooLargeError < ClientError; end
class UnavailableForLegalReasonsError < ClientError; end

# Server errors 5xx
class ServerError < StatusError; end
class InternalServerError < ServerError; end
class NotImplementedError < ServerError; end
class BadGatewayError < ServerError; end
class ServiceUnavailableError < ServerError; end
class GatewayTimeoutError < ServerError; end
class HttpVersionNotSupportedError < ServerError; end
class VariantAlsoNegotiatesError < ServerError; end
class InsufficientStorageError < ServerError; end
class LoopDetectedError < ServerError; end
class NotExtendedError < ServerError; end
class NetworkAuthenticationRequiredError < ServerError; end

# Raised when `Response#parse` fails due to any underlying reason (unexpected
# MIME type, or decoder fails). See `Exception#cause` for the original exception.
class ParseError < ResponseError; end
Expand Down
52 changes: 51 additions & 1 deletion lib/http/features/raise_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@ module HTTP
module Features
# Raises an error for non-successful HTTP responses
class RaiseError < Feature
CODE_TO_ERROR_CLASS = {
400 => BadRequestError,
401 => UnauthorizedError,
402 => PaymentRequiredError,
403 => ForbiddenError,
404 => NotFoundError,
405 => MethodNotAllowedError,
406 => NotAcceptableError,
407 => ProxyAuthenticationRequiredError,
408 => RequestTimeoutError,
409 => ConflictError,
410 => GoneError,
411 => LengthRequiredError,
412 => PreconditionFailedError,
413 => ContentTooLargeError,
414 => UriTooLongError,
415 => UnsupportedMediaTypeError,
416 => RangeNotSatisfiableError,
417 => ExpectationFailedError,
418 => ImATeapotError,
421 => MisdirectedRequestError,
422 => UnprocessableContentError,
423 => LockedError,
424 => FailedDependencyError,
425 => TooEarlyError,
426 => UpgradeRequiredError,
428 => PreconditionRequiredError,
429 => TooManyRequestsError,
431 => RequestHeaderFieldsTooLargeError,
451 => UnavailableForLegalReasonsError,
500 => InternalServerError,
501 => NotImplementedError,
502 => BadGatewayError,
503 => ServiceUnavailableError,
504 => GatewayTimeoutError,
505 => HttpVersionNotSupportedError,
506 => VariantAlsoNegotiatesError,
507 => InsufficientStorageError,
508 => LoopDetectedError,
510 => NotExtendedError,
511 => NetworkAuthenticationRequiredError
}.freeze

# Initializes the RaiseError feature
#
# @example
Expand All @@ -28,7 +71,14 @@ def wrap_response(response)
return response if response.code < 400
return response if @ignore.include?(response.code)

raise StatusError, response
default_error_class =
case response.code
when 400...500 then ClientError
when 500...600 then ServerError
else StatusError
end

raise CODE_TO_ERROR_CLASS.fetch(response.code, default_error_class), response
end

HTTP::Options.register_feature(:raise_error, self)
Expand Down
51 changes: 41 additions & 10 deletions test/http/features/raise_error_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,41 @@ def test_wrap_response_when_status_is_399_returns_original_response
assert_same response, result
end

def test_wrap_response_when_status_is_400_raises
def test_wrap_response_when_status_is_400_raises_bad_request_error
feature = HTTP::Features::RaiseError.new(ignore: [])
response = build_response(status: 400)
err = assert_raises(HTTP::StatusError) { feature.wrap_response(response) }
err = assert_raises(HTTP::BadRequestError) { feature.wrap_response(response) }
assert_equal "Unexpected status code 400", err.message
end

def test_wrap_response_when_status_is_599_raises
def test_wrap_response_when_status_is_500_raises_internal_server_error
feature = HTTP::Features::RaiseError.new(ignore: [])
response = build_response(status: 500)
err = assert_raises(HTTP::InternalServerError) { feature.wrap_response(response) }
assert_equal "Unexpected status code 500", err.message
end

def test_wrap_response_when_unmapped_4xx_status_raises_client_error
feature = HTTP::Features::RaiseError.new(ignore: [])
response = build_response(status: 499)
err = assert_raises(HTTP::ClientError) { feature.wrap_response(response) }
assert_equal "Unexpected status code 499", err.message
end

def test_wrap_response_when_unmapped_5xx_status_raises_server_error
feature = HTTP::Features::RaiseError.new(ignore: [])
response = build_response(status: 599)
err = assert_raises(HTTP::StatusError) { feature.wrap_response(response) }
err = assert_raises(HTTP::ServerError) { feature.wrap_response(response) }
assert_equal "Unexpected status code 599", err.message
end

def test_wrap_response_when_error_status_is_ignored_returns_original_response
feature = HTTP::Features::RaiseError.new(ignore: [500])
response = build_response(status: 500)
result = feature.wrap_response(response)

assert_same response, result
def test_each_error_code
HTTP::Features::RaiseError::CODE_TO_ERROR_CLASS.each do |status, expected_error_class|
feature = HTTP::Features::RaiseError.new(ignore: [])
response = build_response(status: status)
err = assert_raises(expected_error_class) { feature.wrap_response(response) }
refute_nil err.message
end
end

# -- #initialize --
Expand All @@ -74,4 +89,20 @@ def test_initialize_defaults_ignore_to_empty_array
def test_initialize_is_a_feature
assert_kind_of HTTP::Feature, HTTP::Features::RaiseError.new
end

def test_wrap_response_when_status_is_400_and_ignored_returns_original_response
feature = HTTP::Features::RaiseError.new(ignore: [400])
response = build_response(status: 400)
result = feature.wrap_response(response)

assert_same response, result
end

def test_wrap_response_when_status_is_500_and_ignored_returns_original_response
feature = HTTP::Features::RaiseError.new(ignore: [500])
response = build_response(status: 500)
result = feature.wrap_response(response)

assert_same response, result
end
end
Loading