faraday 0.7.4 → 1.0.1

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 (119) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +276 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +40 -153
  5. data/Rakefile +4 -139
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday/adapter/em_http.rb +286 -0
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +62 -0
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +69 -0
  11. data/lib/faraday/adapter/em_synchrony.rb +120 -36
  12. data/lib/faraday/adapter/excon.rb +108 -12
  13. data/lib/faraday/adapter/httpclient.rb +152 -0
  14. data/lib/faraday/adapter/net_http.rb +187 -43
  15. data/lib/faraday/adapter/net_http_persistent.rb +91 -0
  16. data/lib/faraday/adapter/patron.rb +106 -10
  17. data/lib/faraday/adapter/rack.rb +75 -0
  18. data/lib/faraday/adapter/test.rb +160 -61
  19. data/lib/faraday/adapter/typhoeus.rb +7 -46
  20. data/lib/faraday/adapter.rb +105 -33
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +95 -0
  23. data/lib/faraday/connection.rb +525 -157
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +98 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +171 -0
  27. data/lib/faraday/error.rb +122 -30
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +14 -22
  31. data/lib/faraday/middleware_registry.rb +129 -0
  32. data/lib/faraday/options/connection_options.rb +22 -0
  33. data/lib/faraday/options/env.rb +181 -0
  34. data/lib/faraday/options/proxy_options.rb +28 -0
  35. data/lib/faraday/options/request_options.rb +22 -0
  36. data/lib/faraday/options/ssl_options.rb +59 -0
  37. data/lib/faraday/options.rb +222 -0
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +5 -0
  40. data/lib/faraday/rack_builder.rb +248 -0
  41. data/lib/faraday/request/authorization.rb +55 -0
  42. data/lib/faraday/request/basic_authentication.rb +20 -0
  43. data/lib/faraday/request/instrumentation.rb +54 -0
  44. data/lib/faraday/request/multipart.rb +84 -48
  45. data/lib/faraday/request/retry.rb +239 -0
  46. data/lib/faraday/request/token_authentication.rb +20 -0
  47. data/lib/faraday/request/url_encoded.rb +46 -27
  48. data/lib/faraday/request.rb +112 -50
  49. data/lib/faraday/response/logger.rb +24 -25
  50. data/lib/faraday/response/raise_error.rb +40 -11
  51. data/lib/faraday/response.rb +44 -35
  52. data/lib/faraday/utils/headers.rb +139 -0
  53. data/lib/faraday/utils/params_hash.rb +61 -0
  54. data/lib/faraday/utils.rb +72 -117
  55. data/lib/faraday.rb +142 -64
  56. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  57. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  58. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  59. data/spec/faraday/adapter/excon_spec.rb +49 -0
  60. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  61. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  62. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  63. data/spec/faraday/adapter/patron_spec.rb +18 -0
  64. data/spec/faraday/adapter/rack_spec.rb +8 -0
  65. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  66. data/spec/faraday/adapter_registry_spec.rb +28 -0
  67. data/spec/faraday/adapter_spec.rb +55 -0
  68. data/spec/faraday/composite_read_io_spec.rb +80 -0
  69. data/spec/faraday/connection_spec.rb +691 -0
  70. data/spec/faraday/error_spec.rb +45 -0
  71. data/spec/faraday/middleware_spec.rb +26 -0
  72. data/spec/faraday/options/env_spec.rb +70 -0
  73. data/spec/faraday/options/options_spec.rb +297 -0
  74. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  75. data/spec/faraday/options/request_options_spec.rb +19 -0
  76. data/spec/faraday/params_encoders/flat_spec.rb +34 -0
  77. data/spec/faraday/params_encoders/nested_spec.rb +134 -0
  78. data/spec/faraday/rack_builder_spec.rb +196 -0
  79. data/spec/faraday/request/authorization_spec.rb +88 -0
  80. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  81. data/spec/faraday/request/multipart_spec.rb +274 -0
  82. data/spec/faraday/request/retry_spec.rb +242 -0
  83. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  84. data/spec/faraday/request_spec.rb +109 -0
  85. data/spec/faraday/response/logger_spec.rb +220 -0
  86. data/spec/faraday/response/middleware_spec.rb +68 -0
  87. data/spec/faraday/response/raise_error_spec.rb +106 -0
  88. data/spec/faraday/response_spec.rb +75 -0
  89. data/spec/faraday/utils/headers_spec.rb +82 -0
  90. data/spec/faraday/utils_spec.rb +56 -0
  91. data/spec/faraday_spec.rb +37 -0
  92. data/spec/spec_helper.rb +132 -0
  93. data/spec/support/disabling_stub.rb +14 -0
  94. data/spec/support/fake_safe_buffer.rb +15 -0
  95. data/spec/support/helper_methods.rb +133 -0
  96. data/spec/support/shared_examples/adapter.rb +104 -0
  97. data/spec/support/shared_examples/params_encoder.rb +18 -0
  98. data/spec/support/shared_examples/request_method.rb +234 -0
  99. data/spec/support/streaming_response_checker.rb +35 -0
  100. data/spec/support/webmock_rack_app.rb +68 -0
  101. metadata +126 -126
  102. data/Gemfile +0 -29
  103. data/config.ru +0 -6
  104. data/faraday.gemspec +0 -92
  105. data/lib/faraday/adapter/action_dispatch.rb +0 -29
  106. data/lib/faraday/builder.rb +0 -160
  107. data/lib/faraday/request/json.rb +0 -35
  108. data/lib/faraday/upload_io.rb +0 -23
  109. data/test/adapters/live_test.rb +0 -205
  110. data/test/adapters/logger_test.rb +0 -37
  111. data/test/adapters/net_http_test.rb +0 -33
  112. data/test/adapters/test_middleware_test.rb +0 -70
  113. data/test/connection_test.rb +0 -254
  114. data/test/env_test.rb +0 -158
  115. data/test/helper.rb +0 -41
  116. data/test/live_server.rb +0 -45
  117. data/test/middleware_stack_test.rb +0 -118
  118. data/test/request_middleware_test.rb +0 -116
  119. data/test/response_middleware_test.rb +0 -74
@@ -1,98 +1,107 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Faraday
6
+ # Response represents an HTTP response from making an HTTP request.
4
7
  class Response
5
8
  # Used for simple response middleware.
6
9
  class Middleware < Faraday::Middleware
7
10
  def call(env)
8
- @app.call(env).on_complete do |env|
9
- on_complete(env)
11
+ @app.call(env).on_complete do |environment|
12
+ on_complete(environment)
10
13
  end
11
14
  end
12
15
 
13
16
  # Override this to modify the environment after the response has finished.
14
17
  # Calls the `parse` method if defined
18
+ # `parse` method can be defined as private, public and protected
15
19
  def on_complete(env)
16
- if respond_to? :parse
17
- env[:body] = parse(env[:body]) unless [204,304].index env[:status]
18
- end
20
+ return unless respond_to?(:parse, true) && env.parse_body?
21
+
22
+ env.body = parse(env.body)
19
23
  end
20
24
  end
21
25
 
22
26
  extend Forwardable
23
- extend AutoloadHelper
27
+ extend MiddlewareRegistry
24
28
 
25
- autoload_all 'faraday/response',
26
- :RaiseError => 'raise_error',
27
- :Logger => 'logger'
28
-
29
- register_lookup_modules \
30
- :raise_error => :RaiseError,
31
- :logger => :Logger
29
+ register_middleware File.expand_path('response', __dir__),
30
+ raise_error: [:RaiseError, 'raise_error'],
31
+ logger: [:Logger, 'logger']
32
32
 
33
33
  def initialize(env = nil)
34
- @env = env
34
+ @env = Env.from(env) if env
35
35
  @on_complete_callbacks = []
36
36
  end
37
37
 
38
38
  attr_reader :env
39
- alias_method :to_hash, :env
40
39
 
41
40
  def status
42
- finished? ? env[:status] : nil
41
+ finished? ? env.status : nil
42
+ end
43
+
44
+ def reason_phrase
45
+ finished? ? env.reason_phrase : nil
43
46
  end
44
47
 
45
48
  def headers
46
- finished? ? env[:response_headers] : {}
49
+ finished? ? env.response_headers : {}
47
50
  end
48
51
  def_delegator :headers, :[]
49
52
 
50
53
  def body
51
- finished? ? env[:body] : nil
54
+ finished? ? env.body : nil
52
55
  end
53
56
 
54
57
  def finished?
55
58
  !!env
56
59
  end
57
60
 
58
- def on_complete
59
- if not finished?
60
- @on_complete_callbacks << Proc.new
61
+ def on_complete(&block)
62
+ if !finished?
63
+ @on_complete_callbacks << block
61
64
  else
62
- yield env
65
+ yield(env)
63
66
  end
64
- return self
67
+ self
65
68
  end
66
69
 
67
70
  def finish(env)
68
- raise "response already finished" if finished?
69
- @env = env
70
- @on_complete_callbacks.each { |callback| callback.call(env) }
71
- return self
71
+ raise 'response already finished' if finished?
72
+
73
+ @env = env.is_a?(Env) ? env : Env.from(env)
74
+ @on_complete_callbacks.each { |callback| callback.call(@env) }
75
+ self
72
76
  end
73
77
 
74
78
  def success?
75
- (200..299).include?(status)
79
+ finished? && env.success?
80
+ end
81
+
82
+ def to_hash
83
+ {
84
+ status: env.status, body: env.body,
85
+ response_headers: env.response_headers
86
+ }
76
87
  end
77
88
 
78
89
  # because @on_complete_callbacks cannot be marshalled
79
90
  def marshal_dump
80
- !finished? ? nil : {
81
- :status => @env[:status], :body => @env[:body],
82
- :response_headers => @env[:response_headers]
83
- }
91
+ finished? ? to_hash : nil
84
92
  end
85
93
 
86
94
  def marshal_load(env)
87
- @env = env
95
+ @env = Env.from(env)
88
96
  end
89
97
 
90
98
  # Expand the env with more properties, without overriding existing ones.
91
99
  # Useful for applying request params after restoring a marshalled Response.
92
100
  def apply_request(request_env)
93
101
  raise "response didn't finish yet" unless finished?
94
- @env = request_env.merge @env
95
- return self
102
+
103
+ @env = Env.from(request_env).update(@env)
104
+ self
96
105
  end
97
106
  end
98
107
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module Utils
5
+ # A case-insensitive Hash that preserves the original case of a header
6
+ # when set.
7
+ #
8
+ # Adapted from Rack::Utils::HeaderHash
9
+ class Headers < ::Hash
10
+ def self.from(value)
11
+ new(value)
12
+ end
13
+
14
+ def self.allocate
15
+ new_self = super
16
+ new_self.initialize_names
17
+ new_self
18
+ end
19
+
20
+ def initialize(hash = nil)
21
+ super()
22
+ @names = {}
23
+ update(hash || {})
24
+ end
25
+
26
+ def initialize_names
27
+ @names = {}
28
+ end
29
+
30
+ # on dup/clone, we need to duplicate @names hash
31
+ def initialize_copy(other)
32
+ super
33
+ @names = other.names.dup
34
+ end
35
+
36
+ # need to synchronize concurrent writes to the shared KeyMap
37
+ keymap_mutex = Mutex.new
38
+
39
+ # symbol -> string mapper + cache
40
+ KeyMap = Hash.new do |map, key|
41
+ value = if key.respond_to?(:to_str)
42
+ key
43
+ else
44
+ key.to_s.split('_') # user_agent: %w(user agent)
45
+ .each(&:capitalize!) # => %w(User Agent)
46
+ .join('-') # => "User-Agent"
47
+ end
48
+ keymap_mutex.synchronize { map[key] = value }
49
+ end
50
+ KeyMap[:etag] = 'ETag'
51
+
52
+ def [](key)
53
+ key = KeyMap[key]
54
+ super(key) || super(@names[key.downcase])
55
+ end
56
+
57
+ def []=(key, val)
58
+ key = KeyMap[key]
59
+ key = (@names[key.downcase] ||= key)
60
+ # join multiple values with a comma
61
+ val = val.to_ary.join(', ') if val.respond_to?(:to_ary)
62
+ super(key, val)
63
+ end
64
+
65
+ def fetch(key, *args, &block)
66
+ key = KeyMap[key]
67
+ key = @names.fetch(key.downcase, key)
68
+ super(key, *args, &block)
69
+ end
70
+
71
+ def delete(key)
72
+ key = KeyMap[key]
73
+ key = @names[key.downcase]
74
+ return unless key
75
+
76
+ @names.delete key.downcase
77
+ super(key)
78
+ end
79
+
80
+ def include?(key)
81
+ @names.include? key.downcase
82
+ end
83
+
84
+ alias has_key? include?
85
+ alias member? include?
86
+ alias key? include?
87
+
88
+ def merge!(other)
89
+ other.each { |k, v| self[k] = v }
90
+ self
91
+ end
92
+
93
+ alias update merge!
94
+
95
+ def merge(other)
96
+ hash = dup
97
+ hash.merge! other
98
+ end
99
+
100
+ def replace(other)
101
+ clear
102
+ @names.clear
103
+ update other
104
+ self
105
+ end
106
+
107
+ def to_hash
108
+ ::Hash.new.update(self)
109
+ end
110
+
111
+ def parse(header_string)
112
+ return unless header_string && !header_string.empty?
113
+
114
+ headers = header_string.split(/\r\n/)
115
+
116
+ # Find the last set of response headers.
117
+ start_index = headers.rindex { |x| x.match(%r{^HTTP/}) } || 0
118
+ last_response = headers.slice(start_index, headers.size)
119
+
120
+ last_response
121
+ .tap { |a| a.shift if a.first.start_with?('HTTP/') }
122
+ .map { |h| h.split(/:\s*/, 2) } # split key and value
123
+ .reject { |p| p[0].nil? } # ignore blank lines
124
+ .each { |key, value| add_parsed(key, value) }
125
+ end
126
+
127
+ protected
128
+
129
+ attr_reader :names
130
+
131
+ private
132
+
133
+ # Join multiple values with a comma.
134
+ def add_parsed(key, value)
135
+ self[key] ? self[key] << ', ' << value : self[key] = value
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module Utils
5
+ # A hash with stringified keys.
6
+ class ParamsHash < Hash
7
+ def [](key)
8
+ super(convert_key(key))
9
+ end
10
+
11
+ def []=(key, value)
12
+ super(convert_key(key), value)
13
+ end
14
+
15
+ def delete(key)
16
+ super(convert_key(key))
17
+ end
18
+
19
+ def include?(key)
20
+ super(convert_key(key))
21
+ end
22
+
23
+ alias has_key? include?
24
+ alias member? include?
25
+ alias key? include?
26
+
27
+ def update(params)
28
+ params.each do |key, value|
29
+ self[key] = value
30
+ end
31
+ self
32
+ end
33
+ alias merge! update
34
+
35
+ def merge(params)
36
+ dup.update(params)
37
+ end
38
+
39
+ def replace(other)
40
+ clear
41
+ update(other)
42
+ end
43
+
44
+ def merge_query(query, encoder = nil)
45
+ return self unless query && !query.empty?
46
+
47
+ update((encoder || Utils.default_params_encoder).decode(query))
48
+ end
49
+
50
+ def to_query(encoder = nil)
51
+ (encoder || Utils.default_params_encoder).encode(self)
52
+ end
53
+
54
+ private
55
+
56
+ def convert_key(key)
57
+ key.to_s
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/faraday/utils.rb CHANGED
@@ -1,149 +1,106 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday/utils/headers'
4
+ require 'faraday/utils/params_hash'
2
5
 
3
6
  module Faraday
7
+ # Utils contains various static helper methods.
4
8
  module Utils
5
- include Rack::Utils
6
-
7
- extend Rack::Utils
8
- extend self
9
-
10
- class Headers < HeaderHash
11
- # symbol -> string mapper + cache
12
- KeyMap = Hash.new do |map, key|
13
- map[key] = if key.respond_to?(:to_str) then key
14
- else
15
- key.to_s.split('_'). # :user_agent => %w(user agent)
16
- each { |w| w.capitalize! }. # => %w(User Agent)
17
- join('-') # => "User-Agent"
18
- end
19
- end
20
- KeyMap[:etag] = "ETag"
21
-
22
- def [](k)
23
- super(KeyMap[k])
24
- end
9
+ module_function
25
10
 
26
- def []=(k, v)
27
- # join multiple values with a comma
28
- v = v.to_ary.join(', ') if v.respond_to? :to_ary
29
- super(KeyMap[k], v)
30
- end
31
-
32
- alias_method :update, :merge!
33
-
34
- def parse(header_string)
35
- return unless header_string && !header_string.empty?
36
- header_string.split(/\r\n/).
37
- tap { |a| a.shift if a.first.index('HTTP/') == 0 }. # drop the HTTP status line
38
- map { |h| h.split(/:\s+/, 2) }.reject { |(k, v)| k.nil? }. # split key and value, ignore blank lines
39
- each { |key, value|
40
- # join multiple values with a comma
41
- if self[key] then self[key] << ', ' << value
42
- else self[key] = value
43
- end
44
- }
45
- end
11
+ def build_query(params)
12
+ FlatParamsEncoder.encode(params)
46
13
  end
47
14
 
48
- # hash with stringified keys
49
- class ParamsHash < Hash
50
- def [](key)
51
- super(convert_key(key))
52
- end
53
-
54
- def []=(key, value)
55
- super(convert_key(key), value)
56
- end
15
+ def build_nested_query(params)
16
+ NestedParamsEncoder.encode(params)
17
+ end
57
18
 
58
- def delete(key)
59
- super(convert_key(key))
60
- end
19
+ def default_space_encoding
20
+ @default_space_encoding ||= '+'
21
+ end
61
22
 
62
- def include?(key)
63
- super(convert_key(key))
64
- end
23
+ class << self
24
+ attr_writer :default_space_encoding
25
+ end
65
26
 
66
- alias_method :has_key?, :include?
67
- alias_method :member?, :include?
68
- alias_method :key?, :include?
27
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
69
28
 
70
- def update(params)
71
- params.each do |key, value|
72
- self[key] = value
73
- end
74
- self
75
- end
76
- alias_method :merge!, :update
29
+ def escape(str)
30
+ str.to_s.gsub(ESCAPE_RE) do |match|
31
+ '%' + match.unpack('H2' * match.bytesize).join('%').upcase
32
+ end.gsub(' ', default_space_encoding)
33
+ end
77
34
 
78
- def merge(params)
79
- dup.update(params)
80
- end
35
+ def unescape(str)
36
+ CGI.unescape str.to_s
37
+ end
81
38
 
82
- def replace(other)
83
- clear
84
- update(other)
85
- end
39
+ DEFAULT_SEP = /[&;] */n.freeze
86
40
 
87
- def merge_query(query)
88
- if query && !query.empty?
89
- update Utils.parse_query(query)
90
- end
91
- self
92
- end
41
+ # Adapted from Rack
42
+ def parse_query(query)
43
+ FlatParamsEncoder.decode(query)
44
+ end
93
45
 
94
- def to_query
95
- Utils.build_query(self)
96
- end
46
+ def parse_nested_query(query)
47
+ NestedParamsEncoder.decode(query)
48
+ end
97
49
 
98
- private
50
+ def default_params_encoder
51
+ @default_params_encoder ||= NestedParamsEncoder
52
+ end
99
53
 
100
- def convert_key(key)
101
- key.to_s
102
- end
54
+ class << self
55
+ attr_writer :default_params_encoder
103
56
  end
104
57
 
105
- # Make Rack::Utils methods public.
106
- public :build_query, :parse_query
107
-
108
- # Override Rack's version since it doesn't handle non-String values
109
- def build_nested_query(value, prefix = nil)
110
- case value
111
- when Array
112
- value.map { |v| build_nested_query(v, "#{prefix}[]") }.join("&")
113
- when Hash
114
- value.map { |k, v|
115
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
116
- }.join("&")
117
- when NilClass
118
- prefix
58
+ # Normalize URI() behavior across Ruby versions
59
+ #
60
+ # url - A String or URI.
61
+ #
62
+ # Returns a parsed URI.
63
+ def URI(url) # rubocop:disable Naming/MethodName
64
+ if url.respond_to?(:host)
65
+ url
66
+ elsif url.respond_to?(:to_str)
67
+ default_uri_parser.call(url)
119
68
  else
120
- raise ArgumentError, "value must be a Hash" if prefix.nil?
121
- "#{prefix}=#{escape(value)}"
69
+ raise ArgumentError, 'bad argument (expected URI object or URI string)'
122
70
  end
123
71
  end
124
72
 
125
- # Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
126
- # as spaces.
127
- def escape(s)
128
- s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
129
- '%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
73
+ def default_uri_parser
74
+ @default_uri_parser ||= begin
75
+ require 'uri'
76
+ Kernel.method(:URI)
130
77
  end
131
78
  end
132
79
 
133
- # Receives a URL and returns just the path with the query string sorted.
80
+ def default_uri_parser=(parser)
81
+ @default_uri_parser = if parser.respond_to?(:call) || parser.nil?
82
+ parser
83
+ else
84
+ parser.method(:parse)
85
+ end
86
+ end
87
+
88
+ # Receives a String or URI and returns just
89
+ # the path with the query string sorted.
134
90
  def normalize_path(url)
135
- (url.path != "" ? url.path : "/") +
136
- (url.query ? "?#{sort_query_params(url.query)}" : "")
91
+ url = URI(url)
92
+ (url.path.start_with?('/') ? url.path : '/' + url.path) +
93
+ (url.query ? "?#{sort_query_params(url.query)}" : '')
137
94
  end
138
95
 
139
96
  # Recursive hash update
140
97
  def deep_merge!(target, hash)
141
98
  hash.each do |key, value|
142
- if Hash === value and Hash === target[key]
143
- target[key] = deep_merge(target[key], value)
144
- else
145
- target[key] = value
146
- end
99
+ target[key] = if value.is_a?(Hash) && target[key].is_a?(Hash)
100
+ deep_merge(target[key], value)
101
+ else
102
+ value
103
+ end
147
104
  end
148
105
  target
149
106
  end
@@ -153,8 +110,6 @@ module Faraday
153
110
  deep_merge!(source.dup, hash)
154
111
  end
155
112
 
156
- protected
157
-
158
113
  def sort_query_params(query)
159
114
  query.split('&').sort.join('&')
160
115
  end