faraday 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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