Skip to content

Commit 5e3ab09

Browse files
committed
Accept any Enumerable input and remove Param.coerce
Both Multipart and Urlencoded encoders now consistently accept any Enumerable of key-value pairs (Hash, Array, Enumerator, etc.) instead of requiring Hash or Array specifically. This is done via a new FormData.ensure_data method that coerces input to an Enumerable. * Remove Param.coerce and inline its logic into Multipart#parts * Replace FormData.ensure_hash with FormData.ensure_data in encoders * Add tests for Enumerable input on both Multipart and ensure_data
1 parent a913904 commit 5e3ab09

6 files changed

Lines changed: 77 additions & 27 deletions

File tree

lib/http/form_data.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ class << self
3838
# FormData factory. Automatically selects best type depending on given
3939
# `data` Hash.
4040
#
41-
# @param [#to_h, Hash] data
41+
# @param [Enumerable, Hash, #to_h] data
4242
# @return [Multipart] if any of values is a {FormData::File}
4343
# @return [Urlencoded] otherwise
4444
def create(data, encoder: nil)
45-
data = ensure_hash data
45+
data = ensure_data data
4646

4747
if multipart?(data)
4848
Multipart.new(data)
@@ -64,6 +64,18 @@ def ensure_hash(obj)
6464
end
6565
end
6666

67+
# Coerce `obj` to an Enumerable of key-value pairs.
68+
#
69+
# @raise [Error] `obj` can't be coerced.
70+
# @return [Enumerable]
71+
def ensure_data(obj)
72+
if obj.nil? then []
73+
elsif obj.is_a?(Enumerable) then obj
74+
elsif obj.respond_to?(:to_h) then obj.to_h
75+
else raise Error, "#{obj.inspect} is neither Enumerable nor responds to :to_h"
76+
end
77+
end
78+
6779
private
6880

6981
# Tells whenever data contains multipart data or not.

lib/http/form_data/multipart.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Multipart
1414

1515
attr_reader :boundary
1616

17-
# @param [#to_h, Hash] data form data key-value Hash
17+
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
1818
def initialize(data, boundary: self.class.generate_boundary)
1919
@boundary = boundary.to_s.freeze
2020
@io = CompositeIO.new(parts(data).flat_map { |part| [glue, part] } << tail)
@@ -54,11 +54,15 @@ def tail
5454
end
5555

5656
def parts(data)
57-
if data.is_a?(Array)
58-
Param.coerce data
59-
else
60-
Param.coerce FormData.ensure_hash data
57+
params = []
58+
59+
FormData.ensure_data(data).each do |name, values|
60+
Array(values).each do |value|
61+
params << Param.new(name, value)
62+
end
6163
end
64+
65+
params
6266
end
6367
end
6468
end

lib/http/form_data/multipart/param.rb

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,6 @@ def initialize(name, value)
4141
@io = CompositeIO.new [header, @part, footer]
4242
end
4343

44-
# Flattens given `data` Hash or Array into an array of `Param`'s.
45-
# Nested arrays are unwinded.
46-
# Behavior is similar to `URL.encode_www_form`.
47-
#
48-
# @param [Array || Hash] data
49-
# @return [Array<FormData::MultiPart::Param>]
50-
def self.coerce(data)
51-
params = []
52-
53-
data.each do |name, values|
54-
Array(values).each do |value|
55-
params << new(name, value)
56-
end
57-
end
58-
59-
params
60-
end
61-
6244
private
6345

6446
def header

lib/http/form_data/urlencoded.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ def escape(value)
9595
private_constant :DefaultEncoder
9696
end
9797

98-
# @param [#to_h, Hash] data form data key-value Hash
98+
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
9999
def initialize(data, encoder: nil)
100100
encoder ||= self.class.encoder
101-
@io = StringIO.new(encoder.call(FormData.ensure_hash(data)))
101+
@io = StringIO.new(encoder.call(FormData.ensure_data(data)))
102102
end
103103

104104
# Returns MIME type to be used for HTTP request `Content-Type` header.

spec/lib/http/form_data/multipart_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,22 @@ def disposition(params)
8787
end
8888
end
8989

90+
it "supports any Enumerable of pairs" do
91+
enum = Enumerator.new { |y| y << [:foo, :bar] << [:foo, :baz] }
92+
form_data = described_class.new(enum)
93+
94+
boundary_value = form_data.boundary
95+
expect(form_data.to_s).to eq([
96+
"--#{boundary_value}#{crlf}",
97+
"#{disposition 'name' => 'foo'}#{crlf}",
98+
"#{crlf}bar#{crlf}",
99+
"--#{boundary_value}#{crlf}",
100+
"#{disposition 'name' => 'foo'}#{crlf}",
101+
"#{crlf}baz#{crlf}",
102+
"--#{boundary_value}--#{crlf}"
103+
].join)
104+
end
105+
90106
# https://github.com/httprb/http/issues/663
91107
context "when params is an Array of pairs" do
92108
let(:params) do

spec/lib/http/form_data_spec.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,42 @@
2222
end
2323
end
2424

25+
describe ".ensure_data" do
26+
subject(:ensure_data) { HTTP::FormData.ensure_data data }
27+
28+
context "when Hash given" do
29+
let(:data) { { :foo => :bar } }
30+
it { is_expected.to eq :foo => :bar }
31+
end
32+
33+
context "when Array given" do
34+
let(:data) { [[:foo, :bar], [:foo, :baz]] }
35+
it { is_expected.to eq [[:foo, :bar], [:foo, :baz]] }
36+
end
37+
38+
context "when Enumerator given" do
39+
let(:data) { Enumerator.new { |y| y << [:foo, :bar] } }
40+
it { is_expected.to be_a Enumerator }
41+
end
42+
43+
context "when #to_h given" do
44+
let(:data) { double(:to_h => { :foo => :bar }) }
45+
it { is_expected.to eq :foo => :bar }
46+
end
47+
48+
context "when nil given" do
49+
let(:data) { nil }
50+
it { is_expected.to eq([]) }
51+
end
52+
53+
context "when neither Enumerable nor #to_h given" do
54+
let(:data) { double }
55+
it "fails with HTTP::FormData::Error" do
56+
expect { ensure_data }.to raise_error HTTP::FormData::Error
57+
end
58+
end
59+
end
60+
2561
describe ".ensure_hash" do
2662
subject(:ensure_hash) { HTTP::FormData.ensure_hash data }
2763

0 commit comments

Comments
 (0)