faraday 2.3.0 → 2.7.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51764b6108e230dfff244e6e6e9f275f92f5fc1bcd1bf8e43d36058d35442e14
4
- data.tar.gz: 2cdf2edfd3e3c0386f501c0c2609db4f5c879a2a1807869a7b7e690e4a22a77a
3
+ metadata.gz: acdabcdd0a13d304292f17cd6764fba01d12f552cf78bdae3006ee38a3921ad0
4
+ data.tar.gz: 13300a1971d6557a4c62f01c4b0c96d1aecb4372b4c8a1ba734022109d9f2c0b
5
5
  SHA512:
6
- metadata.gz: 94ce1d0df0639d8e7a8472085331796a787236989e07943a47e463f6d5c68d83bf180b55636e87d37ac654400fe11660ee10898c9b43122c29a211310682b621
7
- data.tar.gz: 14af6c0c348cf3ca8ae7f7f8a95b3eb8b73b8a444d90a75c7eeb46d9edd4c840fa44dd2eda4e76653fd4924e6b20d13f32236e21bd303f90b589df1abc88da3d
6
+ metadata.gz: 0151023e942ae76b69a6bb28e70c792501333bc4f67c670bb990e80917c6b4d63aaa9ee168bd333abff2dd2b1fb54f244568e7a54d5346034d311924b17eabfe
7
+ data.tar.gz: 9598f16ed0c19cdfe01be90a1a29e6751b6848d44eac1d481224fbe576a33ff73f631a2bd398f70b5534d4126f8dba9ced68bb17dad5ea8efe93214bf7ce4e1b
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2020 Rick Olson, Zack Hobson
1
+ Copyright (c) 2009-2023 Rick Olson, Zack Hobson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
  [![GitHub Actions CI](https://github.com/lostisland/faraday/workflows/CI/badge.svg)](https://github.com/lostisland/faraday/actions?query=workflow%3ACI)
5
5
  [![GitHub Discussions](https://img.shields.io/github/discussions/lostisland/faraday?logo=github)](https://github.com/lostisland/faraday/discussions)
6
6
 
7
-
8
7
  Faraday is an HTTP client library abstraction layer that provides a common interface over many
9
8
  adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
10
9
  You probably don't want to use Faraday directly in your project, as it will lack an actual client library to perform
@@ -42,14 +41,15 @@ Open the issues page and check for the `help wanted` label!
42
41
  But before you start coding, please read our [Contributing Guide][contributing]
43
42
 
44
43
  ## Copyright
45
- © 2009 - 2021, the [Faraday Team][faraday_team]. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
46
44
 
47
- [awesome]: https://github.com/lostisland/awesome-faraday/#adapters
48
- [website]: https://lostisland.github.io/faraday
45
+ © 2009 - 2023, the [Faraday Team][faraday_team]. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
46
+
47
+ [awesome]: https://github.com/lostisland/awesome-faraday/#adapters
48
+ [website]: https://lostisland.github.io/faraday
49
49
  [faraday_team]: https://lostisland.github.io/faraday/team
50
50
  [contributing]: https://github.com/lostisland/faraday/blob/master/.github/CONTRIBUTING.md
51
- [apidoc]: https://www.rubydoc.info/github/lostisland/faraday
52
- [actions]: https://github.com/lostisland/faraday/actions
53
- [jruby]: http://jruby.org/
54
- [rubinius]: http://rubini.us/
55
- [license]: LICENSE.md
51
+ [apidoc]: https://www.rubydoc.info/github/lostisland/faraday
52
+ [actions]: https://github.com/lostisland/faraday/actions
53
+ [jruby]: http://jruby.org/
54
+ [rubinius]: http://rubini.us/
55
+ [license]: LICENSE.md
@@ -17,6 +17,11 @@ class Client
17
17
  data = JSON.parse(res.body)
18
18
  data['origin']
19
19
  end
20
+
21
+ def foo(params)
22
+ res = @conn.post('/foo', JSON.dump(params))
23
+ res.status
24
+ end
20
25
  end
21
26
 
22
27
  RSpec.describe Client do
@@ -94,4 +99,21 @@ RSpec.describe Client do
94
99
  stubs.verify_stubbed_calls
95
100
  end
96
101
  end
102
+
103
+ context 'When you want to test the body, you can use a proc as well as string' do
104
+ it 'tests with a string' do
105
+ stubs.post('/foo', '{"name":"YK"}') { [200, {}, ''] }
106
+
107
+ expect(client.foo(name: 'YK')).to eq 200
108
+ stubs.verify_stubbed_calls
109
+ end
110
+
111
+ it 'tests with a proc' do
112
+ check = ->(request_body) { JSON.parse(request_body).slice('name') == { 'name' => 'YK' } }
113
+ stubs.post('/foo', check) { [200, {}, ''] }
114
+
115
+ expect(client.foo(name: 'YK', created_at: Time.now)).to eq 200
116
+ stubs.verify_stubbed_calls
117
+ end
118
+ end
97
119
  end
@@ -18,6 +18,11 @@ class Client
18
18
  data = JSON.parse(res.body)
19
19
  data['origin']
20
20
  end
21
+
22
+ def foo(params)
23
+ res = @conn.post('/foo', JSON.dump(params))
24
+ res.status
25
+ end
21
26
  end
22
27
 
23
28
  # Example API client test
@@ -109,6 +114,27 @@ class ClientTest < Test::Unit::TestCase
109
114
  stubs.verify_stubbed_calls
110
115
  end
111
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
+
135
+ stubs.verify_stubbed_calls
136
+ end
137
+
112
138
  def client(stubs)
113
139
  conn = Faraday.new do |builder|
114
140
  builder.adapter :test, stubs
@@ -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
@@ -26,6 +28,15 @@ module Faraday
26
28
  # ]
27
29
  # end
28
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
+ #
29
40
  # # You can set strict_mode to exactly match the stubbed requests.
30
41
  # stub.strict_mode = true
31
42
  # end
@@ -42,6 +53,12 @@ module Faraday
42
53
  #
43
54
  # resp = test.get '/items/2'
44
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
45
62
  class Test < Faraday::Adapter
46
63
  attr_accessor :stubs
47
64
 
@@ -181,7 +198,7 @@ module Faraday
181
198
  [(host.nil? || host == request_host) &&
182
199
  path_match?(request_path, meta) &&
183
200
  params_match?(env) &&
184
- (body.to_s.size.zero? || request_body == body) &&
201
+ body_match?(request_body) &&
185
202
  headers_match?(request_headers), meta]
186
203
  end
187
204
 
@@ -222,6 +239,17 @@ module Faraday
222
239
  end
223
240
  end
224
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
+
225
253
  def to_s
226
254
  "#{path} #{body}"
227
255
  end
@@ -246,18 +274,33 @@ module Faraday
246
274
  stub, meta = stubs.match(env)
247
275
 
248
276
  unless stub
249
- raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
277
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} " \
250
278
  "#{env[:url]} #{env[:body]}"
251
279
  end
252
280
 
253
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
+
254
291
  status, headers, body =
255
- if block_arity >= 0
256
- 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
257
296
  else
258
- stub.block.call(env, meta)
297
+ stub.block.call(*params)
259
298
  end
260
- 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)
261
304
 
262
305
  @app.call(env)
263
306
  end
@@ -59,7 +59,7 @@ module Faraday
59
59
 
60
60
  private
61
61
 
62
- 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)
63
63
  env.status = status
64
64
  env.body = body
65
65
  env.reason_phrase = reason_phrase&.to_s&.strip
@@ -68,7 +68,7 @@ module Faraday
68
68
  yield(response_headers) if block_given?
69
69
  end
70
70
 
71
- env.response.finish(env) unless env.parallel?
71
+ env.response.finish(env) unless env.parallel? || !finished
72
72
  env.response
73
73
  end
74
74
 
@@ -78,8 +78,7 @@ module Faraday
78
78
  # @param type [Symbol] Describes which timeout setting to get: :read,
79
79
  # :write, or :open.
80
80
  # @param options [Hash] Hash containing Symbol keys like :timeout,
81
- # :read_timeout, :write_timeout, :open_timeout, or
82
- # :timeout
81
+ # :read_timeout, :write_timeout, or :open_timeout
83
82
  #
84
83
  # @return [Integer, nil] Timeout duration in seconds, or nil if no timeout
85
84
  # has been set.
@@ -130,10 +130,10 @@ module Faraday
130
130
  # Makes a GET HTTP request without a body.
131
131
  # @!scope class
132
132
  #
133
- # @param url [String] The optional String base URL to use as a prefix for
133
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
134
134
  # all requests. Can also be the options Hash.
135
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
136
- # @param headers [Hash] unencoded HTTP header key/value pairs.
135
+ # @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
136
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
137
137
  #
138
138
  # @example
139
139
  # conn.get '/items', { page: 1 }, :accept => 'application/json'
@@ -152,10 +152,10 @@ module Faraday
152
152
  # Makes a HEAD HTTP request without a body.
153
153
  # @!scope class
154
154
  #
155
- # @param url [String] The optional String base URL to use as a prefix for
155
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
156
156
  # all requests. Can also be the options Hash.
157
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
158
- # @param headers [Hash] unencoded HTTP header key/value pairs.
157
+ # @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
158
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
159
159
  #
160
160
  # @example
161
161
  # conn.head '/items/1'
@@ -167,10 +167,10 @@ module Faraday
167
167
  # Makes a DELETE HTTP request without a body.
168
168
  # @!scope class
169
169
  #
170
- # @param url [String] The optional String base URL to use as a prefix for
170
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
171
171
  # all requests. Can also be the options Hash.
172
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
173
- # @param headers [Hash] unencoded HTTP header key/value pairs.
172
+ # @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
173
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
174
174
  #
175
175
  # @example
176
176
  # conn.delete '/items/1'
@@ -182,10 +182,10 @@ module Faraday
182
182
  # Makes a TRACE HTTP request without a body.
183
183
  # @!scope class
184
184
  #
185
- # @param url [String] The optional String base URL to use as a prefix for
185
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
186
186
  # all requests. Can also be the options Hash.
187
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
188
- # @param headers [Hash] unencoded HTTP header key/value pairs.
187
+ # @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
188
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
189
189
  #
190
190
  # @example
191
191
  # conn.connect '/items/1'
@@ -210,9 +210,9 @@ module Faraday
210
210
  #
211
211
  # @overload options(url, params = nil, headers = nil)
212
212
  # Makes an OPTIONS HTTP request to the given URL.
213
- # @param url [String] String base URL to sue as a prefix for all requests.
214
- # @param params [Hash] Hash of URI query unencoded key/value pairs.
215
- # @param headers [Hash] unencoded HTTP header key/value pairs.
213
+ # @param url [String, URI, nil] String base URL to sue as a prefix for all requests.
214
+ # @param params [Hash, nil] Hash of URI query unencoded key/value pairs.
215
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
216
216
  #
217
217
  # @example
218
218
  # conn.options '/items/1'
@@ -220,7 +220,7 @@ module Faraday
220
220
  # @yield [Faraday::Request] for further request customizations
221
221
  # @return [Faraday::Response]
222
222
  def options(*args)
223
- return @options if args.size.zero?
223
+ return @options if args.empty?
224
224
 
225
225
  url, params, headers = *args
226
226
  run_request(:options, url, nil, headers) do |request|
@@ -233,10 +233,10 @@ module Faraday
233
233
  # Makes a POST HTTP request with a body.
234
234
  # @!scope class
235
235
  #
236
- # @param url [String] The optional String base URL to use as a prefix for
236
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
237
237
  # all requests. Can also be the options Hash.
238
- # @param body [String] body for the request.
239
- # @param headers [Hash] unencoded HTTP header key/value pairs.
238
+ # @param body [String, nil] body for the request.
239
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
240
240
  #
241
241
  # @example
242
242
  # conn.post '/items', data, content_type: 'application/json'
@@ -255,20 +255,19 @@ module Faraday
255
255
  # Makes a PUT HTTP request with a body.
256
256
  # @!scope class
257
257
  #
258
- # @param url [String] The optional String base URL to use as a prefix for
258
+ # @param url [String, URI, nil] The optional String base URL to use as a prefix for
259
259
  # all requests. Can also be the options Hash.
260
- # @param body [String] body for the request.
261
- # @param headers [Hash] unencoded HTTP header key/value pairs.
260
+ # @param body [String, nil] body for the request.
261
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
262
262
  #
263
263
  # @example
264
- # # TODO: Make it a PUT example
265
- # conn.post '/items', data, content_type: 'application/json'
264
+ # conn.put '/products/123', data, content_type: 'application/json'
266
265
  #
267
- # # Simple ElasticSearch indexing sample.
268
- # conn.post '/twitter/tweet' do |req|
269
- # req.headers[:content_type] = 'application/json'
270
- # req.params[:routing] = 'kimchy'
271
- # req.body = JSON.generate(user: 'kimchy', ...)
266
+ # # Star a gist.
267
+ # conn.put 'https://api.github.com/gists/GIST_ID/star' do |req|
268
+ # req.headers['Accept'] = 'application/vnd.github+json'
269
+ # req.headers['Authorization'] = 'Bearer <YOUR-TOKEN>'
270
+ # req.headers['X-GitHub-Api-Version'] = '2022-11-28'
272
271
  # end
273
272
  #
274
273
  # @yield [Faraday::Request] for further request customizations
@@ -390,7 +389,7 @@ module Faraday
390
389
  # Takes a relative url for a request and combines it with the defaults
391
390
  # set on the connection instance.
392
391
  #
393
- # @param url [String]
392
+ # @param url [String, URI, nil]
394
393
  # @param extra_params [Hash]
395
394
  #
396
395
  # @example
@@ -423,10 +422,10 @@ module Faraday
423
422
  # Builds and runs the Faraday::Request.
424
423
  #
425
424
  # @param method [Symbol] HTTP method.
426
- # @param url [String, URI] String or URI to access.
427
- # @param body [Object] The request body that will eventually be converted to
425
+ # @param url [String, URI, nil] String or URI to access.
426
+ # @param body [String, nil] The request body that will eventually be converted to
428
427
  # a string.
429
- # @param headers [Hash] unencoded HTTP header key/value pairs.
428
+ # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
430
429
  #
431
430
  # @return [Faraday::Response]
432
431
  def run_request(method, url, body, headers)
@@ -462,7 +461,7 @@ module Faraday
462
461
 
463
462
  # Build an absolute URL based on url_prefix.
464
463
  #
465
- # @param url [String, URI]
464
+ # @param url [String, URI, nil]
466
465
  # @param params [Faraday::Utils::ParamsHash] A Faraday::Utils::ParamsHash to
467
466
  # replace the query values
468
467
  # of the resulting url (default: nil).
@@ -471,10 +470,10 @@ module Faraday
471
470
  def build_exclusive_url(url = nil, params = nil, params_encoder = nil)
472
471
  url = nil if url.respond_to?(:empty?) && url.empty?
473
472
  base = url_prefix.dup
474
- if url && base.path && base.path !~ %r{/$}
473
+ if url && !base.path.end_with?('/')
475
474
  base.path = "#{base.path}/" # ensure trailing slash
476
475
  end
477
- url = url.to_s.gsub(':', '%3A') if url && URI.parse(url.to_s).opaque
476
+ url = url.to_s.gsub(':', '%3A') if URI.parse(url.to_s).opaque
478
477
  uri = url ? base + url : base
479
478
  if params
480
479
  uri.query = params.to_query(params_encoder || options.params_encoder)
@@ -537,7 +536,7 @@ module Faraday
537
536
  end
538
537
 
539
538
  def find_default_proxy
540
- uri = ENV['http_proxy']
539
+ uri = ENV.fetch('http_proxy', nil)
541
540
  return unless uri && !uri.empty?
542
541
 
543
542
  uri = "http://#{uri}" unless uri.match?(/^http/i)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pp'
3
+ require 'pp' # rubocop:disable Lint/RedundantRequireStatement
4
4
 
5
5
  module Faraday
6
6
  module Logging
@@ -8,7 +8,7 @@ module Faraday
8
8
  class Formatter
9
9
  extend Forwardable
10
10
 
11
- DEFAULT_OPTIONS = { headers: true, bodies: false,
11
+ DEFAULT_OPTIONS = { headers: true, bodies: false, errors: false,
12
12
  log_level: :info }.freeze
13
13
 
14
14
  def initialize(logger:, options:)
@@ -37,6 +37,18 @@ module Faraday
37
37
  log_body('response', env[:body]) if env[:body] && log_body?(:response)
38
38
  end
39
39
 
40
+ def exception(exc)
41
+ return unless log_errors?
42
+
43
+ error_log = proc { exc.full_message }
44
+ public_send(log_level, 'error', &error_log)
45
+
46
+ log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
47
+ return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
48
+
49
+ log_body('error', exc.response_body)
50
+ end
51
+
40
52
  def filter(filter_word, filter_replacement)
41
53
  @filter.push([filter_word, filter_replacement])
42
54
  end
@@ -77,6 +89,10 @@ module Faraday
77
89
  end
78
90
  end
79
91
 
92
+ def log_errors?
93
+ @options[:errors]
94
+ end
95
+
80
96
  def apply_filters(output)
81
97
  @filter.each do |pattern, replacement|
82
98
  output = output.to_s.gsub(pattern, replacement)
@@ -17,6 +17,9 @@ module Faraday
17
17
  app.call(env).on_complete do |environment|
18
18
  on_complete(environment) if respond_to?(:on_complete)
19
19
  end
20
+ rescue StandardError => e
21
+ on_error(e) if respond_to?(:on_error)
22
+ raise
20
23
  end
21
24
 
22
25
  def close
@@ -15,13 +15,19 @@ module Faraday
15
15
  # @return [Hash] options for configuring the request.
16
16
  # Options for configuring the request.
17
17
  #
18
- # - `:timeout` open/read timeout Integer in seconds
19
- # - `:open_timeout` - read timeout Integer in seconds
20
- # - `:on_data` - Proc for streaming
21
- # - `:proxy` - Hash of proxy options
22
- # - `:uri` - Proxy Server URI
23
- # - `:user` - Proxy server username
24
- # - `:password` - Proxy server password
18
+ # - `:timeout` - time limit for the entire request (Integer in
19
+ # seconds)
20
+ # - `:open_timeout` - time limit for just the connection phase (e.g.
21
+ # handshake) (Integer in seconds)
22
+ # - `:read_timeout` - time limit for the first response byte received from
23
+ # the server (Integer in seconds)
24
+ # - `:write_timeout` - time limit for the client to send the request to the
25
+ # server (Integer in seconds)
26
+ # - `:on_data` - Proc for streaming
27
+ # - `:proxy` - Hash of proxy options
28
+ # - `:uri` - Proxy server URI
29
+ # - `:user` - Proxy server username
30
+ # - `:password` - Proxy server password
25
31
  #
26
32
  # @!attribute request_headers
27
33
  # @return [Hash] HTTP Headers to be sent to the server.
@@ -157,6 +163,24 @@ module Faraday
157
163
  %(#<#{self.class}#{attrs.join(' ')}>)
158
164
  end
159
165
 
166
+ def stream_response?
167
+ request.stream_response?
168
+ end
169
+
170
+ def stream_response(&block)
171
+ size = 0
172
+ yielded = false
173
+ block_result = block.call do |chunk| # rubocop:disable Performance/RedundantBlockCall
174
+ if chunk.bytesize.positive? || size.positive?
175
+ yielded = true
176
+ size += chunk.bytesize
177
+ request.on_data.call(chunk, size, self)
178
+ end
179
+ end
180
+ request.on_data.call(+'', 0, self) unless yielded
181
+ block_result
182
+ end
183
+
160
184
  # @private
161
185
  def custom_members
162
186
  @custom_members ||= {}
@@ -6,6 +6,10 @@ module Faraday
6
6
  # @!attribute verify
7
7
  # @return [Boolean] whether to verify SSL certificates or not
8
8
  #
9
+ # @!attribute verify_hostname
10
+ # @return [Boolean] whether to enable hostname verification on server certificates
11
+ # during the handshake or not (see https://github.com/ruby/openssl/pull/60)
12
+ #
9
13
  # @!attribute ca_file
10
14
  # @return [String] CA file
11
15
  #
@@ -41,7 +45,8 @@ module Faraday
41
45
  #
42
46
  # @!attribute max_version
43
47
  # @return [String, Symbol] maximum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-max_version-3D)
44
- class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
48
+ class SSLOptions < Options.new(:verify, :verify_hostname,
49
+ :ca_file, :ca_path, :verify_mode,
45
50
  :cert_store, :client_cert, :client_key,
46
51
  :certificate, :private_key, :verify_depth,
47
52
  :version, :min_version, :max_version)
@@ -55,5 +60,10 @@ module Faraday
55
60
  def disable?
56
61
  !verify?
57
62
  end
63
+
64
+ # @return [Boolean] true if should verify_hostname
65
+ def verify_hostname?
66
+ verify_hostname != false
67
+ end
58
68
  end
59
69
  end
@@ -23,22 +23,27 @@ module Faraday
23
23
  def on_request(env)
24
24
  return if env.request_headers[KEY]
25
25
 
26
- env.request_headers[KEY] = header_from(@type, *@params)
26
+ env.request_headers[KEY] = header_from(@type, env, *@params)
27
27
  end
28
28
 
29
29
  private
30
30
 
31
31
  # @param type [String, Symbol]
32
+ # @param env [Faraday::Env]
32
33
  # @param params [Array]
33
34
  # @return [String] a header value
34
- def header_from(type, *params)
35
+ def header_from(type, env, *params)
35
36
  if type.to_s.casecmp('basic').zero? && params.size == 2
36
37
  Utils.basic_header_from(*params)
37
38
  elsif params.size != 1
38
39
  raise ArgumentError, "Unexpected params received (got #{params.size} instead of 1)"
39
40
  else
40
41
  value = params.first
41
- value = value.call if value.is_a?(Proc) || value.respond_to?(:call)
42
+ if (value.is_a?(Proc) && value.arity == 1) || (value.respond_to?(:call) && value.method(:call).arity == 1)
43
+ value = value.call(env)
44
+ elsif value.is_a?(Proc) || value.respond_to?(:call)
45
+ value = value.call
46
+ end
42
47
  "#{type} #{value}"
43
48
  end
44
49
  end
@@ -21,7 +21,7 @@ module Faraday
21
21
  # @!attribute headers
22
22
  # @return [Faraday::Utils::Headers] headers
23
23
  # @!attribute body
24
- # @return [Hash] body
24
+ # @return [String] body
25
25
  # @!attribute options
26
26
  # @return [RequestOptions] options
27
27
  #
@@ -26,6 +26,10 @@ module Faraday
26
26
  def on_complete(env)
27
27
  @formatter.response(env)
28
28
  end
29
+
30
+ def on_error(exc)
31
+ @formatter.exception(exc) if @formatter.respond_to?(:exception)
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -61,7 +61,8 @@ module Faraday
61
61
  def to_hash
62
62
  {
63
63
  status: env.status, body: env.body,
64
- response_headers: env.response_headers
64
+ response_headers: env.response_headers,
65
+ url: env.url
65
66
  }
66
67
  end
67
68
 
@@ -132,7 +132,12 @@ module Faraday
132
132
 
133
133
  # Join multiple values with a comma.
134
134
  def add_parsed(key, value)
135
- self[key] ? self[key] << ', ' << value : self[key] = value
135
+ if key?(key)
136
+ self[key] = self[key].to_s
137
+ self[key] << ', ' << value
138
+ else
139
+ self[key] = value
140
+ end
136
141
  end
137
142
  end
138
143
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.3.0'
4
+ VERSION = '2.7.4'
5
5
  end
@@ -373,5 +373,70 @@ RSpec.describe Faraday::Adapter::Test do
373
373
  it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent' }
374
374
  it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent', x_special: 'special' }
375
375
  end
376
+
377
+ describe 'body_match?' do
378
+ let(:stubs) do
379
+ described_class::Stubs.new do |stubs|
380
+ stubs.post('/no_check') { [200, {}, 'ok'] }
381
+ stubs.post('/with_string', 'abc') { [200, {}, 'ok'] }
382
+ stubs.post(
383
+ '/with_proc',
384
+ ->(request_body) { JSON.parse(request_body, symbolize_names: true) == { x: '!', a: [{ m: [{ a: true }], n: 123 }] } },
385
+ { content_type: 'application/json' }
386
+ ) do
387
+ [200, {}, 'ok']
388
+ end
389
+ end
390
+ end
391
+
392
+ context 'when trying without any args for body' do
393
+ subject(:without_body) { connection.post('/no_check') }
394
+
395
+ it { expect(without_body.status).to eq 200 }
396
+ end
397
+
398
+ context 'when trying with string body stubs' do
399
+ subject(:with_string) { connection.post('/with_string', 'abc') }
400
+
401
+ it { expect(with_string.status).to eq 200 }
402
+ end
403
+
404
+ context 'when trying with proc body stubs' do
405
+ subject(:with_proc) do
406
+ connection.post('/with_proc', JSON.dump(a: [{ n: 123, m: [{ a: true }] }], x: '!'), { 'Content-Type' => 'application/json' })
407
+ end
408
+
409
+ it { expect(with_proc.status).to eq 200 }
410
+ end
411
+ end
412
+ end
413
+
414
+ describe 'request timeout' do
415
+ subject(:request) do
416
+ connection.get('/sleep') do |req|
417
+ req.options.timeout = timeout
418
+ end
419
+ end
420
+
421
+ before do
422
+ stubs.get('/sleep') do
423
+ sleep(0.01)
424
+ [200, {}, '']
425
+ end
426
+ end
427
+
428
+ context 'when request is within timeout' do
429
+ let(:timeout) { 1 }
430
+
431
+ it { expect(request.status).to eq 200 }
432
+ end
433
+
434
+ context 'when request is too slow' do
435
+ let(:timeout) { 0.001 }
436
+
437
+ it 'raises an exception' do
438
+ expect { request }.to raise_error(Faraday::TimeoutError)
439
+ end
440
+ end
376
441
  end
377
442
  end
@@ -6,7 +6,7 @@ class CustomEncoder
6
6
  end
7
7
 
8
8
  def decode(params)
9
- params.split(',').map { |pair| pair.split('-') }.to_h
9
+ params.split(',').to_h { |pair| pair.split('-') }
10
10
  end
11
11
  end
12
12
 
@@ -131,6 +131,12 @@ RSpec.describe Faraday::Connection do
131
131
  it { expect(subject.ssl.verify?).to be_falsey }
132
132
  end
133
133
 
134
+ context 'with verify_hostname false' do
135
+ let(:options) { { ssl: { verify_hostname: false } } }
136
+
137
+ it { expect(subject.ssl.verify_hostname?).to be_falsey }
138
+ end
139
+
134
140
  context 'with empty block' do
135
141
  let(:conn) { Faraday::Connection.new {} }
136
142
 
@@ -304,6 +310,21 @@ RSpec.describe Faraday::Connection do
304
310
  expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
305
311
  end
306
312
  end
313
+
314
+ context 'with a custom `default_uri_parser`' do
315
+ let(:url) { 'http://httpbingo.org' }
316
+ let(:parser) { Addressable::URI }
317
+
318
+ around do |example|
319
+ with_default_uri_parser(parser) do
320
+ example.run
321
+ end
322
+ end
323
+
324
+ it 'does not raise error' do
325
+ expect { conn.build_exclusive_url('/nigiri') }.not_to raise_error
326
+ end
327
+ end
307
328
  end
308
329
 
309
330
  describe '#build_url' do
@@ -505,7 +526,7 @@ RSpec.describe Faraday::Connection do
505
526
  it 'uses env http_proxy' do
506
527
  with_env 'http_proxy' => 'http://proxy.com' do
507
528
  conn = Faraday.new
508
- expect(conn.instance_variable_get('@manual_proxy')).to be_falsey
529
+ expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
509
530
  expect(conn.proxy_for_request('http://google.co.uk').host).to eq('proxy.com')
510
531
  end
511
532
  end
@@ -513,7 +534,7 @@ RSpec.describe Faraday::Connection do
513
534
  it 'uses processes no_proxy before http_proxy' do
514
535
  with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'google.co.uk' do
515
536
  conn = Faraday.new
516
- expect(conn.instance_variable_get('@manual_proxy')).to be_falsey
537
+ expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
517
538
  expect(conn.proxy_for_request('http://google.co.uk')).to be_nil
518
539
  end
519
540
  end
@@ -521,7 +542,7 @@ RSpec.describe Faraday::Connection do
521
542
  it 'uses env https_proxy' do
522
543
  with_env 'https_proxy' => 'https://proxy.com' do
523
544
  conn = Faraday.new
524
- expect(conn.instance_variable_get('@manual_proxy')).to be_falsey
545
+ expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
525
546
  expect(conn.proxy_for_request('https://google.co.uk').host).to eq('proxy.com')
526
547
  end
527
548
  end
@@ -529,7 +550,7 @@ RSpec.describe Faraday::Connection do
529
550
  it 'uses processes no_proxy before https_proxy' do
530
551
  with_env 'https_proxy' => 'https://proxy.com', 'no_proxy' => 'google.co.uk' do
531
552
  conn = Faraday.new
532
- expect(conn.instance_variable_get('@manual_proxy')).to be_falsey
553
+ expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
533
554
  expect(conn.proxy_for_request('https://google.co.uk')).to be_nil
534
555
  end
535
556
  end
@@ -539,7 +560,7 @@ RSpec.describe Faraday::Connection do
539
560
  conn = Faraday.new
540
561
  conn.proxy = 'http://proxy2.com'
541
562
 
542
- expect(conn.instance_variable_get('@manual_proxy')).to be_truthy
563
+ expect(conn.instance_variable_get(:@manual_proxy)).to be_truthy
543
564
  expect(conn.proxy_for_request('https://google.co.uk').host).to eq('proxy2.com')
544
565
  end
545
566
  end
@@ -574,7 +595,7 @@ RSpec.describe Faraday::Connection do
574
595
  end
575
596
 
576
597
  conn.get(url)
577
- expect(conn.instance_variable_get('@temp_proxy')).to be_nil
598
+ expect(conn.instance_variable_get(:@temp_proxy)).to be_nil
578
599
  end
579
600
 
580
601
  it 'dynamically check no proxy' do
@@ -33,6 +33,24 @@ RSpec.describe Faraday::Middleware do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#on_error' do
37
+ subject do
38
+ Class.new(described_class) do
39
+ def on_error(error)
40
+ # do nothing
41
+ end
42
+ end.new(app)
43
+ end
44
+
45
+ it 'is called by #call' do
46
+ expect(app).to receive(:call).and_raise(Faraday::ConnectionFailed)
47
+ is_expected.to receive(:call).and_call_original
48
+ is_expected.to receive(:on_error)
49
+
50
+ expect { subject.call(double) }.to raise_error(Faraday::ConnectionFailed)
51
+ end
52
+ end
53
+
36
54
  describe '#close' do
37
55
  context "with app that doesn't support \#close" do
38
56
  it 'should issue warning' do
@@ -27,6 +27,12 @@ RSpec.describe Faraday::Env do
27
27
  expect(ssl.fetch(:verify, true)).to be_falsey
28
28
  end
29
29
 
30
+ it 'handle verify_hostname when fetching' do
31
+ ssl = Faraday::SSLOptions.new
32
+ ssl.verify_hostname = true
33
+ expect(ssl.fetch(:verify_hostname, false)).to be_truthy
34
+ end
35
+
30
36
  it 'retains custom members' do
31
37
  env[:foo] = 'custom 1'
32
38
  env[:bar] = :custom2
@@ -72,6 +72,41 @@ RSpec.describe Faraday::Request::Authorization do
72
72
  include_examples 'does not interfere with existing authentication'
73
73
  end
74
74
 
75
+ context 'with an argument' do
76
+ let(:response) { conn.get('/auth-echo', nil, 'middle' => 'crunchy surprise') }
77
+
78
+ context 'when passed a proc' do
79
+ let(:auth_config) { [proc { |env| "proc #{env.request_headers['middle']}" }] }
80
+
81
+ it { expect(response.body).to eq('Bearer proc crunchy surprise') }
82
+
83
+ include_examples 'does not interfere with existing authentication'
84
+ end
85
+
86
+ context 'when passed a lambda' do
87
+ let(:auth_config) { [->(env) { "lambda #{env.request_headers['middle']}" }] }
88
+
89
+ it { expect(response.body).to eq('Bearer lambda crunchy surprise') }
90
+
91
+ include_examples 'does not interfere with existing authentication'
92
+ end
93
+
94
+ context 'when passed a callable with an argument' do
95
+ let(:callable) do
96
+ Class.new do
97
+ def call(env)
98
+ "callable #{env.request_headers['middle']}"
99
+ end
100
+ end.new
101
+ end
102
+ let(:auth_config) { [callable] }
103
+
104
+ it { expect(response.body).to eq('Bearer callable crunchy surprise') }
105
+
106
+ include_examples 'does not interfere with existing authentication'
107
+ end
108
+ end
109
+
75
110
  context 'when passed too many arguments' do
76
111
  let(:auth_config) { %w[baz foo] }
77
112
 
@@ -14,6 +14,7 @@ RSpec.describe Faraday::Request do
14
14
  context 'when nothing particular is configured' do
15
15
  it { expect(subject.http_method).to eq(:get) }
16
16
  it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
17
+ it { expect(subject.to_env(conn).ssl.verify_hostname).to be_falsey }
17
18
  end
18
19
 
19
20
  context 'when HTTP method is post' do
@@ -64,6 +64,15 @@ RSpec.describe Faraday::Response::Logger do
64
64
  expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65
65
  conn.get '/hello'
66
66
  end
67
+
68
+ context 'when no route' do
69
+ it 'delegates logging to the formatter' do
70
+ expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
71
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
72
+
73
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
74
+ end
75
+ end
67
76
  end
68
77
 
69
78
  context 'with custom formatter' do
@@ -94,6 +103,16 @@ RSpec.describe Faraday::Response::Logger do
94
103
  expect(string_io.string).to match('GET http:/hello')
95
104
  end
96
105
 
106
+ it 'logs status' do
107
+ conn.get '/hello', nil, accept: 'text/html'
108
+ expect(string_io.string).to match('Status 200')
109
+ end
110
+
111
+ it 'does not log error message by default' do
112
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
113
+ expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
114
+ end
115
+
97
116
  it 'logs request headers by default' do
98
117
  conn.get '/hello', nil, accept: 'text/html'
99
118
  expect(string_io.string).to match(%(Accept: "text/html))
@@ -188,6 +207,15 @@ RSpec.describe Faraday::Response::Logger do
188
207
  end
189
208
  end
190
209
 
210
+ context 'when logging errors' do
211
+ let(:logger_options) { { errors: true } }
212
+
213
+ it 'logs error message' do
214
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
215
+ expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
216
+ end
217
+ end
218
+
191
219
  context 'when using log_level' do
192
220
  let(:logger_options) { { bodies: true, log_level: :debug } }
193
221
 
@@ -4,7 +4,7 @@ RSpec.describe Faraday::Response do
4
4
  subject { Faraday::Response.new(env) }
5
5
 
6
6
  let(:env) do
7
- Faraday::Env.from(status: 404, body: 'yikes',
7
+ Faraday::Env.from(status: 404, body: 'yikes', url: Faraday::Utils.URI('https://lostisland.github.io/faraday'),
8
8
  response_headers: { 'Content-Type' => 'text/plain' })
9
9
  end
10
10
 
@@ -30,6 +30,7 @@ RSpec.describe Faraday::Response do
30
30
  it { expect(hash[:status]).to eq(subject.status) }
31
31
  it { expect(hash[:response_headers]).to eq(subject.headers) }
32
32
  it { expect(hash[:body]).to eq(subject.body) }
33
+ it { expect(hash[:url]).to eq(subject.env.url) }
33
34
  end
34
35
 
35
36
  describe 'marshal serialization support' do
@@ -45,6 +46,7 @@ RSpec.describe Faraday::Response do
45
46
  it { expect(loaded.env[:body]).to eq(env[:body]) }
46
47
  it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
47
48
  it { expect(loaded.env[:status]).to eq(env[:status]) }
49
+ it { expect(loaded.env[:url]).to eq(env[:url]) }
48
50
  end
49
51
 
50
52
  describe '#on_complete' do
@@ -57,11 +57,11 @@ RSpec.describe Faraday::Utils::Headers do
57
57
  end
58
58
 
59
59
  describe '#parse' do
60
- before { subject.parse(headers) }
61
-
62
60
  context 'when response headers leave http status line out' do
63
61
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
64
62
 
63
+ before { subject.parse(headers) }
64
+
65
65
  it { expect(subject.keys).to eq(%w[Content-Type]) }
66
66
  it { expect(subject['Content-Type']).to eq('text/html') }
67
67
  it { expect(subject['content-type']).to eq('text/html') }
@@ -70,13 +70,31 @@ RSpec.describe Faraday::Utils::Headers do
70
70
  context 'when response headers values include a colon' do
71
71
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://httpbingo.org/\r\n\r\n" }
72
72
 
73
+ before { subject.parse(headers) }
74
+
73
75
  it { expect(subject['location']).to eq('http://httpbingo.org/') }
74
76
  end
75
77
 
76
78
  context 'when response headers include a blank line' do
77
79
  let(:headers) { "HTTP/1.1 200 OK\r\n\r\nContent-Type: text/html\r\n\r\n" }
78
80
 
81
+ before { subject.parse(headers) }
82
+
79
83
  it { expect(subject['content-type']).to eq('text/html') }
80
84
  end
85
+
86
+ context 'when response headers include already stored keys' do
87
+ let(:headers) { "HTTP/1.1 200 OK\r\nX-Numbers: 123\r\n\r\n" }
88
+
89
+ before do
90
+ h = subject
91
+ h[:x_numbers] = 8
92
+ h.parse(headers)
93
+ end
94
+
95
+ it do
96
+ expect(subject[:x_numbers]).to eq('8, 123')
97
+ end
98
+ end
81
99
  end
82
100
  end
@@ -102,7 +102,8 @@ RSpec.describe Faraday::Utils do
102
102
  verify_depth: nil,
103
103
  version: '2',
104
104
  min_version: nil,
105
- max_version: nil
105
+ max_version: nil,
106
+ verify_hostname: nil
106
107
  }
107
108
  end
108
109
 
@@ -37,7 +37,8 @@ shared_examples 'adapter examples' do |**options|
37
37
 
38
38
  let(:conn) do
39
39
  conn_options[:ssl] ||= {}
40
- conn_options[:ssl][:ca_file] ||= ENV['SSL_FILE']
40
+ conn_options[:ssl][:ca_file] ||= ENV.fetch('SSL_FILE', nil)
41
+ conn_options[:ssl][:verify_hostname] ||= ENV['SSL_VERIFY_HOSTNAME'] == 'yes'
41
42
 
42
43
  Faraday.new(remote, conn_options) do |conn|
43
44
  conn.request :url_encoded
@@ -153,12 +153,19 @@ shared_examples 'a request method' do |http_method|
153
153
  let(:streamed) { [] }
154
154
 
155
155
  context 'when response is empty' do
156
- it do
156
+ it 'handles streaming' do
157
+ env = nil
157
158
  conn.public_send(http_method, '/') do |req|
158
- req.options.on_data = proc { |*args| streamed << args }
159
+ req.options.on_data = proc do |chunk, size, block_env|
160
+ streamed << [chunk, size]
161
+ env ||= block_env
162
+ end
159
163
  end
160
164
 
161
165
  expect(streamed).to eq([['', 0]])
166
+ # TODO: enable this after updating all existing adapters to the new streaming API
167
+ # expect(env).to be_a(Faraday::Env)
168
+ # expect(env.status).to eq(200)
162
169
  end
163
170
  end
164
171
 
@@ -166,12 +173,19 @@ shared_examples 'a request method' do |http_method|
166
173
  before { request_stub.to_return(body: big_string) }
167
174
 
168
175
  it 'handles streaming' do
176
+ env = nil
169
177
  response = conn.public_send(http_method, '/') do |req|
170
- req.options.on_data = proc { |*args| streamed << args }
178
+ req.options.on_data = proc do |chunk, size, block_env|
179
+ streamed << [chunk, size]
180
+ env ||= block_env
181
+ end
171
182
  end
172
183
 
173
184
  expect(response.body).to eq('')
174
185
  check_streaming_response(streamed, chunk_size: 16 * 1024)
186
+ # TODO: enable this after updating all existing adapters to the new streaming API
187
+ # expect(env).to be_a(Faraday::Env)
188
+ # expect(env.status).to eq(200)
175
189
  end
176
190
  end
177
191
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@technoweenie"
@@ -10,22 +10,28 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-05-06 00:00:00.000000000 Z
13
+ date: 2023-01-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: faraday-net_http
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - "~>"
19
+ - - ">="
20
20
  - !ruby/object:Gem::Version
21
21
  version: '2.0'
22
+ - - "<"
23
+ - !ruby/object:Gem::Version
24
+ version: '3.1'
22
25
  type: :runtime
23
26
  prerelease: false
24
27
  version_requirements: !ruby/object:Gem::Requirement
25
28
  requirements:
26
- - - "~>"
29
+ - - ">="
27
30
  - !ruby/object:Gem::Version
28
31
  version: '2.0'
32
+ - - "<"
33
+ - !ruby/object:Gem::Version
34
+ version: '3.1'
29
35
  - !ruby/object:Gem::Dependency
30
36
  name: ruby2_keywords
31
37
  requirement: !ruby/object:Gem::Requirement
@@ -125,7 +131,7 @@ licenses:
125
131
  - MIT
126
132
  metadata:
127
133
  homepage_uri: https://lostisland.github.io/faraday
128
- changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.3.0
134
+ changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.7.4
129
135
  source_code_uri: https://github.com/lostisland/faraday
130
136
  bug_tracker_uri: https://github.com/lostisland/faraday/issues
131
137
  post_install_message: