Skip to content

Commit bd4d522

Browse files
committed
Add pattern matching support to Net::HTTPExceptions
Implements deconstruct_keys for HTTP exception classes to enable pattern matching on error handling. Example: begin http.request(req) rescue => e case e in HTTPRetriableError[response: res] if res.code == '503' retry_with_backoff in HTTPClientException[message: /not found/] handle_not_found end end
1 parent ec9c70a commit bd4d522

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

lib/net/http/exceptions.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,37 @@ module Net
44
# You cannot use Net::HTTPExceptions directly; instead, you must use
55
# its subclasses.
66
module HTTPExceptions
7+
# Valid keys for pattern matching via #deconstruct_keys.
8+
PATTERN_MATCHING_KEYS = %i[message response].freeze
9+
710
def initialize(msg, res) #:nodoc:
811
super msg
912
@response = res
1013
end
1114
attr_reader :response
1215
alias data response #:nodoc: obsolete
16+
17+
# Returns a hash of exception attributes for pattern matching.
18+
#
19+
# Valid keys are: +:message+, +:response+
20+
#
21+
# Example:
22+
#
23+
# begin
24+
# http.request(req)
25+
# rescue => e
26+
# case e
27+
# in HTTPRetriableError[response: { code: '503' }]
28+
# retry_with_backoff
29+
# in HTTPClientException[response: { code: '404' }]
30+
# handle_not_found
31+
# end
32+
# end
33+
#
34+
def deconstruct_keys(keys)
35+
valid_keys = keys ? PATTERN_MATCHING_KEYS & keys : PATTERN_MATCHING_KEYS
36+
valid_keys.to_h { |key| [key, public_send(key)] }
37+
end
1338
end
1439

1540
class HTTPError < ProtocolError
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: false
2+
require 'net/http'
3+
require 'test/unit'
4+
5+
class HTTPExceptionsTest < Test::Unit::TestCase
6+
def test_deconstruct_keys
7+
response = Net::HTTPOK.new('1.1', '200', 'OK')
8+
error = Net::HTTPError.new('test error', response)
9+
10+
keys = error.deconstruct_keys(nil)
11+
assert_equal 'test error', keys[:message]
12+
assert_equal response, keys[:response]
13+
end
14+
15+
def test_deconstruct_keys_with_specific_keys
16+
response = Net::HTTPNotFound.new('1.1', '404', 'Not Found')
17+
error = Net::HTTPClientException.new('not found', response)
18+
19+
keys = error.deconstruct_keys([:message])
20+
assert_equal({message: 'not found'}, keys)
21+
end
22+
23+
def test_pattern_matching
24+
response = Net::HTTPServiceUnavailable.new('1.1', '503', 'Service Unavailable')
25+
error = Net::HTTPRetriableError.new('service unavailable', response)
26+
27+
begin
28+
matched = instance_eval <<~RUBY, __FILE__, __LINE__ + 1
29+
case error
30+
in message: /unavailable/, response:
31+
true
32+
else
33+
false
34+
end
35+
RUBY
36+
assert_equal true, matched
37+
rescue SyntaxError
38+
omit "Pattern matching requires Ruby 2.7+"
39+
end
40+
end
41+
42+
def test_pattern_matching_with_response_attributes
43+
response = Net::HTTPNotFound.new('1.1', '404', 'Not Found')
44+
error = Net::HTTPClientException.new('not found', response)
45+
46+
begin
47+
matched = instance_eval <<~RUBY, __FILE__, __LINE__ + 1
48+
case error
49+
in response: res if res.code == '404'
50+
true
51+
else
52+
false
53+
end
54+
RUBY
55+
assert_equal true, matched
56+
rescue SyntaxError
57+
omit "Pattern matching requires Ruby 2.7+"
58+
end
59+
end
60+
end

0 commit comments

Comments
 (0)