faraday 1.1.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -1
  3. data/LICENSE.md +1 -1
  4. data/README.md +34 -21
  5. data/Rakefile +3 -1
  6. data/examples/client_spec.rb +67 -13
  7. data/examples/client_test.rb +80 -15
  8. data/lib/faraday/adapter/test.rb +117 -52
  9. data/lib/faraday/adapter.rb +5 -20
  10. data/lib/faraday/connection.rb +70 -129
  11. data/lib/faraday/encoders/nested_params_encoder.rb +14 -7
  12. data/lib/faraday/error.rb +29 -8
  13. data/lib/faraday/logging/formatter.rb +28 -15
  14. data/lib/faraday/methods.rb +6 -0
  15. data/lib/faraday/middleware.rb +17 -5
  16. data/lib/faraday/middleware_registry.rb +17 -63
  17. data/lib/faraday/options/connection_options.rb +7 -6
  18. data/lib/faraday/options/env.rb +85 -62
  19. data/lib/faraday/options/proxy_options.rb +11 -3
  20. data/lib/faraday/options/request_options.rb +7 -6
  21. data/lib/faraday/options/ssl_options.rb +56 -45
  22. data/lib/faraday/options.rb +7 -6
  23. data/lib/faraday/rack_builder.rb +23 -21
  24. data/lib/faraday/request/authorization.rb +37 -38
  25. data/lib/faraday/request/instrumentation.rb +5 -1
  26. data/lib/faraday/request/json.rb +70 -0
  27. data/lib/faraday/request/url_encoded.rb +5 -1
  28. data/lib/faraday/request.rb +20 -37
  29. data/lib/faraday/response/json.rb +73 -0
  30. data/lib/faraday/response/logger.rb +8 -4
  31. data/lib/faraday/response/raise_error.rb +33 -6
  32. data/lib/faraday/response.rb +10 -26
  33. data/lib/faraday/utils/headers.rb +7 -2
  34. data/lib/faraday/utils.rb +11 -7
  35. data/lib/faraday/version.rb +5 -0
  36. data/lib/faraday.rb +49 -58
  37. data/spec/faraday/adapter/test_spec.rb +182 -0
  38. data/spec/faraday/connection_spec.rb +207 -90
  39. data/spec/faraday/error_spec.rb +45 -5
  40. data/spec/faraday/middleware_registry_spec.rb +31 -0
  41. data/spec/faraday/middleware_spec.rb +50 -6
  42. data/spec/faraday/options/env_spec.rb +8 -2
  43. data/spec/faraday/options/options_spec.rb +1 -1
  44. data/spec/faraday/options/proxy_options_spec.rb +15 -0
  45. data/spec/faraday/params_encoders/nested_spec.rb +8 -0
  46. data/spec/faraday/rack_builder_spec.rb +26 -54
  47. data/spec/faraday/request/authorization_spec.rb +54 -24
  48. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  49. data/spec/faraday/request/json_spec.rb +199 -0
  50. data/spec/faraday/request/url_encoded_spec.rb +12 -2
  51. data/spec/faraday/request_spec.rb +5 -15
  52. data/spec/faraday/response/json_spec.rb +189 -0
  53. data/spec/faraday/response/logger_spec.rb +38 -0
  54. data/spec/faraday/response/raise_error_spec.rb +77 -5
  55. data/spec/faraday/response_spec.rb +3 -1
  56. data/spec/faraday/utils/headers_spec.rb +22 -4
  57. data/spec/faraday/utils_spec.rb +63 -1
  58. data/spec/faraday_spec.rb +8 -4
  59. data/spec/spec_helper.rb +6 -5
  60. data/spec/support/fake_safe_buffer.rb +1 -1
  61. data/spec/support/helper_methods.rb +0 -37
  62. data/spec/support/shared_examples/adapter.rb +4 -3
  63. data/spec/support/shared_examples/request_method.rb +58 -29
  64. metadata +17 -57
  65. data/lib/faraday/adapter/em_http.rb +0 -286
  66. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
  67. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
  68. data/lib/faraday/adapter/em_synchrony.rb +0 -150
  69. data/lib/faraday/adapter/excon.rb +0 -124
  70. data/lib/faraday/adapter/httpclient.rb +0 -152
  71. data/lib/faraday/adapter/net_http.rb +0 -219
  72. data/lib/faraday/adapter/net_http_persistent.rb +0 -91
  73. data/lib/faraday/adapter/patron.rb +0 -132
  74. data/lib/faraday/adapter/rack.rb +0 -75
  75. data/lib/faraday/adapter/typhoeus.rb +0 -15
  76. data/lib/faraday/autoload.rb +0 -95
  77. data/lib/faraday/dependency_loader.rb +0 -39
  78. data/lib/faraday/file_part.rb +0 -128
  79. data/lib/faraday/param_part.rb +0 -53
  80. data/lib/faraday/request/basic_authentication.rb +0 -20
  81. data/lib/faraday/request/multipart.rb +0 -106
  82. data/lib/faraday/request/retry.rb +0 -239
  83. data/lib/faraday/request/token_authentication.rb +0 -20
  84. data/spec/faraday/adapter/em_http_spec.rb +0 -47
  85. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -16
  86. data/spec/faraday/adapter/excon_spec.rb +0 -49
  87. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  88. data/spec/faraday/adapter/net_http_persistent_spec.rb +0 -57
  89. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  90. data/spec/faraday/adapter/patron_spec.rb +0 -18
  91. data/spec/faraday/adapter/rack_spec.rb +0 -8
  92. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  93. data/spec/faraday/composite_read_io_spec.rb +0 -80
  94. data/spec/faraday/request/multipart_spec.rb +0 -302
  95. data/spec/faraday/request/retry_spec.rb +0 -242
  96. data/spec/faraday/response/middleware_spec.rb +0 -68
  97. data/spec/support/webmock_rack_app.rb +0 -68
@@ -13,24 +13,29 @@ class Client
13
13
  @conn = conn
14
14
  end
15
15
 
16
- def sushi(jname)
17
- res = @conn.get("/#{jname}")
16
+ def httpbingo(jname, params: {})
17
+ res = @conn.get("/#{jname}", params)
18
18
  data = JSON.parse(res.body)
19
- data['name']
19
+ data['origin']
20
+ end
21
+
22
+ def foo(params)
23
+ res = @conn.post('/foo', JSON.dump(params))
24
+ res.status
20
25
  end
21
26
  end
22
27
 
23
28
  # Example API client test
24
29
  class ClientTest < Test::Unit::TestCase
25
- def test_sushi_name
30
+ def test_httpbingo_name
26
31
  stubs = Faraday::Adapter::Test::Stubs.new
27
- stubs.get('/ebi') do |env|
32
+ stubs.get('/api') do |env|
28
33
  # optional: you can inspect the Faraday::Env
29
- assert_equal '/ebi', env.url.path
34
+ assert_equal '/api', env.url.path
30
35
  [
31
36
  200,
32
37
  { 'Content-Type': 'application/javascript' },
33
- '{"name": "shrimp"}'
38
+ '{"origin": "127.0.0.1"}'
34
39
  ]
35
40
  end
36
41
 
@@ -38,13 +43,13 @@ class ClientTest < Test::Unit::TestCase
38
43
  # stubs.get('/unused') { [404, {}, ''] }
39
44
 
40
45
  cli = client(stubs)
41
- assert_equal 'shrimp', cli.sushi('ebi')
46
+ assert_equal '127.0.0.1', cli.httpbingo('api')
42
47
  stubs.verify_stubbed_calls
43
48
  end
44
49
 
45
- def test_sushi_404
50
+ def test_httpbingo_not_found
46
51
  stubs = Faraday::Adapter::Test::Stubs.new
47
- stubs.get('/ebi') do
52
+ stubs.get('/api') do
48
53
  [
49
54
  404,
50
55
  { 'Content-Type': 'application/javascript' },
@@ -53,20 +58,80 @@ class ClientTest < Test::Unit::TestCase
53
58
  end
54
59
 
55
60
  cli = client(stubs)
56
- assert_nil cli.sushi('ebi')
61
+ assert_nil cli.httpbingo('api')
57
62
  stubs.verify_stubbed_calls
58
63
  end
59
64
 
60
- def test_sushi_exception
65
+ def test_httpbingo_exception
61
66
  stubs = Faraday::Adapter::Test::Stubs.new
62
- stubs.get('/ebi') do
63
- raise Faraday::ConnectionFailed, nil
67
+ stubs.get('/api') do
68
+ raise Faraday::ConnectionFailed
64
69
  end
65
70
 
66
71
  cli = client(stubs)
67
72
  assert_raise Faraday::ConnectionFailed do
68
- cli.sushi('ebi')
73
+ cli.httpbingo('api')
74
+ end
75
+ stubs.verify_stubbed_calls
76
+ end
77
+
78
+ def test_strict_mode
79
+ stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
80
+ stubs.get('/api?abc=123') do
81
+ [
82
+ 200,
83
+ { 'Content-Type': 'application/javascript' },
84
+ '{"origin": "127.0.0.1"}'
85
+ ]
69
86
  end
87
+
88
+ cli = client(stubs)
89
+ assert_equal '127.0.0.1', cli.httpbingo('api', params: { abc: 123 })
90
+
91
+ # uncomment to raise Stubs::NotFound
92
+ # assert_equal '127.0.0.1', cli.httpbingo('api', params: { abc: 123, foo: 'Kappa' })
93
+ stubs.verify_stubbed_calls
94
+ end
95
+
96
+ def test_non_default_params_encoder
97
+ stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
98
+ stubs.get('/api?a=x&a=y&a=z') do
99
+ [
100
+ 200,
101
+ { 'Content-Type': 'application/javascript' },
102
+ '{"origin": "127.0.0.1"}'
103
+ ]
104
+ end
105
+ conn = Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
106
+ builder.adapter :test, stubs
107
+ end
108
+
109
+ cli = Client.new(conn)
110
+ assert_equal '127.0.0.1', cli.httpbingo('api', params: { a: %w[x y z] })
111
+
112
+ # uncomment to raise Stubs::NotFound
113
+ # assert_equal '127.0.0.1', cli.httpbingo('api', params: { a: %w[x y] })
114
+ stubs.verify_stubbed_calls
115
+ end
116
+
117
+ def test_with_string_body
118
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
119
+ stub.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
120
+ end
121
+ cli = client(stubs)
122
+ assert_equal 200, cli.foo(name: 'YK')
123
+
124
+ stubs.verify_stubbed_calls
125
+ end
126
+
127
+ def test_with_proc_body
128
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
129
+ check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
130
+ stub.post('/foo', check) { [200, {}, ''] }
131
+ end
132
+ cli = client(stubs)
133
+ assert_equal 200, cli.foo(name: 'YK', created_at: Time.now)
134
+
70
135
  stubs.verify_stubbed_calls
71
136
  end
72
137
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'timeout'
4
+
3
5
  module Faraday
4
6
  class Adapter
5
7
  # @example
@@ -25,6 +27,18 @@ module Faraday
25
27
  # "showing item: #{meta[:match_data][1]}"
26
28
  # ]
27
29
  # end
30
+ #
31
+ # # Test the request body is the same as the stubbed body
32
+ # stub.post('/bar', 'name=YK&word=call') { [200, {}, ''] }
33
+ #
34
+ # # You can pass a proc as a stubbed body and check the request body in your way.
35
+ # # In this case, the proc should return true or false.
36
+ # stub.post('/foo', ->(request_body) do
37
+ # JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }) { [200, {}, '']
38
+ # end
39
+ #
40
+ # # You can set strict_mode to exactly match the stubbed requests.
41
+ # stub.strict_mode = true
28
42
  # end
29
43
  # end
30
44
  #
@@ -39,6 +53,12 @@ module Faraday
39
53
  #
40
54
  # resp = test.get '/items/2'
41
55
  # resp.body # => 'showing item: 2'
56
+ #
57
+ # resp = test.post '/bar', 'name=YK&word=call'
58
+ # resp.status # => 200
59
+ #
60
+ # resp = test.post '/foo', JSON.dump(name: 'YK', created_at: Time.now)
61
+ # resp.status # => 200
42
62
  class Test < Faraday::Adapter
43
63
  attr_accessor :stubs
44
64
 
@@ -47,10 +67,12 @@ module Faraday
47
67
  class NotFound < StandardError
48
68
  end
49
69
 
50
- def initialize
70
+ def initialize(strict_mode: false)
51
71
  # { get: [Stub, Stub] }
52
72
  @stack = {}
53
73
  @consumed = {}
74
+ @strict_mode = strict_mode
75
+ @stubs_mutex = Monitor.new
54
76
  yield(self) if block_given?
55
77
  end
56
78
 
@@ -58,18 +80,23 @@ module Faraday
58
80
  @stack.empty?
59
81
  end
60
82
 
61
- def match(request_method, host, path, headers, body)
83
+ # @param env [Faraday::Env]
84
+ def match(env)
85
+ request_method = env[:method]
62
86
  return false unless @stack.key?(request_method)
63
87
 
64
88
  stack = @stack[request_method]
65
89
  consumed = (@consumed[request_method] ||= [])
66
90
 
67
- stub, meta = matches?(stack, host, path, headers, body)
68
- if stub
69
- consumed << stack.delete(stub)
70
- return stub, meta
91
+ @stubs_mutex.synchronize do
92
+ stub, meta = matches?(stack, env)
93
+ if stub
94
+ removed = stack.delete(stub)
95
+ consumed << removed unless removed.nil?
96
+ return stub, meta
97
+ end
71
98
  end
72
- matches?(consumed, host, path, headers, body)
99
+ matches?(consumed, env)
73
100
  end
74
101
 
75
102
  def get(path, headers = {}, &block)
@@ -115,6 +142,17 @@ module Faraday
115
142
  raise failed_stubs.join(' ') unless failed_stubs.empty?
116
143
  end
117
144
 
145
+ # Set strict_mode. If the value is true, this adapter tries to find matched requests strictly,
146
+ # which means that all of a path, parameters, and headers must be the same as an actual request.
147
+ def strict_mode=(value)
148
+ @strict_mode = value
149
+ @stack.each_value do |stubs|
150
+ stubs.each do |stub|
151
+ stub.strict_mode = value
152
+ end
153
+ end
154
+ end
155
+
118
156
  protected
119
157
 
120
158
  def new_stub(request_method, path, headers = {}, body = nil, &block)
@@ -127,14 +165,18 @@ module Faraday
127
165
  Faraday::Utils.URI(path).host
128
166
  ]
129
167
  end
168
+ path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
169
+ headers = Utils::Headers.new(headers)
130
170
 
131
- stub = Stub.new(host, normalized_path, headers, body, block)
171
+ stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
132
172
  (@stack[request_method] ||= []) << stub
133
173
  end
134
174
 
135
- def matches?(stack, host, path, headers, body)
175
+ # @param stack [Hash]
176
+ # @param env [Faraday::Env]
177
+ def matches?(stack, env)
136
178
  stack.each do |stub|
137
- match_result, meta = stub.matches?(host, path, headers, body)
179
+ match_result, meta = stub.matches?(env)
138
180
  return stub, meta if match_result
139
181
  end
140
182
  nil
@@ -142,36 +184,21 @@ module Faraday
142
184
  end
143
185
 
144
186
  # Stub request
145
- # rubocop:disable Style/StructInheritance
146
- class Stub < Struct.new(:host, :path, :params, :headers, :body, :block)
147
- # rubocop:enable Style/StructInheritance
148
- def initialize(host, full, headers, body, block)
149
- path, query = full.respond_to?(:split) ? full.split('?') : full
150
- params =
151
- if query
152
- Faraday::Utils.parse_nested_query(query)
153
- else
154
- {}
155
- end
187
+ Stub = Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) do
188
+ # @param env [Faraday::Env]
189
+ def matches?(env)
190
+ request_host = env[:url].host
191
+ request_path = Faraday::Utils.normalize_path(env[:url].path)
192
+ request_headers = env.request_headers
193
+ request_body = env[:body]
156
194
 
157
- super(host, path, params, headers, body, block)
158
- end
159
-
160
- def matches?(request_host, request_uri, request_headers, request_body)
161
- request_path, request_query = request_uri.split('?')
162
- request_params =
163
- if request_query
164
- Faraday::Utils.parse_nested_query(request_query)
165
- else
166
- {}
167
- end
168
195
  # meta is a hash used as carrier
169
196
  # that will be yielded to consumer block
170
197
  meta = {}
171
198
  [(host.nil? || host == request_host) &&
172
199
  path_match?(request_path, meta) &&
173
- params_match?(request_params) &&
174
- (body.to_s.size.zero? || request_body == body) &&
200
+ params_match?(env) &&
201
+ body_match?(request_body) &&
175
202
  headers_match?(request_headers), meta]
176
203
  end
177
204
 
@@ -183,18 +210,46 @@ module Faraday
183
210
  end
184
211
  end
185
212
 
186
- def params_match?(request_params)
213
+ # @param env [Faraday::Env]
214
+ def params_match?(env)
215
+ request_params = env[:params]
216
+ params = env.params_encoder.decode(query) || {}
217
+
218
+ if strict_mode
219
+ return Set.new(params) == Set.new(request_params)
220
+ end
221
+
187
222
  params.keys.all? do |key|
188
223
  request_params[key] == params[key]
189
224
  end
190
225
  end
191
226
 
192
227
  def headers_match?(request_headers)
228
+ if strict_mode
229
+ headers_with_user_agent = headers.dup.tap do |hs|
230
+ # NOTE: Set User-Agent in case it's not set when creating Stubs.
231
+ # Users would not want to set Faraday's User-Agent explicitly.
232
+ hs[:user_agent] ||= Connection::USER_AGENT
233
+ end
234
+ return Set.new(headers_with_user_agent) == Set.new(request_headers)
235
+ end
236
+
193
237
  headers.keys.all? do |key|
194
238
  request_headers[key] == headers[key]
195
239
  end
196
240
  end
197
241
 
242
+ def body_match?(request_body)
243
+ return true if body.to_s.empty?
244
+
245
+ case body
246
+ when Proc
247
+ body.call(request_body)
248
+ else
249
+ request_body == body
250
+ end
251
+ end
252
+
198
253
  def to_s
199
254
  "#{path} #{body}"
200
255
  end
@@ -210,37 +265,47 @@ module Faraday
210
265
  yield(stubs)
211
266
  end
212
267
 
268
+ # @param env [Faraday::Env]
213
269
  def call(env)
214
270
  super
215
- host = env[:url].host
216
- normalized_path = Faraday::Utils.normalize_path(env[:url])
217
- params_encoder = env.request.params_encoder ||
218
- Faraday::Utils.default_params_encoder
219
271
 
220
- stub, meta = stubs.match(env[:method], host, normalized_path,
221
- env.request_headers, env[:body])
272
+ env.request.params_encoder ||= Faraday::Utils.default_params_encoder
273
+ env[:params] = env.params_encoder.decode(env[:url].query) || {}
274
+ stub, meta = stubs.match(env)
222
275
 
223
276
  unless stub
224
- raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
225
- "#{normalized_path} #{env[:body]}"
277
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} " \
278
+ "#{env[:url]} #{env[:body]} #{env[:headers]}"
226
279
  end
227
280
 
228
- env[:params] = if (query = env[:url].query)
229
- params_encoder.decode(query)
230
- else
231
- {}
232
- end
233
281
  block_arity = stub.block.arity
282
+ params = if block_arity >= 0
283
+ [env, meta].take(block_arity)
284
+ else
285
+ [env, meta]
286
+ end
287
+
288
+ timeout = request_timeout(:open, env[:request])
289
+ timeout ||= request_timeout(:read, env[:request])
290
+
234
291
  status, headers, body =
235
- if block_arity >= 0
236
- stub.block.call(*[env, meta].take(block_arity))
292
+ if timeout
293
+ ::Timeout.timeout(timeout, Faraday::TimeoutError) do
294
+ stub.block.call(*params)
295
+ end
237
296
  else
238
- stub.block.call(env, meta)
297
+ stub.block.call(*params)
239
298
  end
240
- save_response(env, status, body, headers)
299
+
300
+ # We need to explicitly pass `reason_phrase = nil` here to avoid keyword args conflicts.
301
+ # See https://github.com/lostisland/faraday/issues/1444
302
+ # TODO: remove `nil` explicit reason_phrase once Ruby 3.0 becomes minimum req. version
303
+ save_response(env, status, body, headers, nil)
241
304
 
242
305
  @app.call(env)
243
306
  end
244
307
  end
245
308
  end
246
309
  end
310
+
311
+ Faraday::Adapter.register_middleware(test: Faraday::Adapter::Test)
@@ -5,25 +5,9 @@ module Faraday
5
5
  # responsible for fulfilling a Faraday request.
6
6
  class Adapter
7
7
  extend MiddlewareRegistry
8
- extend DependencyLoader
9
8
 
10
9
  CONTENT_LENGTH = 'Content-Length'
11
10
 
12
- register_middleware File.expand_path('adapter', __dir__),
13
- test: [:Test, 'test'],
14
- net_http: [:NetHttp, 'net_http'],
15
- net_http_persistent: [
16
- :NetHttpPersistent,
17
- 'net_http_persistent'
18
- ],
19
- typhoeus: [:Typhoeus, 'typhoeus'],
20
- patron: [:Patron, 'patron'],
21
- em_synchrony: [:EMSynchrony, 'em_synchrony'],
22
- em_http: [:EMHttp, 'em_http'],
23
- excon: [:Excon, 'excon'],
24
- rack: [:Rack, 'rack'],
25
- httpclient: [:HTTPClient, 'httpclient']
26
-
27
11
  # This module marks an Adapter as supporting parallel requests.
28
12
  module Parallelism
29
13
  attr_writer :supports_parallel
@@ -75,7 +59,7 @@ module Faraday
75
59
 
76
60
  private
77
61
 
78
- def save_response(env, status, body, headers = nil, reason_phrase = nil)
62
+ def save_response(env, status, body, headers = nil, reason_phrase = nil, finished: true)
79
63
  env.status = status
80
64
  env.body = body
81
65
  env.reason_phrase = reason_phrase&.to_s&.strip
@@ -84,7 +68,7 @@ module Faraday
84
68
  yield(response_headers) if block_given?
85
69
  end
86
70
 
87
- env.response.finish(env) unless env.parallel?
71
+ env.response.finish(env) unless env.parallel? || !finished
88
72
  env.response
89
73
  end
90
74
 
@@ -94,8 +78,7 @@ module Faraday
94
78
  # @param type [Symbol] Describes which timeout setting to get: :read,
95
79
  # :write, or :open.
96
80
  # @param options [Hash] Hash containing Symbol keys like :timeout,
97
- # :read_timeout, :write_timeout, :open_timeout, or
98
- # :timeout
81
+ # :read_timeout, :write_timeout, or :open_timeout
99
82
  #
100
83
  # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
101
84
  # has been set.
@@ -114,3 +97,5 @@ module Faraday
114
97
  }.freeze
115
98
  end
116
99
  end
100
+
101
+ require 'faraday/adapter/test'