Skip to content

Commit

Permalink
resolve #642 - Add Pattern Matching
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
baweaver committed Jan 19, 2021
1 parent f4fb336 commit 8532bb6
Show file tree
Hide file tree
Showing 25 changed files with 678 additions and 0 deletions.
20 changes: 20 additions & 0 deletions lib/http/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,26 @@ def close
@state = :clean
end

# Hash representation of a client
#
# @return [Hash[Symbol, Any]]
def to_h
{
connection: @connection,
state: @state,
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

def build_response(req, options)
Expand Down
29 changes: 29 additions & 0 deletions lib/http/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,35 @@ def expired?
!@conn_expires_at || @conn_expires_at < Time.now
end

# Hash representation of a connection
#
# @return [Hash[Symbol, Any]]
def to_h
{
persistent: @persistent,
keep_alive_timeout: @keep_alive_timeout,
pending_request: @pending_request,
pending_response: @pending_response,
failed_proxy_connect: @failed_proxy_connect,
buffer: @buffer,
parser: @parser,
socket: @socket,
status_code: status_code,
http_version: http_version,
headers: headers
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

# Sets up SSL context and starts TLS if needed.
Expand Down
20 changes: 20 additions & 0 deletions lib/http/content_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,25 @@ def initialize(mime_type = nil, charset = nil)
@mime_type = mime_type
@charset = charset
end

# Hash representaiton of ContentType
#
# @return [Hash[Symbol, Any]]
def to_h
{
mime_type: @mime_type,
charset: @charset,
}
end

# Pattern matching interface
#
# @param keys [Array[Symbol]]
# Keys to extract
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end
end
end
27 changes: 27 additions & 0 deletions lib/http/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,32 @@ def to_h
end
alias to_hash to_h

# Pattern matching interface
#
# @param keys [Array[Symbol]]
# Keys to extract
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
underscored_keys_map = underscored_keys_mapping

self
.to_h
.map { |k, v| [underscored_keys_map[k], v] }
.to_h
.slice(*keys)
end

# Returns headers key/value pairs.
#
# @return [Array<[String, String]>]
def to_a
@pile.map { |item| item[1..2] }
end

# Adds pattern matching interface using `to_a` as a base
alias_method :deconstruct, :to_a

# Returns human-readable representation of `self` instance.
#
# @return [String]
Expand Down Expand Up @@ -219,6 +238,14 @@ def coerce(object)

private

# Underscored version of HTTP Header keys for
# Pattern Matching
#
# @return [Hash[String, Symbol]]
def underscored_keys_mapping
Hash[keys.map { |k| [k, k.tr('A-Z-', 'a-z_').to_sym] }]
end

# Transforms `name` to canonical HTTP header capitalization
#
# @param [String] name
Expand Down
10 changes: 10 additions & 0 deletions lib/http/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,16 @@ def to_hash
Hash[*hash_pairs]
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_hash.slice(*keys)
end

def dup
dupped = super
yield(dupped) if block_given?
Expand Down
26 changes: 26 additions & 0 deletions lib/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,32 @@ def inspect
"#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
end

# Hash representation of a request
#
# @return [Hash[Symbol, Any]]
def to_h
{
verb: @verb,
uri: @uri,
scheme: @scheme,
proxy: @proxy,
version: @version,
headers: @headers,
body: @body,
port: port
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

# @!attribute [r] host
Expand Down
20 changes: 20 additions & 0 deletions lib/http/request/body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ def ==(other)
self.class == other.class && self.source == other.source # rubocop:disable Style/RedundantSelf
end

# Hash representation of a
#
# @return [Hash[Symbol, Any]]
def to_h
{
source: @source,
size: size
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

def rewind(io)
Expand Down
22 changes: 22 additions & 0 deletions lib/http/request/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,28 @@ def chunked?
@headers[Headers::TRANSFER_ENCODING] == CHUNKED
end

# Hash representation of a
#
# @return [Hash[Symbol, Any]]
def to_h
{
body: @body,
socket: @socket,
headers: @headers,
request_header: @request_header,
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

def write(data)
Expand Down
28 changes: 28 additions & 0 deletions lib/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,34 @@ def to_a
[status.to_i, headers.to_h, body.to_s]
end

# Adds pattern matching interface using `to_a` as a base
alias_method :deconstruct, :to_a

# Returns a Hash of accessible properties
#
# @return [Hash[Symbol, Any]]
def to_h
{
version: @version,
request: @request,
status: @status,
headers: @headers,
proxy_headers: @proxy_headers,
body: @body,
status: @status,
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

# Flushes body and returns self-reference
#
# @return [Response]
Expand Down
23 changes: 23 additions & 0 deletions lib/http/response/body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ def inspect
"#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
end

# Hash representation of a body
#
# @return [Hash[Symbol, Any]]
def to_h
{
stream: @stream,
connection: @connection,
streaming: @streaming,
contents: @contents,
encoding: @encoding,
}
end

# Pattern matching interface
#
# @param keys [Array[Symbol]]
# Keys to extract
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

# Retrieve encoding by name. If encoding cannot be found, default to binary.
Expand Down
19 changes: 19 additions & 0 deletions lib/http/response/inflater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ def readpartial(*args)
chunk
end

# Hash representation of an inflater
#
# @return [Hash[Symbol, Any]]
def to_h
{
connection: connection
}
end

# Pattern matching interface
#
# @param keys [Array[Symbol]]
# Keys to extract
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

private

def zstream
Expand Down
20 changes: 20 additions & 0 deletions lib/http/response/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@ def #{symbol}? # def bad_request?
RUBY
end

# Hash representation of a
#
# @return [Hash[Symbol, Any]]
def to_h
{
code: code,
reason: reason
}
end

# Pattern matching interface
#
# @param keys [Array]
# Keys to be extracted
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end

def __setobj__(obj)
raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to? :to_i

Expand Down
26 changes: 26 additions & 0 deletions lib/http/uri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,31 @@ def to_s
def inspect
format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
end

# Hash representation of a URI
#
# @return [Hash[Symbol, Any]]
def to_h
{
scheme: scheme,
user: user,
password: password,
host: host,
port: port,
path: path,
query: query,
fragment: fragment,
}
end

# Pattern matching interface for a URI
#
# @param keys [Array[Symbol]]
# Keys to extract from the URI
#
# @return [Hash[Symbol, Any]]
def deconstruct_keys(keys)
to_h.slice(*keys)
end
end
end
Loading

0 comments on commit 8532bb6

Please sign in to comment.