diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index ceec8f7..3066420 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -4,12 +4,37 @@ module Net # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. module HTTPExceptions + # Valid keys for pattern matching via #deconstruct_keys. + PATTERN_MATCHING_KEYS = %i[message response].freeze + def initialize(msg, res) #:nodoc: super msg @response = res end attr_reader :response alias data response #:nodoc: obsolete + + # Returns a hash of exception attributes for pattern matching. + # + # Valid keys are: +:message+, +:response+ + # + # Example: + # + # begin + # http.request(req) + # rescue => e + # case e + # in HTTPRetriableError[response: { code: '503' }] + # retry_with_backoff + # in HTTPClientException[response: { code: '404' }] + # handle_not_found + # end + # end + # + def deconstruct_keys(keys) + valid_keys = keys ? PATTERN_MATCHING_KEYS & keys : PATTERN_MATCHING_KEYS + valid_keys.to_h { |key| [key, public_send(key)] } + end end class HTTPError < ProtocolError diff --git a/test/net/http/test_httpexceptions.rb b/test/net/http/test_httpexceptions.rb new file mode 100644 index 0000000..870509f --- /dev/null +++ b/test/net/http/test_httpexceptions.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: false +require 'net/http' +require 'test/unit' + +class HTTPExceptionsTest < Test::Unit::TestCase + def test_deconstruct_keys + response = Net::HTTPOK.new('1.1', '200', 'OK') + error = Net::HTTPError.new('test error', response) + + keys = error.deconstruct_keys(nil) + assert_equal 'test error', keys[:message] + assert_equal response, keys[:response] + end + + def test_deconstruct_keys_with_specific_keys + response = Net::HTTPNotFound.new('1.1', '404', 'Not Found') + error = Net::HTTPClientException.new('not found', response) + + keys = error.deconstruct_keys([:message]) + assert_equal({message: 'not found'}, keys) + end + + def test_pattern_matching + response = Net::HTTPServiceUnavailable.new('1.1', '503', 'Service Unavailable') + error = Net::HTTPRetriableError.new('service unavailable', response) + + begin + matched = instance_eval <<~RUBY, __FILE__, __LINE__ + 1 + case error + in message: /unavailable/, response: + true + else + false + end + RUBY + assert_equal true, matched + rescue SyntaxError + omit "Pattern matching requires Ruby 2.7+" + end + end + + def test_pattern_matching_with_response_attributes + response = Net::HTTPNotFound.new('1.1', '404', 'Not Found') + error = Net::HTTPClientException.new('not found', response) + + begin + matched = instance_eval <<~RUBY, __FILE__, __LINE__ + 1 + case error + in response: res if res.code == '404' + true + else + false + end + RUBY + assert_equal true, matched + rescue SyntaxError + omit "Pattern matching requires Ruby 2.7+" + end + end +end