faraday 0.16.0 → 0.17.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +347 -18
  4. data/lib/faraday/adapter/em_http.rb +99 -142
  5. data/lib/faraday/adapter/em_http_ssl_patch.rb +17 -23
  6. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +15 -18
  7. data/lib/faraday/adapter/em_synchrony.rb +60 -104
  8. data/lib/faraday/adapter/excon.rb +55 -100
  9. data/lib/faraday/adapter/httpclient.rb +39 -61
  10. data/lib/faraday/adapter/net_http.rb +51 -104
  11. data/lib/faraday/adapter/net_http_persistent.rb +27 -48
  12. data/lib/faraday/adapter/patron.rb +35 -54
  13. data/lib/faraday/adapter/rack.rb +12 -28
  14. data/lib/faraday/adapter/test.rb +53 -86
  15. data/lib/faraday/adapter/typhoeus.rb +1 -4
  16. data/lib/faraday/adapter.rb +22 -36
  17. data/lib/faraday/autoload.rb +36 -47
  18. data/lib/faraday/connection.rb +179 -321
  19. data/lib/faraday/error.rb +33 -67
  20. data/lib/faraday/middleware.rb +28 -4
  21. data/lib/faraday/options.rb +186 -35
  22. data/lib/faraday/parameters.rb +197 -4
  23. data/lib/faraday/rack_builder.rb +56 -67
  24. data/lib/faraday/request/authorization.rb +30 -42
  25. data/lib/faraday/request/basic_authentication.rb +7 -14
  26. data/lib/faraday/request/instrumentation.rb +27 -45
  27. data/lib/faraday/request/multipart.rb +48 -79
  28. data/lib/faraday/request/retry.rb +170 -197
  29. data/lib/faraday/request/token_authentication.rb +10 -15
  30. data/lib/faraday/request/url_encoded.rb +23 -41
  31. data/lib/faraday/request.rb +36 -68
  32. data/lib/faraday/response/logger.rb +69 -22
  33. data/lib/faraday/response/raise_error.rb +14 -36
  34. data/lib/faraday/response.rb +16 -23
  35. data/lib/faraday/upload_io.rb +67 -0
  36. data/lib/faraday/utils.rb +245 -28
  37. data/lib/faraday.rb +175 -93
  38. metadata +5 -22
  39. data/lib/faraday/adapter_registry.rb +0 -28
  40. data/lib/faraday/dependency_loader.rb +0 -37
  41. data/lib/faraday/encoders/flat_params_encoder.rb +0 -94
  42. data/lib/faraday/encoders/nested_params_encoder.rb +0 -171
  43. data/lib/faraday/file_part.rb +0 -128
  44. data/lib/faraday/logging/formatter.rb +0 -92
  45. data/lib/faraday/middleware_registry.rb +0 -129
  46. data/lib/faraday/options/connection_options.rb +0 -22
  47. data/lib/faraday/options/env.rb +0 -181
  48. data/lib/faraday/options/proxy_options.rb +0 -28
  49. data/lib/faraday/options/request_options.rb +0 -21
  50. data/lib/faraday/options/ssl_options.rb +0 -59
  51. data/lib/faraday/param_part.rb +0 -53
  52. data/lib/faraday/utils/headers.rb +0 -139
  53. data/lib/faraday/utils/params_hash.rb +0 -61
  54. data/spec/external_adapters/faraday_specs_setup.rb +0 -14
data/lib/faraday/error.rb CHANGED
@@ -1,22 +1,21 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
- # Faraday error base class.
5
- class Error < StandardError
2
+ class Error < StandardError; end
3
+
4
+ class ClientError < Error
6
5
  attr_reader :response, :wrapped_exception
7
6
 
8
- def initialize(exc, response = nil)
7
+ def initialize(ex, response = nil)
9
8
  @wrapped_exception = nil
10
9
  @response = response
11
10
 
12
- if exc.respond_to?(:backtrace)
13
- super(exc.message)
14
- @wrapped_exception = exc
15
- elsif exc.respond_to?(:each_key)
16
- super("the server responded with status #{exc[:status]}")
17
- @response = exc
11
+ if ex.respond_to?(:backtrace)
12
+ super(ex.message)
13
+ @wrapped_exception = ex
14
+ elsif ex.respond_to?(:each_key)
15
+ super("the server responded with status #{ex[:status]}")
16
+ @response = ex
18
17
  else
19
- super(exc.to_s)
18
+ super(ex.to_s)
20
19
  end
21
20
  end
22
21
 
@@ -29,72 +28,39 @@ module Faraday
29
28
  end
30
29
 
31
30
  def inspect
32
- inner = +''
33
- inner << " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
34
- inner << " response=#{@response.inspect}" if @response
35
- inner << " #{super}" if inner.empty?
31
+ inner = ''
32
+ if @wrapped_exception
33
+ inner << " wrapped=#{@wrapped_exception.inspect}"
34
+ end
35
+ if @response
36
+ inner << " response=#{@response.inspect}"
37
+ end
38
+ if inner.empty?
39
+ inner << " #{super}"
40
+ end
36
41
  %(#<#{self.class}#{inner}>)
37
42
  end
38
43
  end
39
44
 
40
- # Faraday client error class. Represents 4xx status responses.
41
- class ClientError < Error
42
- end
43
-
44
- # Raised by Faraday::Response::RaiseError in case of a 400 response.
45
- class BadRequestError < ClientError
46
- end
47
-
48
- # Raised by Faraday::Response::RaiseError in case of a 401 response.
49
- class UnauthorizedError < ClientError
50
- end
51
-
52
- # Raised by Faraday::Response::RaiseError in case of a 403 response.
53
- class ForbiddenError < ClientError
54
- end
55
-
56
- # Raised by Faraday::Response::RaiseError in case of a 404 response.
57
- class ResourceNotFound < ClientError
58
- end
59
-
60
- # Raised by Faraday::Response::RaiseError in case of a 407 response.
61
- class ProxyAuthError < ClientError
62
- end
63
-
64
- # Raised by Faraday::Response::RaiseError in case of a 409 response.
65
- class ConflictError < ClientError
66
- end
45
+ class ConnectionFailed < ClientError; end
46
+ class ResourceNotFound < ClientError; end
47
+ class ParsingError < ClientError; end
67
48
 
68
- # Raised by Faraday::Response::RaiseError in case of a 422 response.
69
- class UnprocessableEntityError < ClientError
70
- end
71
-
72
- # Faraday server error class. Represents 5xx status responses.
73
- class ServerError < Error
74
- end
75
-
76
- # A unified client error for timeouts.
77
- class TimeoutError < ServerError
78
- def initialize(exc = 'timeout', response = nil)
79
- super(exc, response)
49
+ class TimeoutError < ClientError
50
+ def initialize(ex = nil)
51
+ super(ex || "timeout")
80
52
  end
81
53
  end
82
54
 
83
- # A unified error for failed connections.
84
- class ConnectionFailed < Error
55
+ class SSLError < ClientError
85
56
  end
86
57
 
87
- # A unified client error for SSL errors.
88
- class SSLError < Error
89
- end
58
+ class RetriableResponse < ClientError; end
90
59
 
91
- # Raised by FaradayMiddleware::ResponseMiddleware
92
- class ParsingError < Error
60
+ [:ClientError, :ConnectionFailed, :ResourceNotFound,
61
+ :ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
62
+ Error.const_set(const, Faraday.const_get(const))
93
63
  end
94
64
 
95
- # Exception used to control the Retry middleware.
96
- #
97
- # @see Faraday::Request::Retry
98
- class RetriableResponse < Error
99
- end
65
+
100
66
  end
@@ -1,10 +1,34 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
- # Middleware is the basic base class of any Faraday middleware.
5
2
  class Middleware
6
3
  extend MiddlewareRegistry
7
- extend DependencyLoader
4
+
5
+ class << self
6
+ attr_accessor :load_error
7
+ private :load_error=
8
+ end
9
+
10
+ self.load_error = nil
11
+
12
+ # Executes a block which should try to require and reference dependent libraries
13
+ def self.dependency(lib = nil)
14
+ lib ? require(lib) : yield
15
+ rescue LoadError, NameError => error
16
+ self.load_error = error
17
+ end
18
+
19
+ def self.new(*)
20
+ raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
21
+ super
22
+ end
23
+
24
+ def self.loaded?
25
+ load_error.nil?
26
+ end
27
+
28
+ def self.inherited(subclass)
29
+ super
30
+ subclass.send(:load_error=, self.load_error)
31
+ end
8
32
 
9
33
  def initialize(app = nil)
10
34
  @app = app
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Faraday
4
2
  # Subclasses Struct with some special helpers for converting from a Hash to
5
3
  # a Struct.
@@ -12,7 +10,6 @@ module Faraday
12
10
  # Public
13
11
  def each
14
12
  return to_enum(:each) unless block_given?
15
-
16
13
  members.each do |key|
17
14
  yield(key.to_sym, send(key))
18
15
  end
@@ -30,7 +27,7 @@ module Faraday
30
27
  new_value = value
31
28
  end
32
29
 
33
- send("#{key}=", new_value) unless new_value.nil?
30
+ self.send("#{key}=", new_value) unless new_value.nil?
34
31
  end
35
32
  self
36
33
  end
@@ -50,14 +47,10 @@ module Faraday
50
47
  # Public
51
48
  def merge!(other)
52
49
  other.each do |key, other_value|
53
- self_value = send(key)
50
+ self_value = self.send(key)
54
51
  sub_options = self.class.options_for(key)
55
- new_value = if self_value && sub_options && other_value
56
- self_value.merge(other_value)
57
- else
58
- other_value
59
- end
60
- send("#{key}=", new_value) unless new_value.nil?
52
+ new_value = (self_value && sub_options && other_value) ? self_value.merge(other_value) : other_value
53
+ self.send("#{key}=", new_value) unless new_value.nil?
61
54
  end
62
55
  self
63
56
  end
@@ -76,10 +69,10 @@ module Faraday
76
69
  def fetch(key, *args)
77
70
  unless symbolized_key_set.include?(key.to_sym)
78
71
  key_setter = "#{key}="
79
- if !args.empty?
72
+ if args.size > 0
80
73
  send(key_setter, args.first)
81
74
  elsif block_given?
82
- send(key_setter, yield(key))
75
+ send(key_setter, Proc.new.call(key))
83
76
  else
84
77
  raise self.class.fetch_error_class, "key not found: #{key.inspect}"
85
78
  end
@@ -105,7 +98,6 @@ module Faraday
105
98
  # Public
106
99
  def each_key
107
100
  return to_enum(:each_key) unless block_given?
108
-
109
101
  keys.each do |key|
110
102
  yield(key)
111
103
  end
@@ -121,7 +113,6 @@ module Faraday
121
113
  # Public
122
114
  def each_value
123
115
  return to_enum(:each_value) unless block_given?
124
-
125
116
  values.each do |value|
126
117
  yield(value)
127
118
  end
@@ -151,9 +142,9 @@ module Faraday
151
142
  value = send(member)
152
143
  values << "#{member}=#{value.inspect}" if value
153
144
  end
154
- values = values.empty? ? '(empty)' : values.join(', ')
145
+ values = values.empty? ? ' (empty)' : (' ' << values.join(", "))
155
146
 
156
- %(#<#{self.class} #{values}>)
147
+ %(#<#{self.class}#{values}>)
157
148
  end
158
149
 
159
150
  # Internal
@@ -171,12 +162,8 @@ module Faraday
171
162
  @attribute_options ||= {}
172
163
  end
173
164
 
174
- def self.memoized(key, &block)
175
- unless block_given?
176
- raise ArgumentError, '#memoized must be called with a block'
177
- end
178
-
179
- memoized_attributes[key.to_sym] = block
165
+ def self.memoized(key)
166
+ memoized_attributes[key.to_sym] = Proc.new
180
167
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
181
168
  def #{key}() self[:#{key}]; end
182
169
  RUBY
@@ -188,7 +175,7 @@ module Faraday
188
175
 
189
176
  def [](key)
190
177
  key = key.to_sym
191
- if (method = self.class.memoized_attributes[key])
178
+ if method = self.class.memoized_attributes[key]
192
179
  super(key) || (self[key] = instance_eval(&method))
193
180
  else
194
181
  super
@@ -196,7 +183,7 @@ module Faraday
196
183
  end
197
184
 
198
185
  def symbolized_key_set
199
- @symbolized_key_set ||= Set.new(keys.map(&:to_sym))
186
+ @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
200
187
  end
201
188
 
202
189
  def self.inherited(subclass)
@@ -207,16 +194,180 @@ module Faraday
207
194
 
208
195
  def self.fetch_error_class
209
196
  @fetch_error_class ||= if Object.const_defined?(:KeyError)
210
- ::KeyError
211
- else
212
- ::IndexError
213
- end
197
+ ::KeyError
198
+ else
199
+ ::IndexError
200
+ end
214
201
  end
215
202
  end
216
- end
217
203
 
218
- require 'faraday/options/request_options'
219
- require 'faraday/options/ssl_options'
220
- require 'faraday/options/proxy_options'
221
- require 'faraday/options/connection_options'
222
- require 'faraday/options/env'
204
+ class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
205
+ :timeout, :open_timeout, :write_timeout, :boundary, :oauth, :context)
206
+
207
+ def []=(key, value)
208
+ if key && key.to_sym == :proxy
209
+ super(key, value ? ProxyOptions.from(value) : nil)
210
+ else
211
+ super(key, value)
212
+ end
213
+ end
214
+ end
215
+
216
+ class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
217
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth,
218
+ :version, :min_version, :max_version)
219
+
220
+ def verify?
221
+ verify != false
222
+ end
223
+
224
+ def disable?
225
+ !verify?
226
+ end
227
+ end
228
+
229
+ class ProxyOptions < Options.new(:uri, :user, :password)
230
+ extend Forwardable
231
+ def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=, :path, :path=
232
+
233
+ def self.from(value)
234
+ case value
235
+ when String
236
+ value = {:uri => Utils.URI(value)}
237
+ when URI
238
+ value = {:uri => value}
239
+ when Hash, Options
240
+ if uri = value.delete(:uri)
241
+ value[:uri] = Utils.URI(uri)
242
+ end
243
+ end
244
+ super(value)
245
+ end
246
+
247
+ memoized(:user) { uri && uri.user && Utils.unescape(uri.user) }
248
+ memoized(:password) { uri && uri.password && Utils.unescape(uri.password) }
249
+ end
250
+
251
+ class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
252
+ :parallel_manager, :params, :headers, :builder_class)
253
+
254
+ options :request => RequestOptions, :ssl => SSLOptions
255
+
256
+ memoized(:request) { self.class.options_for(:request).new }
257
+
258
+ memoized(:ssl) { self.class.options_for(:ssl).new }
259
+
260
+ memoized(:builder_class) { RackBuilder }
261
+
262
+ def new_builder(block)
263
+ builder_class.new(&block)
264
+ end
265
+ end
266
+
267
+ class Env < Options.new(:method, :body, :url, :request, :request_headers,
268
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status,
269
+ :reason_phrase)
270
+
271
+ ContentLength = 'Content-Length'.freeze
272
+ StatusesWithoutBody = Set.new [204, 304]
273
+ SuccessfulStatuses = 200..299
274
+
275
+ # A Set of HTTP verbs that typically send a body. If no body is set for
276
+ # these requests, the Content-Length header is set to 0.
277
+ MethodsWithBodies = Set.new [:post, :put, :patch, :options]
278
+
279
+ options :request => RequestOptions,
280
+ :request_headers => Utils::Headers, :response_headers => Utils::Headers
281
+
282
+ extend Forwardable
283
+
284
+ def_delegators :request, :params_encoder
285
+
286
+ # Public
287
+ def self.from(value)
288
+ env = super(value)
289
+ if value.respond_to?(:custom_members)
290
+ env.custom_members.update(value.custom_members)
291
+ end
292
+ env
293
+ end
294
+
295
+ # Public
296
+ def [](key)
297
+ if in_member_set?(key)
298
+ super(key)
299
+ else
300
+ custom_members[key]
301
+ end
302
+ end
303
+
304
+ # Public
305
+ def []=(key, value)
306
+ if in_member_set?(key)
307
+ super(key, value)
308
+ else
309
+ custom_members[key] = value
310
+ end
311
+ end
312
+
313
+ # Public
314
+ def success?
315
+ SuccessfulStatuses.include?(status)
316
+ end
317
+
318
+ # Public
319
+ def needs_body?
320
+ !body && MethodsWithBodies.include?(method)
321
+ end
322
+
323
+ # Public
324
+ def clear_body
325
+ request_headers[ContentLength] = '0'
326
+ self.body = ''
327
+ end
328
+
329
+ # Public
330
+ def parse_body?
331
+ !StatusesWithoutBody.include?(status)
332
+ end
333
+
334
+ # Public
335
+ def parallel?
336
+ !!parallel_manager
337
+ end
338
+
339
+ def inspect
340
+ attrs = [nil]
341
+ members.each do |mem|
342
+ if value = send(mem)
343
+ attrs << "@#{mem}=#{value.inspect}"
344
+ end
345
+ end
346
+ if !custom_members.empty?
347
+ attrs << "@custom=#{custom_members.inspect}"
348
+ end
349
+ %(#<#{self.class}#{attrs.join(" ")}>)
350
+ end
351
+
352
+ # Internal
353
+ def custom_members
354
+ @custom_members ||= {}
355
+ end
356
+
357
+ # Internal
358
+ if members.first.is_a?(Symbol)
359
+ def in_member_set?(key)
360
+ self.class.member_set.include?(key.to_sym)
361
+ end
362
+ else
363
+ def in_member_set?(key)
364
+ self.class.member_set.include?(key.to_s)
365
+ end
366
+ end
367
+
368
+ # Internal
369
+ def self.member_set
370
+ @member_set ||= Set.new(members)
371
+ end
372
+ end
373
+ end
@@ -1,5 +1,198 @@
1
- # frozen_string_literal: true
1
+ require "forwardable"
2
2
 
3
- require 'forwardable'
4
- require 'faraday/encoders/nested_params_encoder'
5
- require 'faraday/encoders/flat_params_encoder'
3
+ module Faraday
4
+ module NestedParamsEncoder
5
+ class << self
6
+ extend Forwardable
7
+ def_delegators :'Faraday::Utils', :escape, :unescape
8
+ end
9
+
10
+ def self.encode(params)
11
+ return nil if params == nil
12
+
13
+ if !params.is_a?(Array)
14
+ if !params.respond_to?(:to_hash)
15
+ raise TypeError,
16
+ "Can't convert #{params.class} into Hash."
17
+ end
18
+ params = params.to_hash
19
+ params = params.map do |key, value|
20
+ key = key.to_s if key.kind_of?(Symbol)
21
+ [key, value]
22
+ end
23
+ # Useful default for OAuth and caching.
24
+ # Only to be used for non-Array inputs. Arrays should preserve order.
25
+ params.sort!
26
+ end
27
+
28
+ # Helper lambda
29
+ to_query = lambda do |parent, value|
30
+ if value.is_a?(Hash)
31
+ value = value.map do |key, val|
32
+ key = escape(key)
33
+ [key, val]
34
+ end
35
+ value.sort!
36
+ buffer = ""
37
+ value.each do |key, val|
38
+ new_parent = "#{parent}%5B#{key}%5D"
39
+ buffer << "#{to_query.call(new_parent, val)}&"
40
+ end
41
+ return buffer.chop
42
+ elsif value.is_a?(Array)
43
+ new_parent = "#{parent}%5B%5D"
44
+ return new_parent if value.empty?
45
+ buffer = ""
46
+ value.each_with_index do |val, i|
47
+ buffer << "#{to_query.call(new_parent, val)}&"
48
+ end
49
+ return buffer.chop
50
+ elsif value.nil?
51
+ return parent
52
+ else
53
+ encoded_value = escape(value)
54
+ return "#{parent}=#{encoded_value}"
55
+ end
56
+ end
57
+
58
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
59
+ buffer = ''
60
+ params.each do |parent, value|
61
+ encoded_parent = escape(parent)
62
+ buffer << "#{to_query.call(encoded_parent, value)}&"
63
+ end
64
+ return buffer.chop
65
+ end
66
+
67
+ def self.decode(query)
68
+ return nil if query == nil
69
+
70
+ params = {}
71
+ query.split("&").each do |pair|
72
+ next if pair.empty?
73
+ key, value = pair.split("=", 2)
74
+ key = unescape(key)
75
+ value = unescape(value.gsub(/\+/, ' ')) if value
76
+
77
+ subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
78
+ context = params
79
+ subkeys.each_with_index do |subkey, i|
80
+ is_array = subkey =~ /[\[\]]+\Z/
81
+ subkey = $` if is_array
82
+ last_subkey = i == subkeys.length - 1
83
+
84
+ if !last_subkey || is_array
85
+ value_type = is_array ? Array : Hash
86
+ if context[subkey] && !context[subkey].is_a?(value_type)
87
+ raise TypeError, "expected %s (got %s) for param `%s'" % [
88
+ value_type.name,
89
+ context[subkey].class.name,
90
+ subkey
91
+ ]
92
+ end
93
+ context = (context[subkey] ||= value_type.new)
94
+ end
95
+
96
+ if context.is_a?(Array) && !is_array
97
+ if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
98
+ context << {}
99
+ end
100
+ context = context.last
101
+ end
102
+
103
+ if last_subkey
104
+ if is_array
105
+ context << value
106
+ else
107
+ context[subkey] = value
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ dehash(params, 0)
114
+ end
115
+
116
+ # Internal: convert a nested hash with purely numeric keys into an array.
117
+ # FIXME: this is not compatible with Rack::Utils.parse_nested_query
118
+ def self.dehash(hash, depth)
119
+ hash.each do |key, value|
120
+ hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
121
+ end
122
+
123
+ if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
124
+ hash.keys.sort.inject([]) { |all, key| all << hash[key] }
125
+ else
126
+ hash
127
+ end
128
+ end
129
+ end
130
+
131
+ module FlatParamsEncoder
132
+ class << self
133
+ extend Forwardable
134
+ def_delegators :'Faraday::Utils', :escape, :unescape
135
+ end
136
+
137
+ def self.encode(params)
138
+ return nil if params == nil
139
+
140
+ if !params.is_a?(Array)
141
+ if !params.respond_to?(:to_hash)
142
+ raise TypeError,
143
+ "Can't convert #{params.class} into Hash."
144
+ end
145
+ params = params.to_hash
146
+ params = params.map do |key, value|
147
+ key = key.to_s if key.kind_of?(Symbol)
148
+ [key, value]
149
+ end
150
+ # Useful default for OAuth and caching.
151
+ # Only to be used for non-Array inputs. Arrays should preserve order.
152
+ params.sort!
153
+ end
154
+
155
+ # The params have form [['key1', 'value1'], ['key2', 'value2']].
156
+ buffer = ''
157
+ params.each do |key, value|
158
+ encoded_key = escape(key)
159
+ value = value.to_s if value == true || value == false
160
+ if value == nil
161
+ buffer << "#{encoded_key}&"
162
+ elsif value.kind_of?(Array)
163
+ value.each do |sub_value|
164
+ encoded_value = escape(sub_value)
165
+ buffer << "#{encoded_key}=#{encoded_value}&"
166
+ end
167
+ else
168
+ encoded_value = escape(value)
169
+ buffer << "#{encoded_key}=#{encoded_value}&"
170
+ end
171
+ end
172
+ return buffer.chop
173
+ end
174
+
175
+ def self.decode(query)
176
+ empty_accumulator = {}
177
+ return nil if query == nil
178
+ split_query = (query.split('&').map do |pair|
179
+ pair.split('=', 2) if pair && !pair.empty?
180
+ end).compact
181
+ return split_query.inject(empty_accumulator.dup) do |accu, pair|
182
+ pair[0] = unescape(pair[0])
183
+ pair[1] = true if pair[1].nil?
184
+ if pair[1].respond_to?(:to_str)
185
+ pair[1] = unescape(pair[1].to_str.gsub(/\+/, " "))
186
+ end
187
+ if accu[pair[0]].kind_of?(Array)
188
+ accu[pair[0]] << pair[1]
189
+ elsif accu[pair[0]]
190
+ accu[pair[0]] = [accu[pair[0]], pair[1]]
191
+ else
192
+ accu[pair[0]] = pair[1]
193
+ end
194
+ accu
195
+ end
196
+ end
197
+ end
198
+ end