faraday 0.17.6 → 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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -8
  3. data/LICENSE.md +1 -1
  4. data/README.md +18 -358
  5. data/Rakefile +1 -7
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday/adapter/em_http.rb +142 -99
  9. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  10. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  11. data/lib/faraday/adapter/em_synchrony.rb +104 -60
  12. data/lib/faraday/adapter/excon.rb +98 -56
  13. data/lib/faraday/adapter/httpclient.rb +83 -59
  14. data/lib/faraday/adapter/net_http.rb +129 -63
  15. data/lib/faraday/adapter/net_http_persistent.rb +50 -27
  16. data/lib/faraday/adapter/patron.rb +80 -43
  17. data/lib/faraday/adapter/rack.rb +30 -13
  18. data/lib/faraday/adapter/test.rb +86 -53
  19. data/lib/faraday/adapter/typhoeus.rb +4 -1
  20. data/lib/faraday/adapter.rb +82 -22
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +47 -36
  23. data/lib/faraday/connection.rb +312 -182
  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 +9 -35
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/middleware.rb +12 -28
  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 +32 -183
  38. data/lib/faraday/param_part.rb +53 -0
  39. data/lib/faraday/parameters.rb +4 -197
  40. data/lib/faraday/rack_builder.rb +66 -55
  41. data/lib/faraday/request/authorization.rb +44 -30
  42. data/lib/faraday/request/basic_authentication.rb +14 -7
  43. data/lib/faraday/request/instrumentation.rb +45 -27
  44. data/lib/faraday/request/multipart.rb +79 -48
  45. data/lib/faraday/request/retry.rb +197 -171
  46. data/lib/faraday/request/token_authentication.rb +15 -10
  47. data/lib/faraday/request/url_encoded.rb +43 -23
  48. data/lib/faraday/request.rb +68 -38
  49. data/lib/faraday/response/logger.rb +22 -69
  50. data/lib/faraday/response/raise_error.rb +38 -18
  51. data/lib/faraday/response.rb +24 -14
  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 +36 -245
  55. data/lib/faraday.rb +94 -175
  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 +0 -57
  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 +15 -15
  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 +63 -36
  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 +66 -38
  102. data/lib/faraday/deprecate.rb +0 -109
  103. data/lib/faraday/upload_io.rb +0 -77
  104. data/spec/faraday/deprecate_spec.rb +0 -147
  105. data/test/adapters/default_test.rb +0 -14
  106. data/test/adapters/em_http_test.rb +0 -30
  107. data/test/adapters/em_synchrony_test.rb +0 -32
  108. data/test/adapters/excon_test.rb +0 -30
  109. data/test/adapters/httpclient_test.rb +0 -34
  110. data/test/adapters/integration.rb +0 -263
  111. data/test/adapters/logger_test.rb +0 -136
  112. data/test/adapters/net_http_persistent_test.rb +0 -114
  113. data/test/adapters/net_http_test.rb +0 -79
  114. data/test/adapters/patron_test.rb +0 -40
  115. data/test/adapters/rack_test.rb +0 -38
  116. data/test/adapters/test_middleware_test.rb +0 -157
  117. data/test/adapters/typhoeus_test.rb +0 -38
  118. data/test/authentication_middleware_test.rb +0 -65
  119. data/test/composite_read_io_test.rb +0 -109
  120. data/test/connection_test.rb +0 -738
  121. data/test/env_test.rb +0 -268
  122. data/test/helper.rb +0 -75
  123. data/test/live_server.rb +0 -67
  124. data/test/middleware/instrumentation_test.rb +0 -88
  125. data/test/middleware/retry_test.rb +0 -282
  126. data/test/middleware_stack_test.rb +0 -260
  127. data/test/multibyte.txt +0 -1
  128. data/test/options_test.rb +0 -333
  129. data/test/parameters_test.rb +0 -157
  130. data/test/request_middleware_test.rb +0 -126
  131. data/test/response_middleware_test.rb +0 -72
  132. data/test/strawberry.rb +0 -2
  133. data/test/utils_test.rb +0 -98
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- # Used to setup urls, params, headers, and the request body in a sane manner.
4
+ # Used to setup URLs, params, headers, and the request body in a sane manner.
3
5
  #
6
+ # @example
4
7
  # @connection.post do |req|
5
8
  # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
6
9
  # req.headers['b'] = '2' # Header
@@ -9,27 +12,53 @@ module Faraday
9
12
  # req.body = 'abc'
10
13
  # end
11
14
  #
15
+ # @!attribute method
16
+ # @return [Symbol] the HTTP method of the Request
17
+ # @!attribute path
18
+ # @return [URI, String] the path
19
+ # @!attribute params
20
+ # @return [Hash] query parameters
21
+ # @!attribute headers
22
+ # @return [Faraday::Utils::Headers] headers
23
+ # @!attribute body
24
+ # @return [Hash] body
25
+ # @!attribute options
26
+ # @return [RequestOptions] options
27
+ #
28
+ # rubocop:disable Style/StructInheritance
12
29
  class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
- extend MiddlewareRegistry
30
+ # rubocop:enable Style/StructInheritance
14
31
 
15
- alias_method :http_method, :method
32
+ extend MiddlewareRegistry
16
33
 
17
- register_middleware File.expand_path('../request', __FILE__),
18
- :url_encoded => [:UrlEncoded, 'url_encoded'],
19
- :multipart => [:Multipart, 'multipart'],
20
- :retry => [:Retry, 'retry'],
21
- :authorization => [:Authorization, 'authorization'],
22
- :basic_auth => [:BasicAuthentication, 'basic_authentication'],
23
- :token_auth => [:TokenAuthentication, 'token_authentication'],
24
- :instrumentation => [:Instrumentation, 'instrumentation']
34
+ register_middleware File.expand_path('request', __dir__),
35
+ url_encoded: [:UrlEncoded, 'url_encoded'],
36
+ multipart: [:Multipart, 'multipart'],
37
+ retry: [:Retry, 'retry'],
38
+ authorization: [:Authorization, 'authorization'],
39
+ basic_auth: [
40
+ :BasicAuthentication,
41
+ 'basic_authentication'
42
+ ],
43
+ token_auth: [
44
+ :TokenAuthentication,
45
+ 'token_authentication'
46
+ ],
47
+ instrumentation: [:Instrumentation, 'instrumentation']
25
48
 
49
+ # @param request_method [String]
50
+ # @yield [request] for block customization, if block given
51
+ # @yieldparam request [Request]
52
+ # @return [Request]
26
53
  def self.create(request_method)
27
54
  new(request_method).tap do |request|
28
55
  yield(request) if block_given?
29
56
  end
30
57
  end
31
58
 
32
- # Public: Replace params, preserving the existing hash type
59
+ # Replace params, preserving the existing hash type.
60
+ #
61
+ # @param hash [Hash] new params
33
62
  def params=(hash)
34
63
  if params
35
64
  params.replace hash
@@ -38,7 +67,9 @@ module Faraday
38
67
  end
39
68
  end
40
69
 
41
- # Public: Replace request headers, preserving the existing hash type
70
+ # Replace request headers, preserving the existing hash type.
71
+ #
72
+ # @param hash [Hash] new headers
42
73
  def headers=(hash)
43
74
  if headers
44
75
  headers.replace hash
@@ -47,9 +78,14 @@ module Faraday
47
78
  end
48
79
  end
49
80
 
81
+ # Update path and params.
82
+ #
83
+ # @param path [URI, String]
84
+ # @param params [Hash, nil]
85
+ # @return [void]
50
86
  def url(path, params = nil)
51
87
  if path.respond_to? :query
52
- if query = path.query
88
+ if (query = path.query)
53
89
  path = path.dup
54
90
  path.query = nil
55
91
  end
@@ -63,25 +99,35 @@ module Faraday
63
99
  self.params.update(params) if params
64
100
  end
65
101
 
102
+ # @param key [Object] key to look up in headers
103
+ # @return [Object] value of the given header name
66
104
  def [](key)
67
105
  headers[key]
68
106
  end
69
107
 
108
+ # @param key [Object] key of header to write
109
+ # @param value [Object] value of header
70
110
  def []=(key, value)
71
111
  headers[key] = value
72
112
  end
73
113
 
114
+ # Marshal serialization support.
115
+ #
116
+ # @return [Hash] the hash ready to be serialized in Marshal.
74
117
  def marshal_dump
75
118
  {
76
- :method => method,
77
- :body => body,
78
- :headers => headers,
79
- :path => path,
80
- :params => params,
81
- :options => options
119
+ method: method,
120
+ body: body,
121
+ headers: headers,
122
+ path: path,
123
+ params: params,
124
+ options: options
82
125
  }
83
126
  end
84
127
 
128
+ # Marshal serialization support.
129
+ # Restores the instance variables according to the +serialised+.
130
+ # @param serialised [Hash] the serialised object.
85
131
  def marshal_load(serialised)
86
132
  self.method = serialised[:method]
87
133
  self.body = serialised[:body]
@@ -91,26 +137,10 @@ module Faraday
91
137
  self.options = serialised[:options]
92
138
  end
93
139
 
94
- # ENV Keys
95
- # :method - a symbolized request method (:get, :post)
96
- # :body - the request body that will eventually be converted to a string.
97
- # :url - URI instance for the current request.
98
- # :status - HTTP response status code
99
- # :request_headers - hash of HTTP Headers to be sent to the server
100
- # :response_headers - Hash of HTTP headers from the server
101
- # :parallel_manager - sent if the connection is in parallel mode
102
- # :request - Hash of options for configuring the request.
103
- # :timeout - open/read timeout Integer in seconds
104
- # :open_timeout - read timeout Integer in seconds
105
- # :proxy - Hash of proxy options
106
- # :uri - Proxy Server URI
107
- # :user - Proxy server username
108
- # :password - Proxy server password
109
- # :ssl - Hash of options for configuring SSL requests.
140
+ # @return [Env] the Env for this Request
110
141
  def to_env(connection)
111
142
  Env.new(method, body, connection.build_exclusive_url(path, params),
112
- options, headers, connection.ssl, connection.parallel_manager)
143
+ options, headers, connection.ssl, connection.parallel_manager)
113
144
  end
114
145
  end
115
146
  end
116
-
@@ -1,80 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
4
+ require 'faraday/logging/formatter'
2
5
 
3
6
  module Faraday
4
- class Response::Logger < Response::Middleware
5
- extend Forwardable
6
-
7
- DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
-
9
- def initialize(app, logger = nil, options = {})
10
- super(app)
11
- @logger = logger || begin
12
- require 'logger'
13
- ::Logger.new($stdout)
14
- end
15
- @filter = []
16
- @options = DEFAULT_OPTIONS.merge(options)
17
- yield self if block_given?
18
- end
19
-
20
- def_delegators :@logger, :debug, :info, :warn, :error, :fatal
21
-
22
- def call(env)
23
- info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
- debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
- debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
26
- super
27
- end
28
-
29
- def on_complete(env)
30
- info('response') { "Status #{env.status.to_s}" }
31
- debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
- debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
- end
34
-
35
- def filter(filter_word, filter_replacement)
36
- @filter.push([ filter_word, filter_replacement ])
37
- end
38
-
39
- private
40
-
41
- def dump_headers(headers)
42
- headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
43
- end
44
-
45
- def dump_body(body)
46
- if body.respond_to?(:to_str)
47
- body.to_str
48
- else
49
- pretty_inspect(body)
50
- end
51
- end
52
-
53
- def pretty_inspect(body)
54
- require 'pp' unless body.respond_to?(:pretty_inspect)
55
- body.pretty_inspect
56
- end
57
-
58
- def log_headers?(type)
59
- case @options[:headers]
60
- when Hash then @options[:headers][type]
61
- else @options[:headers]
7
+ class Response
8
+ # Logger is a middleware that logs internal events in the HTTP request
9
+ # lifecycle to a given Logger object. By default, this logs to STDOUT. See
10
+ # Faraday::Logging::Formatter to see specifically what is logged.
11
+ class Logger < Middleware
12
+ def initialize(app, logger = nil, options = {})
13
+ super(app)
14
+ logger ||= begin
15
+ require 'logger'
16
+ ::Logger.new($stdout)
17
+ end
18
+ formatter_class = options.delete(:formatter) || Logging::Formatter
19
+ @formatter = formatter_class.new(logger: logger, options: options)
20
+ yield @formatter if block_given?
62
21
  end
63
- end
64
22
 
65
- def log_body?(type)
66
- case @options[:bodies]
67
- when Hash then @options[:bodies][type]
68
- else @options[:bodies]
23
+ def call(env)
24
+ @formatter.request(env)
25
+ super
69
26
  end
70
- end
71
27
 
72
- def apply_filters(output)
73
- @filter.each do |pattern, replacement|
74
- output = output.to_s.gsub(pattern, replacement)
28
+ def on_complete(env)
29
+ @formatter.response(env)
75
30
  end
76
- output
77
31
  end
78
-
79
32
  end
80
33
  end
@@ -1,25 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- class Response::RaiseError < Response::Middleware
3
- ClientErrorStatuses = 400...600
4
+ class Response
5
+ # RaiseError is a Faraday middleware that raises exceptions on common HTTP
6
+ # client or server error responses.
7
+ class RaiseError < Middleware
8
+ # rubocop:disable Naming/ConstantName
9
+ ClientErrorStatuses = (400...500).freeze
10
+ ServerErrorStatuses = (500...600).freeze
11
+ # rubocop:enable Naming/ConstantName
4
12
 
5
- def on_complete(env)
6
- case env[:status]
7
- when 404
8
- raise Faraday::ResourceNotFound, response_values(env)
9
- when 407
10
- # mimic the behavior that we get with proxy requests with HTTPS
11
- raise Faraday::ConnectionFailed.new(
12
- %{407 "Proxy Authentication Required "},
13
- response_values(env))
14
- when ClientErrorStatuses
15
- raise Faraday::ClientError, response_values(env)
16
- when nil
17
- raise Faraday::NilStatusError, response_values(env)
13
+ def on_complete(env)
14
+ case env[:status]
15
+ when 400
16
+ raise Faraday::BadRequestError, response_values(env)
17
+ when 401
18
+ raise Faraday::UnauthorizedError, response_values(env)
19
+ when 403
20
+ raise Faraday::ForbiddenError, response_values(env)
21
+ when 404
22
+ raise Faraday::ResourceNotFound, response_values(env)
23
+ when 407
24
+ # mimic the behavior that we get with proxy requests with HTTPS
25
+ msg = %(407 "Proxy Authentication Required")
26
+ raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
+ when 409
28
+ raise Faraday::ConflictError, response_values(env)
29
+ when 422
30
+ raise Faraday::UnprocessableEntityError, response_values(env)
31
+ when ClientErrorStatuses
32
+ raise Faraday::ClientError, response_values(env)
33
+ when ServerErrorStatuses
34
+ raise Faraday::ServerError, response_values(env)
35
+ when nil
36
+ raise Faraday::NilStatusError, response_values(env)
37
+ end
18
38
  end
19
- end
20
39
 
21
- def response_values(env)
22
- {:status => env.status, :headers => env.response_headers, :body => env.body}
40
+ def response_values(env)
41
+ { status: env.status, headers: env.response_headers, body: env.body }
42
+ end
23
43
  end
24
44
  end
25
45
  end
@@ -1,6 +1,9 @@
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
@@ -12,17 +15,20 @@ module Faraday
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
- env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
20
+ return unless respond_to?(:parse, true) && env.parse_body?
21
+
22
+ env.body = parse(env.body)
17
23
  end
18
24
  end
19
25
 
20
26
  extend Forwardable
21
27
  extend MiddlewareRegistry
22
28
 
23
- register_middleware File.expand_path('../response', __FILE__),
24
- :raise_error => [:RaiseError, 'raise_error'],
25
- :logger => [:Logger, 'logger']
29
+ register_middleware File.expand_path('response', __dir__),
30
+ raise_error: [:RaiseError, 'raise_error'],
31
+ logger: [:Logger, 'logger']
26
32
 
27
33
  def initialize(env = nil)
28
34
  @env = Env.from(env) if env
@@ -31,8 +37,6 @@ module Faraday
31
37
 
32
38
  attr_reader :env
33
39
 
34
- def_delegators :env, :to_hash
35
-
36
40
  def status
37
41
  finished? ? env.status : nil
38
42
  end
@@ -60,26 +64,31 @@ module Faraday
60
64
  else
61
65
  yield(env)
62
66
  end
63
- return self
67
+ self
64
68
  end
65
69
 
66
70
  def finish(env)
67
- raise "response already finished" if finished?
71
+ raise 'response already finished' if finished?
72
+
68
73
  @env = env.is_a?(Env) ? env : Env.from(env)
69
74
  @on_complete_callbacks.each { |callback| callback.call(@env) }
70
- return self
75
+ self
71
76
  end
72
77
 
73
78
  def success?
74
79
  finished? && env.success?
75
80
  end
76
81
 
82
+ def to_hash
83
+ {
84
+ status: env.status, body: env.body,
85
+ response_headers: env.response_headers
86
+ }
87
+ end
88
+
77
89
  # because @on_complete_callbacks cannot be marshalled
78
90
  def marshal_dump
79
- !finished? ? nil : {
80
- :status => @env.status, :body => @env.body,
81
- :response_headers => @env.response_headers
82
- }
91
+ finished? ? to_hash : nil
83
92
  end
84
93
 
85
94
  def marshal_load(env)
@@ -90,8 +99,9 @@ module Faraday
90
99
  # Useful for applying request params after restoring a marshalled Response.
91
100
  def apply_request(request_env)
92
101
  raise "response didn't finish yet" unless finished?
102
+
93
103
  @env = Env.from(request_env).update(@env)
94
- return self
104
+ self
95
105
  end
96
106
  end
97
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