Skip to content

Commit

Permalink
Merge pull request #104 from ifad/feature/rack-array-encoding
Browse files Browse the repository at this point in the history
Add option to enable Rack-like encoding of array parameters
  • Loading branch information
hanshasselberg committed Jun 11, 2015
2 parents db95d73 + b4fce78 commit e0fc816
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 16 deletions.
46 changes: 43 additions & 3 deletions lib/ethon/easy/http/actionable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module Http
# for more real actions like GET, HEAD, POST and PUT.
module Actionable

QUERY_OPTIONS = [ :params, :body, :params_encoding ]

# Create a new action.
#
# @example Create a new action.
Expand All @@ -19,7 +21,7 @@ module Actionable
# @return [ Action ] A new action.
def initialize(url, options)
@url = url
@options = options.dup
@options, @query_options = parse_options(options)
end

# Return the url.
Expand All @@ -42,14 +44,24 @@ def options
@options
end

# Returns the query options hash.
#
# @example Return query options.
# action.query_options
#
# @return [ Hash ] The query options.
def query_options
@query_options
end

# Return the params.
#
# @example Return params.
# action.params
#
# @return [ Params ] The params.
def params
@params ||= Params.new(@easy, options.delete(:params))
@params ||= Params.new(@easy, query_options.fetch(:params, nil))
end

# Return the form.
Expand All @@ -59,7 +71,17 @@ def params
#
# @return [ Form ] The form.
def form
@form ||= Form.new(@easy, options.delete(:body))
@form ||= Form.new(@easy, query_options.fetch(:body, nil))
end

# Get the requested array encoding. By default it's
# :typhoeus, but it can also be set to :rack.
#
# @example Get encoding from options
# action.params_encoding
#
def params_encoding
@params_encoding ||= query_options.fetch(:params_encoding, :typhoeus)
end

# Setup everything necessary for a proper request.
Expand All @@ -70,6 +92,7 @@ def form
# @param [ easy ] easy the easy to setup.
def setup(easy)
@easy = easy

if params.empty?
easy.url = url
else
Expand All @@ -87,6 +110,8 @@ def setup(easy)
# @param [ Easy ] easy The easy to setup.
def set_params(easy)
params.escape = true
params.params_encoding = params_encoding

base_url, base_params = url.split("?")
base_params += "&" if base_params
easy.url = "#{base_url}?#{base_params}#{params.to_s}"
Expand All @@ -100,6 +125,21 @@ def set_params(easy)
# @param [ Easy ] easy The easy to setup.
def set_form(easy)
end

private

def parse_options(options)
query_options = {}
options = options.dup

QUERY_OPTIONS.each do |query_option|
if options.key?(query_option)
query_options[query_option] = options.delete(query_option)
end
end

return options, query_options
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/ethon/easy/http/postable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Postable
# @param [ Easy ] easy The easy to setup.
def set_form(easy)
easy.url ||= url
form.params_encoding = params_encoding
if form.multipart?
form.escape = false
form.materialize
Expand Down
1 change: 1 addition & 0 deletions lib/ethon/easy/http/putable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module Putable
def set_form(easy)
easy.upload = true
form.escape = true
form.params_encoding = params_encoding
easy.infilesize = form.to_s.bytesize
easy.set_read_callback(form.to_s)
end
Expand Down
34 changes: 27 additions & 7 deletions lib/ethon/easy/queryable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Queryable
# :nodoc:
def self.included(base)
base.send(:attr_accessor, :escape)
base.send(:attr_accessor, :params_encoding)
end

# Return wether there are elements in params or not.
Expand Down Expand Up @@ -95,18 +96,37 @@ def mime_type(filename)
def recursively_generate_pairs(h, prefix, pairs)
case h
when Hash
h.each_pair do |k,v|
key = prefix.nil? ? k : "#{prefix}[#{k}]"
pairs_for(v, key, pairs)
end
encode_hash_pairs(h, prefix, pairs)
when Array
h.each_with_index do |v, i|
key = "#{prefix}[#{i}]"
pairs_for(v, key, pairs)
if params_encoding == :rack
encode_rack_array_pairs(h, prefix, pairs)
else
encode_indexed_array_pairs(h, prefix, pairs)
end
end
end

def encode_hash_pairs(h, prefix, pairs)
h.each_pair do |k,v|
key = prefix.nil? ? k : "#{prefix}[#{k}]"
pairs_for(v, key, pairs)
end
end

def encode_indexed_array_pairs(h, prefix, pairs)
h.each_with_index do |v, i|
key = "#{prefix}[#{i}]"
pairs_for(v, key, pairs)
end
end

def encode_rack_array_pairs(h, prefix, pairs)
h.each do |v|
key = "#{prefix}[]"
pairs_for(v, key, pairs)
end
end

def pairs_for(v, key, pairs)
case v
when Hash, Array
Expand Down
42 changes: 41 additions & 1 deletion spec/ethon/easy/http/post_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
let(:url) { "http://localhost:3001/" }
let(:params) { nil }
let(:form) { nil }
let(:post) { described_class.new(url, {:params => params, :body => form}) }
let(:options) { Hash.new }
let(:post) { described_class.new(url, options.merge({:params => params, :body => form})) }

describe "#setup" do
context "when nothing" do
Expand Down Expand Up @@ -39,6 +40,25 @@
expect(easy.url).to eq("#{url}?a=1%26")
end

context "with arrays" do
let(:params) { {:a => %w( foo bar )} }

context "by default" do
it "encodes them with indexes" do
post.setup(easy)
expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar")
end
end

context "when params_encoding is :rack" do
let(:options) { {:params_encoding => :rack} }
it "encodes them without indexes" do
post.setup(easy)
expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar")
end
end
end

it "sets postfieldsize" do
expect(easy).to receive(:postfieldsize=).with(0)
post.setup(easy)
Expand Down Expand Up @@ -214,6 +234,26 @@
end
end
end

context "when arrays" do
let(:form) { {:a => %w( foo bar )} }

context "by default" do
it "sets copypostfields with indexed, escaped representation" do
expect(easy).to receive(:copypostfields=).with('a%5B0%5D=foo&a%5B1%5D=bar')
post.setup(easy)
end
end

context "when params_encoding is :rack" do
let(:options) { {:params_encoding => :rack} }

it "sets copypostfields with non-indexed, escaped representation" do
expect(easy).to receive(:copypostfields=).with('a%5B%5D=foo&a%5B%5D=bar')
post.setup(easy)
end
end
end
end

context "when params and body" do
Expand Down
45 changes: 44 additions & 1 deletion spec/ethon/easy/http/put_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
let(:url) { "http://localhost:3001/" }
let(:params) { nil }
let(:form) { nil }
let(:put) { described_class.new(url, {:params => params, :body => form}) }
let(:options) { Hash.new }
let(:put) { described_class.new(url, options.merge({:params => params, :body => form})) }

describe "#setup" do
context "when nothing" do
Expand Down Expand Up @@ -41,6 +42,25 @@
expect(easy.url).to eq("#{url}?a=1%26")
end

context "with arrays" do
let(:params) { {:a => %w( foo bar )} }

context "by default" do
it "encodes them with indexes" do
put.setup(easy)
expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar")
end
end

context "when params_encoding is :rack" do
let(:options) { {:params_encoding => :rack} }
it "encodes them without indexes" do
put.setup(easy)
expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar")
end
end
end

it "sets upload" do
expect(easy).to receive(:upload=).with(true)
put.setup(easy)
Expand Down Expand Up @@ -117,6 +137,29 @@
end
end
end

context "when arrays" do
let(:form) { {:a => %w( foo bar )} }

before do
put.setup(easy)
easy.perform
end

context "by default" do
it "submits an indexed, escaped representation" do
expect(easy.response_body).to include('"body":"a%5B0%5D=foo&a%5B1%5D=bar"')
end
end

context "when params_encoding is :rack" do
let(:options) { {:params_encoding => :rack} }

it "submits an non-indexed, escaped representation" do
expect(easy.response_body).to include('"body":"a%5B%5D=foo&a%5B%5D=bar"')
end
end
end
end

context "when params and body"
Expand Down
19 changes: 15 additions & 4 deletions spec/ethon/easy/queryable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,21 @@
context "when params contains an array" do
let(:hash) { {:a => 1, :b => [2, 3]} }

it "transforms" do
expect(pairs).to include([:a, 1])
expect(pairs).to include(["b[0]", 2])
expect(pairs).to include(["b[1]", 3])
context "by default" do
it "transforms" do
expect(pairs).to include([:a, 1])
expect(pairs).to include(["b[0]", 2])
expect(pairs).to include(["b[1]", 3])
end
end

context "when params_encoding is :rack" do
before { params.params_encoding = :rack }
it "transforms without indexes" do
expect(pairs).to include([:a, 1])
expect(pairs).to include(["b[]", 2])
expect(pairs).to include(["b[]", 3])
end
end
end

Expand Down

0 comments on commit e0fc816

Please sign in to comment.