faraday 0.9.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/LICENSE.md +1 -1
  2. data/README.md +52 -23
  3. data/lib/faraday.rb +1 -1
  4. data/lib/faraday/adapter/em_synchrony.rb +8 -0
  5. data/lib/faraday/adapter/excon.rb +1 -0
  6. data/lib/faraday/adapter/httpclient.rb +14 -3
  7. data/lib/faraday/adapter/net_http.rb +26 -20
  8. data/lib/faraday/adapter/net_http_persistent.rb +5 -3
  9. data/lib/faraday/adapter/patron.rb +13 -3
  10. data/lib/faraday/autoload.rb +0 -1
  11. data/lib/faraday/connection.rb +8 -3
  12. data/lib/faraday/options.rb +9 -0
  13. data/lib/faraday/parameters.rb +62 -58
  14. data/lib/faraday/rack_builder.rb +3 -2
  15. data/lib/faraday/request/retry.rb +40 -4
  16. data/lib/faraday/response.rb +2 -2
  17. data/lib/faraday/response/logger.rb +26 -1
  18. data/lib/faraday/utils.rb +13 -1
  19. metadata +61 -120
  20. checksums.yaml +0 -7
  21. data/.document +0 -6
  22. data/CHANGELOG.md +0 -15
  23. data/CONTRIBUTING.md +0 -36
  24. data/Gemfile +0 -29
  25. data/Rakefile +0 -71
  26. data/faraday.gemspec +0 -34
  27. data/script/console +0 -7
  28. data/script/generate_certs +0 -42
  29. data/script/package +0 -7
  30. data/script/proxy-server +0 -42
  31. data/script/release +0 -17
  32. data/script/server +0 -36
  33. data/script/test +0 -172
  34. data/test/adapters/default_test.rb +0 -14
  35. data/test/adapters/em_http_test.rb +0 -20
  36. data/test/adapters/em_synchrony_test.rb +0 -20
  37. data/test/adapters/excon_test.rb +0 -20
  38. data/test/adapters/httpclient_test.rb +0 -21
  39. data/test/adapters/integration.rb +0 -254
  40. data/test/adapters/logger_test.rb +0 -37
  41. data/test/adapters/net_http_persistent_test.rb +0 -20
  42. data/test/adapters/net_http_test.rb +0 -14
  43. data/test/adapters/patron_test.rb +0 -20
  44. data/test/adapters/rack_test.rb +0 -31
  45. data/test/adapters/test_middleware_test.rb +0 -114
  46. data/test/adapters/typhoeus_test.rb +0 -28
  47. data/test/authentication_middleware_test.rb +0 -65
  48. data/test/composite_read_io_test.rb +0 -111
  49. data/test/connection_test.rb +0 -522
  50. data/test/env_test.rb +0 -210
  51. data/test/helper.rb +0 -81
  52. data/test/live_server.rb +0 -67
  53. data/test/middleware/instrumentation_test.rb +0 -88
  54. data/test/middleware/retry_test.rb +0 -109
  55. data/test/middleware_stack_test.rb +0 -173
  56. data/test/multibyte.txt +0 -1
  57. data/test/options_test.rb +0 -252
  58. data/test/request_middleware_test.rb +0 -142
  59. data/test/response_middleware_test.rb +0 -72
  60. data/test/strawberry.rb +0 -2
  61. data/test/utils_test.rb +0 -58
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2013 Rick Olson, Zack Hobson
1
+ Copyright (c) 2009-2015 Rick Olson, Zack Hobson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -6,11 +6,13 @@ processing the request/response cycle.
6
6
 
7
7
  Faraday supports these adapters:
8
8
 
9
- * Net::HTTP
9
+ * [Net::HTTP][net_http] _(default)_
10
+ * [Net::HTTP::Persistent][persistent]
10
11
  * [Excon][]
11
12
  * [Typhoeus][]
12
13
  * [Patron][]
13
14
  * [EventMachine][]
15
+ * [HTTPClient][]
14
16
 
15
17
  It also includes a Rack adapter for hitting loaded Rack applications through
16
18
  Rack::Test, and a Test adapter for stubbing requests by hand.
@@ -29,7 +31,7 @@ end
29
31
  response = conn.get '/nigiri/sake.json' # GET http://sushi.com/nigiri/sake.json
30
32
  response.body
31
33
 
32
- conn.get '/nigiri', { :name => 'Maguro' } # GET /nigiri?name=Maguro
34
+ conn.get '/nigiri', { :name => 'Maguro' } # GET http://sushi.com/nigiri?name=Maguro
33
35
 
34
36
  conn.get do |req| # GET http://sushi.com/search?page=2&limit=100
35
37
  req.url '/search', :page => 2
@@ -56,13 +58,39 @@ conn.get do |req|
56
58
  end
57
59
  ```
58
60
 
59
- If you don't need to set up anything, you can roll with just the bare minimum:
61
+ If you don't need to set up anything, you can roll with just the default middleware
62
+ stack and default adapter (see [Faraday::RackBuilder#initialize](https://github.com/lostisland/faraday/blob/master/lib/faraday/rack_builder.rb)):
60
63
 
61
64
  ```ruby
62
- # using the default stack:
63
65
  response = Faraday.get 'http://sushi.com/nigiri/sake.json'
64
66
  ```
65
67
 
68
+ ### Changing how parameters are serialized
69
+
70
+ Sometimes you need to send the same URL parameter multiple times with different
71
+ values. This requires manually setting the parameter encoder and can be done on
72
+ either per-connection or per-request basis.
73
+
74
+ ```ruby
75
+ # per-connection setting
76
+ conn = Faraday.new :params_encoder => Faraday::FlatParamsEncoder
77
+
78
+ conn.get do |req|
79
+ # per-request setting:
80
+ # req.options.params_encoder = my_encoder
81
+ req.params['roll'] = ['california', 'philadelphia']
82
+ end
83
+ # GET 'http://sushi.com?roll=california&roll=philadelphia'
84
+ ```
85
+
86
+ The value of Faraday `params_encoder` can be any object that responds to:
87
+
88
+ * `encode(hash) #=> String`
89
+ * `decode(string) #=> Hash`
90
+
91
+ The encoder will affect both how query strings are processed and how POST bodies
92
+ get serialized. The default encoder is Faraday::NestedParamsEncoder.
93
+
66
94
  ## Advanced middleware usage
67
95
 
68
96
  The order in which middleware is stacked is important. Like with Rack, the
@@ -106,11 +134,13 @@ Middleware are classes that implement a `call` instance method. They hook into
106
134
  the request/response cycle.
107
135
 
108
136
  ```ruby
109
- def call(env)
137
+ def call(request_env)
110
138
  # do something with the request
139
+ # request_env[:request_headers].merge!(...)
111
140
 
112
- @app.call(env).on_complete do
141
+ @app.call(request_env).on_complete do |response_env|
113
142
  # do something with the response
143
+ # response_env[:response_headers].merge!(...)
114
144
  end
115
145
  end
116
146
  ```
@@ -140,20 +170,20 @@ later, response. Some keys are:
140
170
  ```ruby
141
171
  # It's possible to define stubbed request outside a test adapter block.
142
172
  stubs = Faraday::Adapter::Test::Stubs.new do |stub|
143
- stub.get('/tamago') { [200, {}, 'egg'] }
173
+ stub.get('/tamago') { |env| [200, {}, 'egg'] }
144
174
  end
145
175
 
146
176
  # You can pass stubbed request to the test adapter or define them in a block
147
177
  # or a combination of the two.
148
178
  test = Faraday.new do |builder|
149
179
  builder.adapter :test, stubs do |stub|
150
- stub.get('/ebi') {[ 200, {}, 'shrimp' ]}
180
+ stub.get('/ebi') { |env| [ 200, {}, 'shrimp' ]}
151
181
  end
152
182
  end
153
183
 
154
184
  # It's also possible to stub additional requests after the connection has
155
185
  # been initialized. This is useful for testing.
156
- stubs.get('/uni') {[ 200, {}, 'urchin' ]}
186
+ stubs.get('/uni') { |env| [ 200, {}, 'urchin' ]}
157
187
 
158
188
  resp = test.get '/tamago'
159
189
  resp.body # => 'egg'
@@ -180,13 +210,9 @@ stubs.verify_stubbed_calls
180
210
  This library aims to support and is [tested against][travis] the following Ruby
181
211
  implementations:
182
212
 
183
- * MRI 1.8.7
184
- * MRI 1.9.2
185
- * MRI 1.9.3
186
- * MRI 2.0.0
187
- * MRI 2.1.0
188
- * [JRuby][]
189
- * [Rubinius][]
213
+ * Ruby 1.8.7+
214
+ * [JRuby][] 1.7+
215
+ * [Rubinius][] 2+
190
216
 
191
217
  If something doesn't work on one of these Ruby versions, it's a bug.
192
218
 
@@ -206,11 +232,14 @@ of a major release, support for that Ruby version may be dropped.
206
232
  Copyright (c) 2009-2013 [Rick Olson](mailto:technoweenie@gmail.com), Zack Hobson.
207
233
  See [LICENSE][] for details.
208
234
 
209
- [travis]: http://travis-ci.org/lostisland/faraday
210
- [excon]: https://github.com/geemus/excon#readme
211
- [typhoeus]: https://github.com/typhoeus/typhoeus#readme
212
- [patron]: http://toland.github.com/patron/
235
+ [net_http]: http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html
236
+ [persistent]: https://github.com/drbrain/net-http-persistent
237
+ [travis]: http://travis-ci.org/lostisland/faraday
238
+ [excon]: https://github.com/geemus/excon#readme
239
+ [typhoeus]: https://github.com/typhoeus/typhoeus#readme
240
+ [patron]: http://toland.github.com/patron/
213
241
  [eventmachine]: https://github.com/igrigorik/em-http-request#readme
214
- [jruby]: http://jruby.org/
215
- [rubinius]: http://rubini.us/
216
- [license]: LICENSE.md
242
+ [httpclient]: https://github.com/nahi/httpclient
243
+ [jruby]: http://jruby.org/
244
+ [rubinius]: http://rubini.us/
245
+ [license]: LICENSE.md
data/lib/faraday.rb CHANGED
@@ -14,7 +14,7 @@ require 'forwardable'
14
14
  # conn.get '/'
15
15
  #
16
16
  module Faraday
17
- VERSION = "0.9.0"
17
+ VERSION = "0.9.2"
18
18
 
19
19
  class << self
20
20
  # Public: Gets or sets the root path that Faraday is being loaded from.
@@ -70,6 +70,14 @@ module Faraday
70
70
  else
71
71
  raise Error::ConnectionFailed, err
72
72
  end
73
+ rescue Errno::ETIMEDOUT => err
74
+ raise Error::TimeoutError, err
75
+ rescue RuntimeError => err
76
+ if err.message == "connection closed by server"
77
+ raise Error::ConnectionFailed, err
78
+ else
79
+ raise
80
+ end
73
81
  rescue => err
74
82
  if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
75
83
  raise Faraday::SSLError, err
@@ -41,6 +41,7 @@ module Faraday
41
41
  if req[:proxy]
42
42
  opts[:proxy] = {
43
43
  :host => req[:proxy][:uri].host,
44
+ :hostname => req[:proxy][:uri].hostname,
44
45
  :port => req[:proxy][:uri].port,
45
46
  :scheme => req[:proxy][:uri].scheme,
46
47
  :user => req[:proxy][:user],
@@ -10,6 +10,9 @@ module Faraday
10
10
  def call(env)
11
11
  super
12
12
 
13
+ # enable compression
14
+ client.transparent_gzip_decompression = true
15
+
13
16
  if req = env[:request]
14
17
  if proxy = req[:proxy]
15
18
  configure_proxy proxy
@@ -37,7 +40,7 @@ module Faraday
37
40
  save_response env, resp.status, resp.body, resp.headers
38
41
 
39
42
  @app.call env
40
- rescue ::HTTPClient::TimeoutError
43
+ rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
41
44
  raise Faraday::Error::TimeoutError, $!
42
45
  rescue ::HTTPClient::BadResponseError => err
43
46
  if err.message.include?('status 407')
@@ -69,14 +72,14 @@ module Faraday
69
72
 
70
73
  def configure_ssl(ssl)
71
74
  ssl_config = client.ssl_config
75
+ ssl_config.verify_mode = ssl_verify_mode(ssl)
76
+ ssl_config.cert_store = ssl_cert_store(ssl)
72
77
 
73
78
  ssl_config.add_trust_ca ssl[:ca_file] if ssl[:ca_file]
74
79
  ssl_config.add_trust_ca ssl[:ca_path] if ssl[:ca_path]
75
- ssl_config.cert_store = ssl[:cert_store] if ssl[:cert_store]
76
80
  ssl_config.client_cert = ssl[:client_cert] if ssl[:client_cert]
77
81
  ssl_config.client_key = ssl[:client_key] if ssl[:client_key]
78
82
  ssl_config.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
79
- ssl_config.verify_mode = ssl_verify_mode(ssl)
80
83
  end
81
84
 
82
85
  def configure_timeouts(req)
@@ -92,6 +95,14 @@ module Faraday
92
95
  end
93
96
  end
94
97
 
98
+ def ssl_cert_store(ssl)
99
+ return ssl[:cert_store] if ssl[:cert_store]
100
+ # Use the default cert store by default, i.e. system ca certs
101
+ cert_store = OpenSSL::X509::Store.new
102
+ cert_store.set_default_paths
103
+ cert_store
104
+ end
105
+
95
106
  def ssl_verify_mode(ssl)
96
107
  ssl[:verify_mode] || begin
97
108
  if ssl.fetch(:verify, true)
@@ -25,34 +25,36 @@ module Faraday
25
25
  ]
26
26
 
27
27
  NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
28
+ NET_HTTP_EXCEPTIONS << Net::OpenTimeout if defined?(Net::OpenTimeout)
28
29
 
29
30
  def call(env)
30
31
  super
31
- http = net_http_connection(env)
32
- configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
33
-
34
- req = env[:request]
35
- http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
36
- http.open_timeout = req[:open_timeout] if req[:open_timeout]
37
-
38
- begin
39
- http_response = perform_request(http, env)
40
- rescue *NET_HTTP_EXCEPTIONS => err
41
- if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
42
- raise Faraday::SSLError, err
43
- else
44
- raise Error::ConnectionFailed, err
32
+ with_net_http_connection(env) do |http|
33
+ configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
34
+
35
+ req = env[:request]
36
+ http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
37
+ http.open_timeout = req[:open_timeout] if req[:open_timeout]
38
+
39
+ begin
40
+ http_response = perform_request(http, env)
41
+ rescue *NET_HTTP_EXCEPTIONS => err
42
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
43
+ raise Faraday::SSLError, err
44
+ else
45
+ raise Error::ConnectionFailed, err
46
+ end
45
47
  end
46
- end
47
48
 
48
- save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers|
49
- http_response.each_header do |key, value|
50
- response_headers[key] = value
49
+ save_response(env, http_response.code.to_i, http_response.body || '') do |response_headers|
50
+ http_response.each_header do |key, value|
51
+ response_headers[key] = value
52
+ end
51
53
  end
52
54
  end
53
55
 
54
56
  @app.call env
55
- rescue Timeout::Error => err
57
+ rescue Timeout::Error, Errno::ETIMEDOUT => err
56
58
  raise Faraday::Error::TimeoutError, err
57
59
  end
58
60
 
@@ -81,12 +83,16 @@ module Faraday
81
83
  end
82
84
  end
83
85
 
86
+ def with_net_http_connection(env)
87
+ yield net_http_connection(env)
88
+ end
89
+
84
90
  def net_http_connection(env)
85
91
  if proxy = env[:request][:proxy]
86
92
  Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:user], proxy[:password])
87
93
  else
88
94
  Net::HTTP
89
- end.new(env[:url].host, env[:url].port)
95
+ end.new(env[:url].host, env[:url].port || (env[:url].scheme == 'https' ? 443 : 80))
90
96
  end
91
97
 
92
98
  def configure_ssl(http, ssl)
@@ -4,11 +4,10 @@
4
4
 
5
5
  module Faraday
6
6
  class Adapter
7
- # Experimental adapter for net-http-persistent
8
7
  class NetHttpPersistent < NetHttp
9
8
  dependency 'net/http/persistent'
10
9
 
11
- def net_http_connection(env)
10
+ def with_net_http_connection(env)
12
11
  if proxy = env[:request][:proxy]
13
12
  proxy_uri = ::URI::HTTP === proxy[:uri] ? proxy[:uri].dup : ::URI.parse(proxy[:uri].to_s)
14
13
  proxy_uri.user = proxy_uri.password = nil
@@ -18,11 +17,14 @@ module Faraday
18
17
  define_method(:password) { proxy[:password] }
19
18
  end if proxy[:user]
20
19
  end
21
- Net::HTTP::Persistent.new 'Faraday', proxy_uri
20
+
21
+ yield Net::HTTP::Persistent.new 'Faraday', proxy_uri
22
22
  end
23
23
 
24
24
  def perform_request(http, env)
25
25
  http.request env[:url], create_request(env)
26
+ rescue Errno::ETIMEDOUT => error
27
+ raise Faraday::Error::TimeoutError, error
26
28
  rescue Net::HTTP::Persistent::Error => error
27
29
  if error.message.include? 'Timeout'
28
30
  raise Faraday::Error::TimeoutError, error
@@ -39,7 +39,11 @@ module Faraday
39
39
 
40
40
  @app.call env
41
41
  rescue ::Patron::TimeoutError => err
42
- raise Faraday::Error::TimeoutError, err
42
+ if err.message == "Connection time-out"
43
+ raise Faraday::Error::ConnectionFailed, err
44
+ else
45
+ raise Faraday::Error::TimeoutError, err
46
+ end
43
47
  rescue ::Patron::Error => err
44
48
  if err.message.include?("code 407")
45
49
  raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
@@ -52,8 +56,14 @@ module Faraday
52
56
  # HAX: helps but doesn't work completely
53
57
  # https://github.com/toland/patron/issues/34
54
58
  ::Patron::Request::VALID_ACTIONS.tap do |actions|
55
- actions << :patch unless actions.include? :patch
56
- actions << :options unless actions.include? :options
59
+ if actions[0].is_a?(Symbol)
60
+ actions << :patch unless actions.include? :patch
61
+ actions << :options unless actions.include? :options
62
+ else
63
+ # Patron 0.4.20 and up
64
+ actions << "PATCH" unless actions.include? "PATCH"
65
+ actions << "OPTIONS" unless actions.include? "OPTIONS"
66
+ end
57
67
  end
58
68
  end
59
69
 
@@ -69,7 +69,6 @@ module Faraday
69
69
  :UrlEncoded => 'url_encoded',
70
70
  :Multipart => 'multipart',
71
71
  :Retry => 'retry',
72
- :Timeout => 'timeout',
73
72
  :Authorization => 'authorization',
74
73
  :BasicAuthentication => 'basic_authentication',
75
74
  :TokenAuthentication => 'token_authentication',
@@ -396,7 +396,7 @@ module Faraday
396
396
  # of the resulting url (default: nil).
397
397
  #
398
398
  # Returns the resulting URI instance.
399
- def build_exclusive_url(url = nil, params = nil)
399
+ def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
400
400
  url = nil if url.respond_to?(:empty?) and url.empty?
401
401
  base = url_prefix
402
402
  if url and base.path and base.path !~ /\/$/
@@ -404,7 +404,7 @@ module Faraday
404
404
  base.path = base.path + '/' # ensure trailing slash
405
405
  end
406
406
  uri = url ? base + url : base
407
- uri.query = params.to_query(options.params_encoder) if params
407
+ uri.query = params.to_query(params_encoder || options.params_encoder) if params
408
408
  uri.query = nil if uri.query and uri.query.empty?
409
409
  uri
410
410
  end
@@ -413,7 +413,12 @@ module Faraday
413
413
  #
414
414
  # Returns a Faraday::Connection.
415
415
  def dup
416
- self.class.new(build_exclusive_url, :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
416
+ self.class.new(build_exclusive_url,
417
+ :headers => headers.dup,
418
+ :params => params.dup,
419
+ :builder => builder.dup,
420
+ :ssl => ssl.dup,
421
+ :request => options.dup)
417
422
  end
418
423
 
419
424
  # Internal: Yields username and password extracted from a URI if they both exist.
@@ -269,6 +269,15 @@ module Faraday
269
269
 
270
270
  def_delegators :request, :params_encoder
271
271
 
272
+ # Public
273
+ def self.from(value)
274
+ env = super(value)
275
+ if value.respond_to?(:custom_members)
276
+ env.custom_members.update(value.custom_members)
277
+ end
278
+ env
279
+ end
280
+
272
281
  # Public
273
282
  def [](key)
274
283
  if in_member_set?(key)
@@ -1,15 +1,10 @@
1
+ require "forwardable"
2
+
1
3
  module Faraday
2
4
  module NestedParamsEncoder
3
- ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
4
-
5
- def self.escape(s)
6
- return s.to_s.gsub(ESCAPE_RE) {
7
- '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
8
- }.tr(' ', '+')
9
- end
10
-
11
- def self.unescape(s)
12
- CGI.unescape(s.to_s)
5
+ class << self
6
+ extend Forwardable
7
+ def_delegators :'Faraday::Utils', :escape, :unescape
13
8
  end
14
9
 
15
10
  def self.encode(params)
@@ -51,6 +46,8 @@ module Faraday
51
46
  buffer << "#{to_query.call(new_parent, val)}&"
52
47
  end
53
48
  return buffer.chop
49
+ elsif value.nil?
50
+ return parent
54
51
  else
55
52
  encoded_value = escape(value)
56
53
  return "#{parent}=#{encoded_value}"
@@ -68,65 +65,72 @@ module Faraday
68
65
 
69
66
  def self.decode(query)
70
67
  return nil if query == nil
71
- # Recursive helper lambda
72
- dehash = lambda do |hash|
73
- hash.each do |(key, value)|
74
- if value.kind_of?(Hash)
75
- hash[key] = dehash.call(value)
68
+
69
+ params = {}
70
+ query.split("&").each do |pair|
71
+ next if pair.empty?
72
+ key, value = pair.split("=", 2)
73
+ key = unescape(key)
74
+ value = unescape(value.gsub(/\+/, ' ')) if value
75
+
76
+ subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
77
+ context = params
78
+ subkeys.each_with_index do |subkey, i|
79
+ is_array = subkey =~ /[\[\]]+\Z/
80
+ subkey = $` if is_array
81
+ last_subkey = i == subkeys.length - 1
82
+
83
+ if !last_subkey || is_array
84
+ value_type = is_array ? Array : Hash
85
+ if context[subkey] && !context[subkey].is_a?(value_type)
86
+ raise TypeError, "expected %s (got %s) for param `%s'" % [
87
+ value_type.name,
88
+ context[subkey].class.name,
89
+ subkey
90
+ ]
91
+ end
92
+ context = (context[subkey] ||= value_type.new)
76
93
  end
77
- end
78
- # Numeric keys implies an array
79
- if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
80
- hash.sort.inject([]) do |accu, (_, value)|
81
- accu << value; accu
94
+
95
+ if context.is_a?(Array) && !is_array
96
+ if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
97
+ context << {}
98
+ end
99
+ context = context.last
100
+ end
101
+
102
+ if last_subkey
103
+ if is_array
104
+ context << value
105
+ else
106
+ context[subkey] = value
107
+ end
82
108
  end
83
- else
84
- hash
85
109
  end
86
110
  end
87
111
 
88
- empty_accumulator = {}
89
- return ((query.split('&').map do |pair|
90
- pair.split('=', 2) if pair && !pair.empty?
91
- end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
92
- key = unescape(key)
93
- if value.kind_of?(String)
94
- value = unescape(value.gsub(/\+/, ' '))
95
- end
112
+ dehash(params, 0)
113
+ end
96
114
 
97
- array_notation = !!(key =~ /\[\]$/)
98
- subkeys = key.split(/[\[\]]+/)
99
- current_hash = accu
100
- for i in 0...(subkeys.size - 1)
101
- subkey = subkeys[i]
102
- current_hash[subkey] = {} unless current_hash[subkey]
103
- current_hash = current_hash[subkey]
104
- end
105
- if array_notation
106
- current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
107
- current_hash[subkeys.last] << value
108
- else
109
- current_hash[subkeys.last] = value
110
- end
111
- accu
112
- end).inject(empty_accumulator.dup) do |accu, (key, value)|
113
- accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
114
- accu
115
+ # Internal: convert a nested hash with purely numeric keys into an array.
116
+ # FIXME: this is not compatible with Rack::Utils.parse_nested_query
117
+ def self.dehash(hash, depth)
118
+ hash.each do |key, value|
119
+ hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
120
+ end
121
+
122
+ if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
123
+ hash.keys.sort.inject([]) { |all, key| all << hash[key] }
124
+ else
125
+ hash
115
126
  end
116
127
  end
117
128
  end
118
129
 
119
130
  module FlatParamsEncoder
120
- ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
121
-
122
- def self.escape(s)
123
- return s.to_s.gsub(ESCAPE_RE) {
124
- '%' + $&.unpack('H2' * $&.bytesize).join('%').upcase
125
- }.tr(' ', '+')
126
- end
127
-
128
- def self.unescape(s)
129
- CGI.unescape(s.to_s)
131
+ class << self
132
+ extend Forwardable
133
+ def_delegators :'Faraday::Utils', :escape, :unescape
130
134
  end
131
135
 
132
136
  def self.encode(params)