faraday 0.15.0 → 2.2.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +570 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +25 -351
  5. data/Rakefile +7 -0
  6. data/examples/client_spec.rb +97 -0
  7. data/examples/client_test.rb +118 -0
  8. data/lib/faraday/adapter/test.rb +127 -72
  9. data/lib/faraday/adapter.rb +69 -22
  10. data/lib/faraday/adapter_registry.rb +30 -0
  11. data/lib/faraday/connection.rb +309 -232
  12. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  13. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  14. data/lib/faraday/error.rb +119 -38
  15. data/lib/faraday/logging/formatter.rb +106 -0
  16. data/lib/faraday/methods.rb +6 -0
  17. data/lib/faraday/middleware.rb +18 -25
  18. data/lib/faraday/middleware_registry.rb +83 -0
  19. data/lib/faraday/options/connection_options.rb +22 -0
  20. data/lib/faraday/options/env.rb +181 -0
  21. data/lib/faraday/options/proxy_options.rb +32 -0
  22. data/lib/faraday/options/request_options.rb +22 -0
  23. data/lib/faraday/options/ssl_options.rb +59 -0
  24. data/lib/faraday/options.rb +41 -195
  25. data/lib/faraday/parameters.rb +4 -196
  26. data/lib/faraday/rack_builder.rb +91 -76
  27. data/lib/faraday/request/authorization.rb +37 -29
  28. data/lib/faraday/request/instrumentation.rb +47 -27
  29. data/lib/faraday/request/json.rb +55 -0
  30. data/lib/faraday/request/url_encoded.rb +45 -23
  31. data/lib/faraday/request.rb +74 -32
  32. data/lib/faraday/response/json.rb +54 -0
  33. data/lib/faraday/response/logger.rb +22 -69
  34. data/lib/faraday/response/raise_error.rb +57 -14
  35. data/lib/faraday/response.rb +26 -33
  36. data/lib/faraday/utils/headers.rb +139 -0
  37. data/lib/faraday/utils/params_hash.rb +61 -0
  38. data/lib/faraday/utils.rb +47 -251
  39. data/lib/faraday/version.rb +5 -0
  40. data/lib/faraday.rb +108 -199
  41. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  42. data/spec/faraday/adapter/test_spec.rb +377 -0
  43. data/spec/faraday/adapter_registry_spec.rb +28 -0
  44. data/spec/faraday/adapter_spec.rb +55 -0
  45. data/spec/faraday/connection_spec.rb +787 -0
  46. data/spec/faraday/error_spec.rb +60 -0
  47. data/spec/faraday/middleware_registry_spec.rb +31 -0
  48. data/spec/faraday/middleware_spec.rb +52 -0
  49. data/spec/faraday/options/env_spec.rb +70 -0
  50. data/spec/faraday/options/options_spec.rb +297 -0
  51. data/spec/faraday/options/proxy_options_spec.rb +44 -0
  52. data/spec/faraday/options/request_options_spec.rb +19 -0
  53. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  54. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  55. data/spec/faraday/rack_builder_spec.rb +317 -0
  56. data/spec/faraday/request/authorization_spec.rb +83 -0
  57. data/spec/faraday/request/instrumentation_spec.rb +74 -0
  58. data/spec/faraday/request/json_spec.rb +111 -0
  59. data/spec/faraday/request/url_encoded_spec.rb +82 -0
  60. data/spec/faraday/request_spec.rb +109 -0
  61. data/spec/faraday/response/json_spec.rb +117 -0
  62. data/spec/faraday/response/logger_spec.rb +220 -0
  63. data/spec/faraday/response/raise_error_spec.rb +172 -0
  64. data/spec/faraday/response_spec.rb +75 -0
  65. data/spec/faraday/utils/headers_spec.rb +82 -0
  66. data/spec/faraday/utils_spec.rb +117 -0
  67. data/spec/faraday_spec.rb +37 -0
  68. data/spec/spec_helper.rb +132 -0
  69. data/spec/support/disabling_stub.rb +14 -0
  70. data/spec/support/fake_safe_buffer.rb +15 -0
  71. data/spec/support/helper_methods.rb +96 -0
  72. data/spec/support/shared_examples/adapter.rb +104 -0
  73. data/spec/support/shared_examples/params_encoder.rb +18 -0
  74. data/spec/support/shared_examples/request_method.rb +249 -0
  75. data/spec/support/streaming_response_checker.rb +35 -0
  76. metadata +86 -34
  77. data/lib/faraday/adapter/em_http.rb +0 -243
  78. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -56
  79. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -66
  80. data/lib/faraday/adapter/em_synchrony.rb +0 -106
  81. data/lib/faraday/adapter/excon.rb +0 -79
  82. data/lib/faraday/adapter/httpclient.rb +0 -128
  83. data/lib/faraday/adapter/net_http.rb +0 -137
  84. data/lib/faraday/adapter/net_http_persistent.rb +0 -63
  85. data/lib/faraday/adapter/patron.rb +0 -100
  86. data/lib/faraday/adapter/rack.rb +0 -58
  87. data/lib/faraday/adapter/typhoeus.rb +0 -12
  88. data/lib/faraday/autoload.rb +0 -84
  89. data/lib/faraday/request/basic_authentication.rb +0 -13
  90. data/lib/faraday/request/multipart.rb +0 -68
  91. data/lib/faraday/request/retry.rb +0 -211
  92. data/lib/faraday/request/token_authentication.rb +0 -15
  93. data/lib/faraday/upload_io.rb +0 -67
@@ -1,51 +1,61 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
4
  class Adapter
3
- # Examples
4
- #
5
+ # @example
5
6
  # test = Faraday::Connection.new do
6
7
  # use Faraday::Adapter::Test do |stub|
7
- # # simply define matcher to match the request
8
+ # # Define matcher to match the request
8
9
  # stub.get '/resource.json' do
9
10
  # # return static content
10
11
  # [200, {'Content-Type' => 'application/json'}, 'hi world']
11
12
  # end
12
- #
13
+ #
13
14
  # # response with content generated based on request
14
15
  # stub.get '/showget' do |env|
15
16
  # [200, {'Content-Type' => 'text/plain'}, env[:method].to_s]
16
17
  # end
17
- #
18
- # # regular expression can be used as matching filter
18
+ #
19
+ # # A regular expression can be used as matching filter
19
20
  # stub.get /\A\/items\/(\d+)\z/ do |env, meta|
20
- # # in case regular expression is used an instance of MatchData can be received
21
- # [200, {'Content-Type' => 'text/plain'}, "showing item: #{meta[:match_data][1]}"]
21
+ # # in case regular expression is used, an instance of MatchData
22
+ # # can be received
23
+ # [200,
24
+ # {'Content-Type' => 'text/plain'},
25
+ # "showing item: #{meta[:match_data][1]}"
26
+ # ]
22
27
  # end
28
+ #
29
+ # # You can set strict_mode to exactly match the stubbed requests.
30
+ # stub.strict_mode = true
23
31
  # end
24
32
  # end
25
- #
33
+ #
26
34
  # resp = test.get '/resource.json'
27
35
  # resp.body # => 'hi world'
28
- #
36
+ #
29
37
  # resp = test.get '/showget'
30
38
  # resp.body # => 'get'
31
- #
39
+ #
32
40
  # resp = test.get '/items/1'
33
41
  # resp.body # => 'showing item: 1'
34
- #
42
+ #
35
43
  # resp = test.get '/items/2'
36
44
  # resp.body # => 'showing item: 2'
37
- #
38
-
39
45
  class Test < Faraday::Adapter
40
46
  attr_accessor :stubs
41
47
 
48
+ # A stack of Stubs
42
49
  class Stubs
43
50
  class NotFound < StandardError
44
51
  end
45
52
 
46
- def initialize
47
- # {:get => [Stub, Stub]}
48
- @stack, @consumed = {}, {}
53
+ def initialize(strict_mode: false)
54
+ # { get: [Stub, Stub] }
55
+ @stack = {}
56
+ @consumed = {}
57
+ @strict_mode = strict_mode
58
+ @stubs_mutex = Monitor.new
49
59
  yield(self) if block_given?
50
60
  end
51
61
 
@@ -53,17 +63,23 @@ module Faraday
53
63
  @stack.empty?
54
64
  end
55
65
 
56
- def match(request_method, host, path, headers, body)
57
- return false if !@stack.key?(request_method)
66
+ # @param env [Faraday::Env]
67
+ def match(env)
68
+ request_method = env[:method]
69
+ return false unless @stack.key?(request_method)
70
+
58
71
  stack = @stack[request_method]
59
72
  consumed = (@consumed[request_method] ||= [])
60
73
 
61
- stub, meta = matches?(stack, host, path, headers, body)
62
- if stub
63
- consumed << stack.delete(stub)
64
- return stub, meta
74
+ @stubs_mutex.synchronize do
75
+ stub, meta = matches?(stack, env)
76
+ if stub
77
+ removed = stack.delete(stub)
78
+ consumed << removed unless removed.nil?
79
+ return stub, meta
80
+ end
65
81
  end
66
- matches?(consumed, host, path, headers, body)
82
+ matches?(consumed, env)
67
83
  end
68
84
 
69
85
  def get(path, headers = {}, &block)
@@ -74,15 +90,15 @@ module Faraday
74
90
  new_stub(:head, path, headers, &block)
75
91
  end
76
92
 
77
- def post(path, body=nil, headers = {}, &block)
93
+ def post(path, body = nil, headers = {}, &block)
78
94
  new_stub(:post, path, headers, body, &block)
79
95
  end
80
96
 
81
- def put(path, body=nil, headers = {}, &block)
97
+ def put(path, body = nil, headers = {}, &block)
82
98
  new_stub(:put, path, headers, body, &block)
83
99
  end
84
100
 
85
- def patch(path, body=nil, headers = {}, &block)
101
+ def patch(path, body = nil, headers = {}, &block)
86
102
  new_stub(:patch, path, headers, body, &block)
87
103
  end
88
104
 
@@ -98,76 +114,109 @@ module Faraday
98
114
  def verify_stubbed_calls
99
115
  failed_stubs = []
100
116
  @stack.each do |method, stubs|
101
- unless stubs.size == 0
102
- failed_stubs.concat(stubs.map {|stub|
117
+ next if stubs.empty?
118
+
119
+ failed_stubs.concat(
120
+ stubs.map do |stub|
103
121
  "Expected #{method} #{stub}."
104
- })
122
+ end
123
+ )
124
+ end
125
+ raise failed_stubs.join(' ') unless failed_stubs.empty?
126
+ end
127
+
128
+ # Set strict_mode. If the value is true, this adapter tries to find matched requests strictly,
129
+ # which means that all of a path, parameters, and headers must be the same as an actual request.
130
+ def strict_mode=(value)
131
+ @strict_mode = value
132
+ @stack.each do |_method, stubs|
133
+ stubs.each do |stub|
134
+ stub.strict_mode = value
105
135
  end
106
136
  end
107
- raise failed_stubs.join(" ") unless failed_stubs.size == 0
108
137
  end
109
138
 
110
139
  protected
111
140
 
112
- def new_stub(request_method, path, headers = {}, body=nil, &block)
141
+ def new_stub(request_method, path, headers = {}, body = nil, &block)
113
142
  normalized_path, host =
114
143
  if path.is_a?(Regexp)
115
144
  path
116
145
  else
117
- [Faraday::Utils.normalize_path(path), Faraday::Utils.URI(path).host]
146
+ [
147
+ Faraday::Utils.normalize_path(path),
148
+ Faraday::Utils.URI(path).host
149
+ ]
118
150
  end
151
+ path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
152
+ headers = Utils::Headers.new(headers)
119
153
 
120
- (@stack[request_method] ||= []) << Stub.new(host, normalized_path, headers, body, block)
154
+ stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
155
+ (@stack[request_method] ||= []) << stub
121
156
  end
122
157
 
123
- def matches?(stack, host, path, headers, body)
158
+ # @param stack [Hash]
159
+ # @param env [Faraday::Env]
160
+ def matches?(stack, env)
124
161
  stack.each do |stub|
125
- match_result, meta = stub.matches?(host, path, headers, body)
162
+ match_result, meta = stub.matches?(env)
126
163
  return stub, meta if match_result
127
164
  end
128
165
  nil
129
166
  end
130
167
  end
131
168
 
132
- class Stub < Struct.new(:host, :path, :params, :headers, :body, :block)
133
- def initialize(host, full, headers, body, block)
134
- path, query = full.respond_to?(:split) ? full.split("?") : full
135
- params = query ?
136
- Faraday::Utils.parse_nested_query(query) :
137
- {}
138
- super(host, path, params, headers, body, block)
139
- end
140
-
141
- def matches?(request_host, request_uri, request_headers, request_body)
142
- request_path, request_query = request_uri.split('?')
143
- request_params = request_query ?
144
- Faraday::Utils.parse_nested_query(request_query) :
145
- {}
146
- # meta is a hash use as carrier
169
+ # Stub request
170
+ class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
171
+ # @param env [Faraday::Env]
172
+ def matches?(env)
173
+ request_host = env[:url].host
174
+ request_path = Faraday::Utils.normalize_path(env[:url].path)
175
+ request_headers = env.request_headers
176
+ request_body = env[:body]
177
+
178
+ # meta is a hash used as carrier
147
179
  # that will be yielded to consumer block
148
180
  meta = {}
149
- return (host.nil? || host == request_host) &&
181
+ [(host.nil? || host == request_host) &&
150
182
  path_match?(request_path, meta) &&
151
- params_match?(request_params) &&
183
+ params_match?(env) &&
152
184
  (body.to_s.size.zero? || request_body == body) &&
153
- headers_match?(request_headers), meta
185
+ headers_match?(request_headers), meta]
154
186
  end
155
187
 
156
188
  def path_match?(request_path, meta)
157
- if path.is_a? Regexp
189
+ if path.is_a?(Regexp)
158
190
  !!(meta[:match_data] = path.match(request_path))
159
191
  else
160
192
  path == request_path
161
193
  end
162
194
  end
163
195
 
164
- def params_match?(request_params)
196
+ # @param env [Faraday::Env]
197
+ def params_match?(env)
198
+ request_params = env[:params]
199
+ params = env.params_encoder.decode(query) || {}
200
+
201
+ if strict_mode
202
+ return Set.new(params) == Set.new(request_params)
203
+ end
204
+
165
205
  params.keys.all? do |key|
166
206
  request_params[key] == params[key]
167
207
  end
168
208
  end
169
209
 
170
210
  def headers_match?(request_headers)
211
+ if strict_mode
212
+ headers_with_user_agent = headers.dup.tap do |hs|
213
+ # NOTE: Set User-Agent in case it's not set when creating Stubs.
214
+ # Users would not want to set Faraday's User-Agent explicitly.
215
+ hs[:user_agent] ||= Connection::USER_AGENT
216
+ end
217
+ return Set.new(headers_with_user_agent) == Set.new(request_headers)
218
+ end
219
+
171
220
  headers.keys.all? do |key|
172
221
  request_headers[key] == headers[key]
173
222
  end
@@ -178,7 +227,7 @@ module Faraday
178
227
  end
179
228
  end
180
229
 
181
- def initialize(app, stubs=nil, &block)
230
+ def initialize(app, stubs = nil, &block)
182
231
  super(app)
183
232
  @stubs = stubs || Stubs.new
184
233
  configure(&block) if block
@@ -188,26 +237,32 @@ module Faraday
188
237
  yield(stubs)
189
238
  end
190
239
 
240
+ # @param env [Faraday::Env]
191
241
  def call(env)
192
242
  super
193
- host = env[:url].host
194
- normalized_path = Faraday::Utils.normalize_path(env[:url])
195
- params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
196
-
197
- stub, meta = stubs.match(env[:method], host, normalized_path, env.request_headers, env[:body])
198
- if stub
199
- env[:params] = (query = env[:url].query) ?
200
- params_encoder.decode(query) : {}
201
- block_arity = stub.block.arity
202
- status, headers, body = (block_arity >= 0) ?
203
- stub.block.call(*[env, meta].take(block_arity)) :
204
- stub.block.call(env, meta)
205
- save_response(env, status, body, headers)
206
- else
207
- raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
243
+
244
+ env.request.params_encoder ||= Faraday::Utils.default_params_encoder
245
+ env[:params] = env.params_encoder.decode(env[:url].query) || {}
246
+ stub, meta = stubs.match(env)
247
+
248
+ unless stub
249
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
250
+ "#{env[:url]} #{env[:body]}"
208
251
  end
252
+
253
+ block_arity = stub.block.arity
254
+ status, headers, body =
255
+ if block_arity >= 0
256
+ stub.block.call(*[env, meta].take(block_arity))
257
+ else
258
+ stub.block.call(env, meta)
259
+ end
260
+ save_response(env, status, body, headers)
261
+
209
262
  @app.call(env)
210
263
  end
211
264
  end
212
265
  end
213
266
  end
267
+
268
+ Faraday::Adapter.register_middleware(test: Faraday::Adapter::Test)
@@ -1,43 +1,60 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Faraday
2
- # Public: This is a base class for all Faraday adapters. Adapters are
4
+ # Base class for all Faraday adapters. Adapters are
3
5
  # responsible for fulfilling a Faraday request.
4
- class Adapter < Middleware
5
- CONTENT_LENGTH = 'Content-Length'.freeze
6
-
7
- register_middleware File.expand_path('../adapter', __FILE__),
8
- :test => [:Test, 'test'],
9
- :net_http => [:NetHttp, 'net_http'],
10
- :net_http_persistent => [:NetHttpPersistent, 'net_http_persistent'],
11
- :typhoeus => [:Typhoeus, 'typhoeus'],
12
- :patron => [:Patron, 'patron'],
13
- :em_synchrony => [:EMSynchrony, 'em_synchrony'],
14
- :em_http => [:EMHttp, 'em_http'],
15
- :excon => [:Excon, 'excon'],
16
- :rack => [:Rack, 'rack'],
17
- :httpclient => [:HTTPClient, 'httpclient']
18
-
19
- # Public: This module marks an Adapter as supporting parallel requests.
6
+ class Adapter
7
+ extend MiddlewareRegistry
8
+
9
+ CONTENT_LENGTH = 'Content-Length'
10
+
11
+ # This module marks an Adapter as supporting parallel requests.
20
12
  module Parallelism
21
13
  attr_writer :supports_parallel
22
- def supports_parallel?() @supports_parallel end
14
+
15
+ def supports_parallel?
16
+ @supports_parallel
17
+ end
23
18
 
24
19
  def inherited(subclass)
25
20
  super
26
- subclass.supports_parallel = self.supports_parallel?
21
+ subclass.supports_parallel = supports_parallel?
27
22
  end
28
23
  end
29
24
 
30
25
  extend Parallelism
31
26
  self.supports_parallel = false
32
27
 
33
- def initialize(app = nil, opts = {}, &block)
34
- super(app)
28
+ def initialize(_app = nil, opts = {}, &block)
29
+ @app = ->(env) { env.response }
35
30
  @connection_options = opts
36
31
  @config_block = block
37
32
  end
38
33
 
34
+ # Yields or returns an adapter's configured connection. Depends on
35
+ # #build_connection being defined on this adapter.
36
+ #
37
+ # @param env [Faraday::Env, Hash] The env object for a faraday request.
38
+ #
39
+ # @return The return value of the given block, or the HTTP connection object
40
+ # if no block is given.
41
+ def connection(env)
42
+ conn = build_connection(env)
43
+ return conn unless block_given?
44
+
45
+ yield conn
46
+ end
47
+
48
+ # Close any persistent connections. The adapter should still be usable
49
+ # after calling close.
50
+ def close
51
+ # Possible implementation:
52
+ # @app.close if @app.respond_to?(:close)
53
+ end
54
+
39
55
  def call(env)
40
56
  env.clear_body if env.needs_body?
57
+ env.response = Response.new
41
58
  end
42
59
 
43
60
  private
@@ -45,11 +62,41 @@ module Faraday
45
62
  def save_response(env, status, body, headers = nil, reason_phrase = nil)
46
63
  env.status = status
47
64
  env.body = body
48
- env.reason_phrase = reason_phrase && reason_phrase.to_s.strip
65
+ env.reason_phrase = reason_phrase&.to_s&.strip
49
66
  env.response_headers = Utils::Headers.new.tap do |response_headers|
50
67
  response_headers.update headers unless headers.nil?
51
68
  yield(response_headers) if block_given?
52
69
  end
70
+
71
+ env.response.finish(env) unless env.parallel?
72
+ env.response
73
+ end
74
+
75
+ # Fetches either a read, write, or open timeout setting. Defaults to the
76
+ # :timeout value if a more specific one is not given.
77
+ #
78
+ # @param type [Symbol] Describes which timeout setting to get: :read,
79
+ # :write, or :open.
80
+ # @param options [Hash] Hash containing Symbol keys like :timeout,
81
+ # :read_timeout, :write_timeout, :open_timeout, or
82
+ # :timeout
83
+ #
84
+ # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
85
+ # has been set.
86
+ def request_timeout(type, options)
87
+ key = TIMEOUT_KEYS.fetch(type) do
88
+ msg = "Expected :read, :write, :open. Got #{type.inspect} :("
89
+ raise ArgumentError, msg
90
+ end
91
+ options[key] || options[:timeout]
53
92
  end
93
+
94
+ TIMEOUT_KEYS = {
95
+ read: :read_timeout,
96
+ open: :open_timeout,
97
+ write: :write_timeout
98
+ }.freeze
54
99
  end
55
100
  end
101
+
102
+ require 'faraday/adapter/test'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monitor'
4
+
5
+ module Faraday
6
+ # AdapterRegistry registers adapter class names so they can be looked up by a
7
+ # String or Symbol name.
8
+ class AdapterRegistry
9
+ def initialize
10
+ @lock = Monitor.new
11
+ @constants = {}
12
+ end
13
+
14
+ def get(name)
15
+ klass = @lock.synchronize do
16
+ @constants[name]
17
+ end
18
+ return klass if klass
19
+
20
+ Object.const_get(name).tap { |c| set(c, name) }
21
+ end
22
+
23
+ def set(klass, name = nil)
24
+ name ||= klass.to_s
25
+ @lock.synchronize do
26
+ @constants[name] = klass
27
+ end
28
+ end
29
+ end
30
+ end