Skip to content

Commit

Permalink
fix: parse query string to hash for v2 interactions
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Nov 3, 2020
1 parent 8246d3b commit faff17c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 2 deletions.
9 changes: 7 additions & 2 deletions lib/pact/consumer_contract/interaction_v2_parser.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'pact/consumer_contract/request'
require 'pact/consumer_contract/response'
require 'pact/consumer_contract/provider_state'
require 'pact/consumer_contract/query'
require 'pact/symbolize_keys'
require 'pact/matching_rules'
require 'pact/errors'
Expand All @@ -15,13 +16,17 @@ def self.call hash, options
response = parse_response(hash['response'], options)
provider_states = parse_provider_states(hash['providerState'] || hash['provider_state'])
metadata = parse_metadata(hash['metadata'])
Interaction.new(symbolize_keys(hash).merge(request: request,
response: response,
Interaction.new(symbolize_keys(hash).merge(request: request,
response: response,
provider_states: provider_states,
metadata: metadata))
end

def self.parse_request request_hash, options
if request_hash['query'].is_a?(String)
request_hash = request_hash.dup
request_hash['query'] = Pact::Query.parse_string(request_hash['query'])
end
request_hash = Pact::MatchingRules.merge(request_hash, request_hash['matchingRules'], options)
Pact::Request::Expected.from_hash(request_hash)
end
Expand Down
98 changes: 98 additions & 0 deletions lib/pact/consumer_contract/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,110 @@

module Pact
class Query
DEFAULT_SEP = /[&;] */n
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }

def self.create query
if query.is_a? Hash
Pact::QueryHash.new(query)
else
Pact::QueryString.new(query)
end
end

def self.parse_string query_string
parsed_query = parse_query(query_string)

# If Rails nested params...
if parsed_query.keys.any?{ | key| key.include?("[") }
parse_nested_query(query_string)
else
parsed_query.each_with_object({}) do | (key, value), new_hash |
new_hash[key] = [*value]
end
end
end

# Ripped from Rack to avoid adding an unnecessary dependency, thank you Rack
# https://github.com/rack/rack/blob/649c72bab9e7b50d657b5b432d0c205c95c2be07/lib/rack/utils.rb
def self.parse_query(qs, d = nil, &unescaper)
unescaper ||= method(:unescape)

params = {}

(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
next if p.empty?
k, v = p.split('=', 2).map!(&unescaper)

if cur = params[k]
if cur.class == Array
params[k] << v
else
params[k] = [cur, v]
end
else
params[k] = v
end
end

return params.to_h
end

def self.parse_nested_query(qs, d = nil)
params = {}

unless qs.nil? || qs.empty?
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map! { |s| unescape(s) }

normalize_params(params, k, v)
end
end

return params.to_h
end

def self.normalize_params(params, name, v)
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
k = $1 || ''
after = $' || ''

if k.empty?
if !v.nil? && name == "[]"
return Array(v)
else
return
end
end

if after == ''
params[k] = v
elsif after == "["
params[name] = v
elsif after == "[]"
params[k] ||= []
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
params[k] << v
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
child_key = $1
params[k] ||= []
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
normalize_params(params[k].last, child_key, v)
else
params[k] << normalize_params({}, child_key, v)
end
else
params[k] ||= {}
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
params[k] = normalize_params(params[k], after, v, depth - 1)
end

params
end

def self.unescape(s, encoding = Encoding::UTF_8)
URI.decode_www_form_component(s, encoding)
end
end
end
15 changes: 15 additions & 0 deletions spec/lib/pact/consumer_contract/interaction_v2_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ module Pact
expect(subject.provider_state).to eq "foo"
end
end

describe "query" do
let(:interaction_hash) do
{
"description" => "description",
"request" => { "method" => "GET", "path" => "/", "query" => "foo=bar1&foo=bar2"},
"response" => { "status" => 200 },
"providerState" => "foo"
}
end

it "parses a string query into a hash" do
expect(subject.request.query).to eq Pact::QueryHash.new("foo"=> [ "bar1", "bar2" ])
end
end
end
end
end
33 changes: 33 additions & 0 deletions spec/lib/pact/consumer_contract/query_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'pact/consumer_contract/query'

module Pact
describe Query do
describe ".parse_string" do
subject { Query.parse_string(query_string) }

describe "with a non nested query string" do
let(:query_string) { "foo=bar1" }

it "returns a map of string to array" do
expect(subject).to eq "foo" => ["bar1"]
end
end

describe "with a non nested query string with multiple params with the same name" do
let(:query_string) { "foo=bar1&foo=bar2" }

it "returns a map of string to array" do
expect(subject).to eq "foo" => ["bar1", "bar2"]
end
end

describe "with a rails style nested query" do
let(:query_string) { "foo=bar1&foo=bar2&baz[]=thing1&baz[]=thing2" }

it "returns a nested map" do
expect(subject).to eq "foo" => "bar2", "baz" => ["thing1", "thing2"]
end
end
end
end
end

0 comments on commit faff17c

Please sign in to comment.