faraday 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)