From f5386d831a86683151da47ee25b747864ff42920 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Fri, 6 Jul 2018 19:16:54 +0300 Subject: [PATCH] Fix Urlencoded for nested Hashes (and Arrays) --- lib/http/form_data/urlencoded.rb | 36 ++++++++++++++++++++-- spec/lib/http/form_data/urlencoded_spec.rb | 15 +++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/http/form_data/urlencoded.rb b/lib/http/form_data/urlencoded.rb index dc47b94..f55e1c9 100644 --- a/lib/http/form_data/urlencoded.rb +++ b/lib/http/form_data/urlencoded.rb @@ -53,13 +53,45 @@ def encoder=(implementation) end # Returns form data encoder implementation. - # Default: `URI.encode_www_form`. + # Default: custom realization. # # @see .encoder= # @return [#call] def encoder - @encoder ||= ::URI.method(:encode_www_form) + @encoder ||= DefaultEncoder.method(:encode) end + + # Default encoder + module DefaultEncoder + class << self + def encode(value, prefix = nil) + case value + when Hash + encode_hash(value, prefix) + when Array + value.map { |v| encode(v, "#{prefix}[]") }.join("&") + when nil then prefix.to_s + else + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{escape(value)}" + end + end + + private + + def encode_hash(hash, prefix) + hash.map do |k, v| + encode(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) + end.reject(&:empty?).join("&") + end + + def escape(value) + URI.encode_www_form_component(value) + end + end + end + + private_constant :DefaultEncoder end # @param [#to_h, Hash] data form data key-value Hash diff --git a/spec/lib/http/form_data/urlencoded_spec.rb b/spec/lib/http/form_data/urlencoded_spec.rb index a3de752..cdb62d5 100644 --- a/spec/lib/http/form_data/urlencoded_spec.rb +++ b/spec/lib/http/form_data/urlencoded_spec.rb @@ -29,6 +29,11 @@ it { is_expected.to eq "foo%5Bbar%5D=%D1%82%D0%B5%D1%81%D1%82" } end + context "with nested hashes" do + let(:data) { { "foo" => { "bar" => "test" } } } + it { is_expected.to eq "foo[bar]=test" } + end + it "rewinds content" do content = form_data.read expect(form_data.to_s).to eq content @@ -57,8 +62,14 @@ end describe ".encoder=" do - before { described_class.encoder = ::JSON.method(:dump) } - after { described_class.encoder = ::URI.method(:encode_www_form) } + before do + @original_encoder = described_class.encoder + described_class.encoder = ::JSON.method(:dump) + end + + after do + described_class.encoder = @original_encoder + end it "switches form encoder implementation" do expect(form_data.to_s).to eq('{"foo[bar]":"test"}')