Skip to content

Commit f5386d8

Browse files
committed
Fix Urlencoded for nested Hashes (and Arrays)
1 parent 0280035 commit f5386d8

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

lib/http/form_data/urlencoded.rb

+34-2
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,45 @@ def encoder=(implementation)
5353
end
5454

5555
# Returns form data encoder implementation.
56-
# Default: `URI.encode_www_form`.
56+
# Default: custom realization.
5757
#
5858
# @see .encoder=
5959
# @return [#call]
6060
def encoder
61-
@encoder ||= ::URI.method(:encode_www_form)
61+
@encoder ||= DefaultEncoder.method(:encode)
6262
end
63+
64+
# Default encoder
65+
module DefaultEncoder
66+
class << self
67+
def encode(value, prefix = nil)
68+
case value
69+
when Hash
70+
encode_hash(value, prefix)
71+
when Array
72+
value.map { |v| encode(v, "#{prefix}[]") }.join("&")
73+
when nil then prefix.to_s
74+
else
75+
raise ArgumentError, "value must be a Hash" if prefix.nil?
76+
"#{prefix}=#{escape(value)}"
77+
end
78+
end
79+
80+
private
81+
82+
def encode_hash(hash, prefix)
83+
hash.map do |k, v|
84+
encode(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
85+
end.reject(&:empty?).join("&")
86+
end
87+
88+
def escape(value)
89+
URI.encode_www_form_component(value)
90+
end
91+
end
92+
end
93+
94+
private_constant :DefaultEncoder
6395
end
6496

6597
# @param [#to_h, Hash] data form data key-value Hash

spec/lib/http/form_data/urlencoded_spec.rb

+13-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
it { is_expected.to eq "foo%5Bbar%5D=%D1%82%D0%B5%D1%81%D1%82" }
3030
end
3131

32+
context "with nested hashes" do
33+
let(:data) { { "foo" => { "bar" => "test" } } }
34+
it { is_expected.to eq "foo[bar]=test" }
35+
end
36+
3237
it "rewinds content" do
3338
content = form_data.read
3439
expect(form_data.to_s).to eq content
@@ -57,8 +62,14 @@
5762
end
5863

5964
describe ".encoder=" do
60-
before { described_class.encoder = ::JSON.method(:dump) }
61-
after { described_class.encoder = ::URI.method(:encode_www_form) }
65+
before do
66+
@original_encoder = described_class.encoder
67+
described_class.encoder = ::JSON.method(:dump)
68+
end
69+
70+
after do
71+
described_class.encoder = @original_encoder
72+
end
6273

6374
it "switches form encoder implementation" do
6475
expect(form_data.to_s).to eq('{"foo[bar]":"test"}')

0 commit comments

Comments
 (0)