Skip to content

Commit 8532bb6

Browse files
committed
resolve #642 - Add Pattern Matching
Resolves feature request #642 requesting the addition of Pattern Matching hooks for Ruby 2.7+ by introducing `to_h`, `deconstruct`, and `deconstruct_keys` to core classes. This change also addresses spec changes by gating pattern matching specs on Ruby 2.7+ to prevent failures in older versions. All specs have also been given a pattern matching spec matching their implementation to demonstrate potential usages. While demonstrational usages could dive into nested classes that are in `HTTP` this was avoided to keep tests isolated from eachother.
1 parent f4fb336 commit 8532bb6

25 files changed

+678
-0
lines changed

lib/http/client.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,26 @@ def close
102102
@state = :clean
103103
end
104104

105+
# Hash representation of a client
106+
#
107+
# @return [Hash[Symbol, Any]]
108+
def to_h
109+
{
110+
connection: @connection,
111+
state: @state,
112+
}
113+
end
114+
115+
# Pattern matching interface
116+
#
117+
# @param keys [Array]
118+
# Keys to be extracted
119+
#
120+
# @return [Hash[Symbol, Any]]
121+
def deconstruct_keys(keys)
122+
to_h.slice(*keys)
123+
end
124+
105125
private
106126

107127
def build_response(req, options)

lib/http/connection.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,35 @@ def expired?
145145
!@conn_expires_at || @conn_expires_at < Time.now
146146
end
147147

148+
# Hash representation of a connection
149+
#
150+
# @return [Hash[Symbol, Any]]
151+
def to_h
152+
{
153+
persistent: @persistent,
154+
keep_alive_timeout: @keep_alive_timeout,
155+
pending_request: @pending_request,
156+
pending_response: @pending_response,
157+
failed_proxy_connect: @failed_proxy_connect,
158+
buffer: @buffer,
159+
parser: @parser,
160+
socket: @socket,
161+
status_code: status_code,
162+
http_version: http_version,
163+
headers: headers
164+
}
165+
end
166+
167+
# Pattern matching interface
168+
#
169+
# @param keys [Array]
170+
# Keys to be extracted
171+
#
172+
# @return [Hash[Symbol, Any]]
173+
def deconstruct_keys(keys)
174+
to_h.slice(*keys)
175+
end
176+
148177
private
149178

150179
# Sets up SSL context and starts TLS if needed.

lib/http/content_type.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,25 @@ def initialize(mime_type = nil, charset = nil)
3030
@mime_type = mime_type
3131
@charset = charset
3232
end
33+
34+
# Hash representaiton of ContentType
35+
#
36+
# @return [Hash[Symbol, Any]]
37+
def to_h
38+
{
39+
mime_type: @mime_type,
40+
charset: @charset,
41+
}
42+
end
43+
44+
# Pattern matching interface
45+
#
46+
# @param keys [Array[Symbol]]
47+
# Keys to extract
48+
#
49+
# @return [Hash[Symbol, Any]]
50+
def deconstruct_keys(keys)
51+
to_h.slice(*keys)
52+
end
3353
end
3454
end

lib/http/headers.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,32 @@ def to_h
115115
end
116116
alias to_hash to_h
117117

118+
# Pattern matching interface
119+
#
120+
# @param keys [Array[Symbol]]
121+
# Keys to extract
122+
#
123+
# @return [Hash[Symbol, Any]]
124+
def deconstruct_keys(keys)
125+
underscored_keys_map = underscored_keys_mapping
126+
127+
self
128+
.to_h
129+
.map { |k, v| [underscored_keys_map[k], v] }
130+
.to_h
131+
.slice(*keys)
132+
end
133+
118134
# Returns headers key/value pairs.
119135
#
120136
# @return [Array<[String, String]>]
121137
def to_a
122138
@pile.map { |item| item[1..2] }
123139
end
124140

141+
# Adds pattern matching interface using `to_a` as a base
142+
alias_method :deconstruct, :to_a
143+
125144
# Returns human-readable representation of `self` instance.
126145
#
127146
# @return [String]
@@ -219,6 +238,14 @@ def coerce(object)
219238

220239
private
221240

241+
# Underscored version of HTTP Header keys for
242+
# Pattern Matching
243+
#
244+
# @return [Hash[String, Symbol]]
245+
def underscored_keys_mapping
246+
Hash[keys.map { |k| [k, k.tr('A-Z-', 'a-z_').to_sym] }]
247+
end
248+
222249
# Transforms `name` to canonical HTTP header capitalization
223250
#
224251
# @param [String] name

lib/http/options.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ def to_hash
169169
Hash[*hash_pairs]
170170
end
171171

172+
# Pattern matching interface
173+
#
174+
# @param keys [Array]
175+
# Keys to be extracted
176+
#
177+
# @return [Hash[Symbol, Any]]
178+
def deconstruct_keys(keys)
179+
to_hash.slice(*keys)
180+
end
181+
172182
def dup
173183
dupped = super
174184
yield(dupped) if block_given?

lib/http/request.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,32 @@ def inspect
199199
"#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
200200
end
201201

202+
# Hash representation of a request
203+
#
204+
# @return [Hash[Symbol, Any]]
205+
def to_h
206+
{
207+
verb: @verb,
208+
uri: @uri,
209+
scheme: @scheme,
210+
proxy: @proxy,
211+
version: @version,
212+
headers: @headers,
213+
body: @body,
214+
port: port
215+
}
216+
end
217+
218+
# Pattern matching interface
219+
#
220+
# @param keys [Array]
221+
# Keys to be extracted
222+
#
223+
# @return [Hash[Symbol, Any]]
224+
def deconstruct_keys(keys)
225+
to_h.slice(*keys)
226+
end
227+
202228
private
203229

204230
# @!attribute [r] host

lib/http/request/body.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ def ==(other)
4949
self.class == other.class && self.source == other.source # rubocop:disable Style/RedundantSelf
5050
end
5151

52+
# Hash representation of a
53+
#
54+
# @return [Hash[Symbol, Any]]
55+
def to_h
56+
{
57+
source: @source,
58+
size: size
59+
}
60+
end
61+
62+
# Pattern matching interface
63+
#
64+
# @param keys [Array]
65+
# Keys to be extracted
66+
#
67+
# @return [Hash[Symbol, Any]]
68+
def deconstruct_keys(keys)
69+
to_h.slice(*keys)
70+
end
71+
5272
private
5373

5474
def rewind(io)

lib/http/request/writer.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ def chunked?
102102
@headers[Headers::TRANSFER_ENCODING] == CHUNKED
103103
end
104104

105+
# Hash representation of a
106+
#
107+
# @return [Hash[Symbol, Any]]
108+
def to_h
109+
{
110+
body: @body,
111+
socket: @socket,
112+
headers: @headers,
113+
request_header: @request_header,
114+
}
115+
end
116+
117+
# Pattern matching interface
118+
#
119+
# @param keys [Array]
120+
# Keys to be extracted
121+
#
122+
# @return [Hash[Symbol, Any]]
123+
def deconstruct_keys(keys)
124+
to_h.slice(*keys)
125+
end
126+
105127
private
106128

107129
def write(data)

lib/http/response.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,34 @@ def to_a
9090
[status.to_i, headers.to_h, body.to_s]
9191
end
9292

93+
# Adds pattern matching interface using `to_a` as a base
94+
alias_method :deconstruct, :to_a
95+
96+
# Returns a Hash of accessible properties
97+
#
98+
# @return [Hash[Symbol, Any]]
99+
def to_h
100+
{
101+
version: @version,
102+
request: @request,
103+
status: @status,
104+
headers: @headers,
105+
proxy_headers: @proxy_headers,
106+
body: @body,
107+
status: @status,
108+
}
109+
end
110+
111+
# Pattern matching interface
112+
#
113+
# @param keys [Array]
114+
# Keys to be extracted
115+
#
116+
# @return [Hash[Symbol, Any]]
117+
def deconstruct_keys(keys)
118+
to_h.slice(*keys)
119+
end
120+
93121
# Flushes body and returns self-reference
94122
#
95123
# @return [Response]

lib/http/response/body.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ def inspect
7272
"#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
7373
end
7474

75+
# Hash representation of a body
76+
#
77+
# @return [Hash[Symbol, Any]]
78+
def to_h
79+
{
80+
stream: @stream,
81+
connection: @connection,
82+
streaming: @streaming,
83+
contents: @contents,
84+
encoding: @encoding,
85+
}
86+
end
87+
88+
# Pattern matching interface
89+
#
90+
# @param keys [Array[Symbol]]
91+
# Keys to extract
92+
#
93+
# @return [Hash[Symbol, Any]]
94+
def deconstruct_keys(keys)
95+
to_h.slice(*keys)
96+
end
97+
7598
private
7699

77100
# Retrieve encoding by name. If encoding cannot be found, default to binary.

0 commit comments

Comments
 (0)