Produce an error on bad array of maps

This produces an error when we detect an "array of maps" that cannot be
encoded with `application/x-www-form-urlencoded`; that is to say, one
that does not have each hash starting with a consistent key that will
allow a Rack-compliant server to recognize boundaries.

So for example, this is fine:

```
items: [
    { :type => 'sku', :parent => 'sku_94ZYSC0wppRTbk' },
    { :type => 'discount', :amount => -10000, :currency => 'cad', :description => 'potato' }
],
```

But this is _not_ okay:

```
items: [
    { :type => 'sku', :parent => 'sku_94ZYSC0wppRTbk' },
    { :amount => -10000, :currency => 'cad', :description => 'potato', :type => 'discount' }
],
```

(`type` should be moved to the beginning of the array.)

The purpose of this change is to give users better feedback when they
run into an encoding problem like this one. Currently, they just get
something confusing from the server, and someone on support usually
needs to examine a request log to figure out what happened.

CI will fail until the changes in #453 are brought in.
This commit is contained in:
Brandur 2016-08-25 11:26:46 -07:00
parent fb9fd44dd6
commit a5a9eb94db
3 changed files with 56 additions and 1 deletions

View File

@ -140,6 +140,7 @@ module Stripe
if value.is_a?(Hash)
result += flatten_params(value, calculated_key)
elsif value.is_a?(Array)
check_array_of_maps_start_keys!(value)
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
@ -196,5 +197,44 @@ module Stripe
raise TypeError.new("api_key must be a string") unless key.is_a?(String)
key
end
private
# We use a pretty janky version of form encoding (Rack's) that supports
# more complex data structures like maps and arrays through the use of
# specialized syntax. To encode an array of maps like:
#
# [{a: 1, b: 2}, {a: 3, b: 4}]
#
# We have to produce something that looks like this:
#
# arr[][a]=1&arr[][b]=2&arr[][a]=3&arr[][b]=4
#
# The only way for the server to recognize that this is a two item array is
# that it notices the repetition of element "a", so it's key that these
# repeated elements are encoded first.
#
# This method is invoked for any arrays being encoded and checks that if
# the array contains all non-empty maps, that each of those maps must start
# with the same key so that their boundaries can be properly encoded.
def self.check_array_of_maps_start_keys!(arr)
expected_key = nil
arr.each do |item|
return if !item.is_a?(Hash)
return if item.count == 0
first_key = item.first[0]
if expected_key
if expected_key != first_key
raise ArgumentError,
"All maps nested in an array should start with the same key " +
"(expected starting key '#{expected_key}', got '#{first_key}')"
end
else
expected_key = first_key
end
end
end
end
end

View File

@ -138,7 +138,7 @@ module Stripe
:amount => 100,
:source => 'btcrcv_test_receiver',
:currency => "usd",
:level3 => [{:red => 'firstred'}, {:one => 'fish', :red => 'another'}]
:level3 => [{:red => 'firstred'}, {:red => 'another', :one => 'fish'}]
})
assert c.paid
end

View File

@ -20,6 +20,21 @@ module Stripe
)
end
should "#encode_params should throw an error an on array of maps that cannot be encoded" do
params = {
:a => [
{ :a => 1, :b => 2 },
{ :c => 3, :a => 4 },
]
}
e = assert_raises(ArgumentError) do
Stripe::Util.encode_parameters(params)
end
expected = "All maps nested in an array should start with the same key " +
"(expected starting key 'a', got 'c')"
assert_equal expected, e.message
end
should "#url_encode should prepare strings for HTTP" do
assert_equal "foo", Stripe::Util.url_encode("foo")
assert_equal "foo", Stripe::Util.url_encode(:foo)