diff --git a/lib/http/errors.rb b/lib/http/errors.rb index 5ff410df..2d0821fa 100644 --- a/lib/http/errors.rb +++ b/lib/http/errors.rb @@ -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 diff --git a/lib/http/features/raise_error.rb b/lib/http/features/raise_error.rb index 9236a61f..52588aef 100644 --- a/lib/http/features/raise_error.rb +++ b/lib/http/features/raise_error.rb @@ -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 @@ -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) diff --git a/test/http/features/raise_error_test.rb b/test/http/features/raise_error_test.rb index c14dda65..8cf3b8e6 100644 --- a/test/http/features/raise_error_test.rb +++ b/test/http/features/raise_error_test.rb @@ -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 -- @@ -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