faraday 2.9.0 → 2.12.2

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: 8daafc733cd0bf7b27c3ec39dcca14da1dd4ad6bf28d72e96c6c7c214d4992e1
4
- data.tar.gz: 8b5ce62606f14fd89a64adbe24099ef53067c98444419bfe58b0b986501dae5d
3
+ metadata.gz: bfb424c45e703a33fd819b31c8ee41c9e4e30874b280530a19ce8ae320570ef9
4
+ data.tar.gz: c1a1a5b7aab4f393908958701959f7b41fd303ca4da906c1b4c0581a13c590db
5
5
  SHA512:
6
- metadata.gz: 980d8a35587b2ead1383ac7cd7f8649c193a6d9685c0d69c97f9be4937a18deeef7858d50d2c20649774ac97293d0dc6d4b51049cb40e4f4422132d0c58f372b
7
- data.tar.gz: a02dbd0bd5d2670728f39f2a673b385abda43d7449cb204bff10878e562a179c2de595665096dbc24c4d183990a0417c7386159afb952955806c0ea3321bf5fe
6
+ metadata.gz: 6d4d18e2006ac83dfd22aff834b4fe3c983bfbf578b6850944d35cfbb1ca40ba58ce07414821bc3e604ca3310c85500d5d2b8de7c33b0e6147f24484fcae688f
7
+ data.tar.gz: 9cc8057d003f09a02bbdb9a29b920003beff58391c3bf86838ae61b2e9aaac4782d5764286f824b24d486e8dee202cc2d892ec3f658db6b710d5088d5836be3c
data/CHANGELOG.md CHANGED
@@ -517,7 +517,7 @@ Breaking changes:
517
517
  - Drop support for Ruby 1.8
518
518
 
519
519
  Features:
520
- - Include wrapped exception/reponse in ClientErrors
520
+ - Include wrapped exception/response in ClientErrors
521
521
  - Add `response.reason_phrase`
522
522
  - Provide option to selectively skip logging request/response headers
523
523
  - Add regex support for pattern matching in `test` adapter
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/core/rake_task'
4
+ require 'bundler'
5
+
6
+ Bundler::GemHelper.install_tasks
4
7
 
5
8
  RSpec::Core::RakeTask.new(:spec) do |task|
6
9
  task.ruby_opts = %w[-W]
@@ -26,7 +26,7 @@ module Faraday
26
26
  self.supports_parallel = false
27
27
 
28
28
  def initialize(_app = nil, opts = {}, &block)
29
- @app = ->(env) { env.response }
29
+ @app = lambda(&:response)
30
30
  @connection_options = opts
31
31
  @config_block = block
32
32
  end
@@ -314,15 +314,23 @@ module Faraday
314
314
  #
315
315
  # @yield a block to execute multiple requests.
316
316
  # @return [void]
317
- def in_parallel(manager = nil)
317
+ def in_parallel(manager = nil, &block)
318
318
  @parallel_manager = manager || default_parallel_manager do
319
319
  warn 'Warning: `in_parallel` called but no parallel-capable adapter ' \
320
320
  'on Faraday stack'
321
321
  warn caller[2, 10].join("\n")
322
322
  nil
323
323
  end
324
- yield
325
- @parallel_manager&.run
324
+ return yield unless @parallel_manager
325
+
326
+ if @parallel_manager.respond_to?(:execute)
327
+ # Execute is the new method that is responsible for executing the block.
328
+ @parallel_manager.execute(&block)
329
+ else
330
+ # TODO: Old behaviour, deprecate and remove in 3.0
331
+ yield
332
+ @parallel_manager.run
333
+ end
326
334
  ensure
327
335
  @parallel_manager = nil
328
336
  end
@@ -423,8 +431,8 @@ module Faraday
423
431
  #
424
432
  # @param method [Symbol] HTTP method.
425
433
  # @param url [String, URI, nil] String or URI to access.
426
- # @param body [String, nil] The request body that will eventually be converted to
427
- # a string.
434
+ # @param body [String, Hash, Array, nil] The request body that will eventually be converted to
435
+ # a string; middlewares can be used to support more complex types.
428
436
  # @param headers [Hash, nil] unencoded HTTP header key/value pairs.
429
437
  #
430
438
  # @return [Faraday::Response]
@@ -473,7 +481,8 @@ module Faraday
473
481
  if url && !base.path.end_with?('/')
474
482
  base.path = "#{base.path}/" # ensure trailing slash
475
483
  end
476
- url = url.to_s.gsub(':', '%3A') if URI.parse(url.to_s).opaque
484
+ # Ensure relative url will be parsed correctly (such as `service:search` )
485
+ url = "./#{url}" if url.respond_to?(:start_with?) && !url.start_with?('http://', 'https://', '/', './', '../')
477
486
  uri = url ? base + url : base
478
487
  if params
479
488
  uri.query = params.to_query(params_encoder || options.params_encoder)
data/lib/faraday/error.rb CHANGED
@@ -158,4 +158,8 @@ module Faraday
158
158
  # Raised by middlewares that parse the response, like the JSON response middleware.
159
159
  class ParsingError < Error
160
160
  end
161
+
162
+ # Raised by Faraday::Middleware and subclasses when invalid default_options are used
163
+ class InitializationError < Error
164
+ end
161
165
  end
@@ -23,8 +23,8 @@ module Faraday
23
23
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
24
24
 
25
25
  def request(env)
26
- public_send(log_level, 'request') do
27
- "#{env.method.upcase} #{apply_filters(env.url.to_s)}"
26
+ public_send(log_level) do
27
+ "request: #{env.method.upcase} #{apply_filters(env.url.to_s)}"
28
28
  end
29
29
 
30
30
  log_headers('request', env.request_headers) if log_headers?(:request)
@@ -32,7 +32,7 @@ module Faraday
32
32
  end
33
33
 
34
34
  def response(env)
35
- public_send(log_level, 'response') { "Status #{env.status}" }
35
+ public_send(log_level) { "response: Status #{env.status}" }
36
36
 
37
37
  log_headers('response', env.response_headers) if log_headers?(:response)
38
38
  log_body('response', env[:body]) if env[:body] && log_body?(:response)
@@ -41,7 +41,7 @@ module Faraday
41
41
  def exception(exc)
42
42
  return unless log_errors?
43
43
 
44
- public_send(log_level, 'error') { exc.full_message }
44
+ public_send(log_level) { "error: #{exc.full_message}" }
45
45
 
46
46
  log_headers('error', exc.response_headers) if exc.respond_to?(:response_headers) && log_headers?(:error)
47
47
  return unless exc.respond_to?(:response_body) && exc.response_body && log_body?(:error)
@@ -107,11 +107,11 @@ module Faraday
107
107
  end
108
108
 
109
109
  def log_headers(type, headers)
110
- public_send(log_level, type) { apply_filters(dump_headers(headers)) }
110
+ public_send(log_level) { "#{type}: #{apply_filters(dump_headers(headers))}" }
111
111
  end
112
112
 
113
113
  def log_body(type, body)
114
- public_send(log_level, type) { apply_filters(dump_body(body)) }
114
+ public_send(log_level) { "#{type}: #{apply_filters(dump_body(body))}" }
115
115
  end
116
116
  end
117
117
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'monitor'
4
+
3
5
  module Faraday
4
6
  # Middleware is the basic base class of any Faraday middleware.
5
7
  class Middleware
@@ -7,9 +9,46 @@ module Faraday
7
9
 
8
10
  attr_reader :app, :options
9
11
 
12
+ DEFAULT_OPTIONS = {}.freeze
13
+ LOCK = Mutex.new
14
+
10
15
  def initialize(app = nil, options = {})
11
16
  @app = app
12
- @options = options
17
+ @options = self.class.default_options.merge(options)
18
+ end
19
+
20
+ class << self
21
+ # Faraday::Middleware::default_options= allows user to set default options at the Faraday::Middleware
22
+ # class level.
23
+ #
24
+ # @example Set the Faraday::Response::RaiseError option, `include_request` to `false`
25
+ # my_app/config/initializers/my_faraday_middleware.rb
26
+ #
27
+ # Faraday::Response::RaiseError.default_options = { include_request: false }
28
+ #
29
+ def default_options=(options = {})
30
+ validate_default_options(options)
31
+ LOCK.synchronize do
32
+ @default_options = default_options.merge(options)
33
+ end
34
+ end
35
+
36
+ # default_options attr_reader that initializes class instance variable
37
+ # with the values of any Faraday::Middleware defaults, and merges with
38
+ # subclass defaults
39
+ def default_options
40
+ @default_options ||= DEFAULT_OPTIONS.merge(self::DEFAULT_OPTIONS)
41
+ end
42
+
43
+ private
44
+
45
+ def validate_default_options(options)
46
+ invalid_keys = options.keys.reject { |opt| self::DEFAULT_OPTIONS.key?(opt) }
47
+ return unless invalid_keys.any?
48
+
49
+ raise(Faraday::InitializationError,
50
+ "Invalid options provided. Keys not found in #{self}::DEFAULT_OPTIONS: #{invalid_keys.join(', ')}")
51
+ end
13
52
  end
14
53
 
15
54
  def call(env)
@@ -169,7 +169,7 @@ module Faraday
169
169
  def stream_response(&block)
170
170
  size = 0
171
171
  yielded = false
172
- block_result = block.call do |chunk| # rubocop:disable Performance/RedundantBlockCall
172
+ block_result = block.call do |chunk|
173
173
  if chunk.bytesize.positive? || size.positive?
174
174
  yielded = true
175
175
  size += chunk.bytesize
@@ -46,12 +46,15 @@ module Faraday
46
46
  # #
47
47
  # # @!attribute max_version
48
48
  # # @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)
49
+ # #
50
+ # # @!attribute ciphers
51
+ # # @return [String] cipher list in OpenSSL format (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ciphers-3D)
49
52
  # class SSLOptions < Options; end
50
53
  SSLOptions = Options.new(:verify, :verify_hostname,
51
54
  :ca_file, :ca_path, :verify_mode,
52
55
  :cert_store, :client_cert, :client_key,
53
56
  :certificate, :private_key, :verify_depth,
54
- :version, :min_version, :max_version) do
57
+ :version, :min_version, :max_version, :ciphers) do
55
58
  # @return [Boolean] true if should verify
56
59
  def verify?
57
60
  verify != false
@@ -88,22 +88,22 @@ module Faraday
88
88
  @handlers.frozen?
89
89
  end
90
90
 
91
- ruby2_keywords def use(klass, *args, &block)
91
+ def use(klass, ...)
92
92
  if klass.is_a? Symbol
93
- use_symbol(Faraday::Middleware, klass, *args, &block)
93
+ use_symbol(Faraday::Middleware, klass, ...)
94
94
  else
95
95
  raise_if_locked
96
96
  raise_if_adapter(klass)
97
- @handlers << self.class::Handler.new(klass, *args, &block)
97
+ @handlers << self.class::Handler.new(klass, ...)
98
98
  end
99
99
  end
100
100
 
101
- ruby2_keywords def request(key, *args, &block)
102
- use_symbol(Faraday::Request, key, *args, &block)
101
+ def request(key, ...)
102
+ use_symbol(Faraday::Request, key, ...)
103
103
  end
104
104
 
105
- ruby2_keywords def response(key, *args, &block)
106
- use_symbol(Faraday::Response, key, *args, &block)
105
+ def response(...)
106
+ use_symbol(Faraday::Response, ...)
107
107
  end
108
108
 
109
109
  ruby2_keywords def adapter(klass = NO_ARGUMENT, *args, &block)
@@ -115,25 +115,25 @@ module Faraday
115
115
 
116
116
  ## methods to push onto the various positions in the stack:
117
117
 
118
- ruby2_keywords def insert(index, *args, &block)
118
+ def insert(index, ...)
119
119
  raise_if_locked
120
120
  index = assert_index(index)
121
- handler = self.class::Handler.new(*args, &block)
121
+ handler = self.class::Handler.new(...)
122
122
  @handlers.insert(index, handler)
123
123
  end
124
124
 
125
125
  alias insert_before insert
126
126
 
127
- ruby2_keywords def insert_after(index, *args, &block)
127
+ def insert_after(index, ...)
128
128
  index = assert_index(index)
129
- insert(index + 1, *args, &block)
129
+ insert(index + 1, ...)
130
130
  end
131
131
 
132
- ruby2_keywords def swap(index, *args, &block)
132
+ def swap(index, ...)
133
133
  raise_if_locked
134
134
  index = assert_index(index)
135
135
  @handlers.delete_at(index)
136
- insert(index, *args, &block)
136
+ insert(index, ...)
137
137
  end
138
138
 
139
139
  def delete(handler)
@@ -237,8 +237,8 @@ module Faraday
237
237
  klass <= Faraday::Adapter
238
238
  end
239
239
 
240
- ruby2_keywords def use_symbol(mod, key, *args, &block)
241
- use(mod.lookup_middleware(key), *args, &block)
240
+ def use_symbol(mod, key, ...)
241
+ use(mod.lookup_middleware(key), ...)
242
242
  end
243
243
 
244
244
  def assert_index(index)
@@ -60,7 +60,8 @@ module Faraday
60
60
  @decoder_options =
61
61
  if @decoder_options.is_a?(Array) && @decoder_options.size >= 2
62
62
  @decoder_options.slice(0, 2)
63
- elsif @decoder_options.respond_to?(:load)
63
+ elsif @decoder_options&.respond_to?(:load) # rubocop:disable Lint/RedundantSafeNavigation
64
+ # In some versions of Rails, `nil` responds to `load` - hence the safe navigation check above
64
65
  [@decoder_options, :load]
65
66
  else
66
67
  [::JSON, :parse]
@@ -8,30 +8,30 @@ module Faraday
8
8
  # rubocop:disable Naming/ConstantName
9
9
  ClientErrorStatuses = (400...500)
10
10
  ServerErrorStatuses = (500...600)
11
+ ClientErrorStatusesWithCustomExceptions = {
12
+ 400 => Faraday::BadRequestError,
13
+ 401 => Faraday::UnauthorizedError,
14
+ 403 => Faraday::ForbiddenError,
15
+ 404 => Faraday::ResourceNotFound,
16
+ 408 => Faraday::RequestTimeoutError,
17
+ 409 => Faraday::ConflictError,
18
+ 422 => Faraday::UnprocessableEntityError,
19
+ 429 => Faraday::TooManyRequestsError
20
+ }.freeze
11
21
  # rubocop:enable Naming/ConstantName
12
22
 
23
+ DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
24
+
13
25
  def on_complete(env)
26
+ return if Array(options[:allowed_statuses]).include?(env[:status])
27
+
14
28
  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)
29
+ when *ClientErrorStatusesWithCustomExceptions.keys
30
+ raise ClientErrorStatusesWithCustomExceptions[env[:status]], response_values(env)
23
31
  when 407
24
32
  # mimic the behavior that we get with proxy requests with HTTPS
25
33
  msg = %(407 "Proxy Authentication Required")
26
34
  raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
- when 408
28
- raise Faraday::RequestTimeoutError, response_values(env)
29
- when 409
30
- raise Faraday::ConflictError, response_values(env)
31
- when 422
32
- raise Faraday::UnprocessableEntityError, response_values(env)
33
- when 429
34
- raise Faraday::TooManyRequestsError, response_values(env)
35
35
  when ClientErrorStatuses
36
36
  raise Faraday::ClientError, response_values(env)
37
37
  when ServerErrorStatuses
@@ -58,7 +58,7 @@ module Faraday
58
58
 
59
59
  # Include the request data by default. If the middleware was explicitly
60
60
  # configured to _not_ include request data, then omit it.
61
- return response unless options.fetch(:include_request, true)
61
+ return response unless options[:include_request]
62
62
 
63
63
  response.merge(
64
64
  request: {
@@ -62,10 +62,10 @@ module Faraday
62
62
  super(key, val)
63
63
  end
64
64
 
65
- def fetch(key, *args, &block)
65
+ def fetch(key, ...)
66
66
  key = KeyMap[key]
67
67
  key = @names.fetch(key.downcase, key)
68
- super(key, *args, &block)
68
+ super(key, ...)
69
69
  end
70
70
 
71
71
  def delete(key)
@@ -77,6 +77,12 @@ module Faraday
77
77
  super(key)
78
78
  end
79
79
 
80
+ def dig(key, *rest)
81
+ key = KeyMap[key]
82
+ key = @names.fetch(key.downcase, key)
83
+ super(key, *rest)
84
+ end
85
+
80
86
  def include?(key)
81
87
  @names.include? key.downcase
82
88
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.9.0'
4
+ VERSION = '2.12.2'
5
5
  end
@@ -300,14 +300,14 @@ RSpec.describe Faraday::Connection do
300
300
  it 'joins url to base when used relative path' do
301
301
  conn = Faraday.new(url: url)
302
302
  uri = conn.build_exclusive_url('service:search?limit=400')
303
- expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
303
+ expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
304
304
  end
305
305
 
306
306
  it 'joins url to base when used with path prefix' do
307
307
  conn = Faraday.new(url: url)
308
308
  conn.path_prefix = '/api'
309
309
  uri = conn.build_exclusive_url('service:search?limit=400')
310
- expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
310
+ expect(uri.to_s).to eq('http://service.com/api/service:search?limit=400')
311
311
  end
312
312
  end
313
313
 
@@ -24,7 +24,11 @@ RSpec.describe Faraday::Error do
24
24
  it { expect(subject.wrapped_exception).to be_nil }
25
25
  it { expect(subject.response).to eq(exception) }
26
26
  it { expect(subject.message).to eq('the server responded with status 400') }
27
- it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
27
+ if RUBY_VERSION >= '3.4'
28
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
29
+ else
30
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
31
+ end
28
32
  it { expect(subject.response_status).to eq(400) }
29
33
  it { expect(subject.response_headers).to be_nil }
30
34
  it { expect(subject.response_body).to be_nil }
@@ -61,7 +65,11 @@ RSpec.describe Faraday::Error do
61
65
  it { expect(subject.wrapped_exception).to be_nil }
62
66
  it { expect(subject.response).to eq(response) }
63
67
  it { expect(subject.message).to eq('custom message') }
64
- it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
68
+ if RUBY_VERSION >= '3.4'
69
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
70
+ else
71
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
72
+ end
65
73
  it { expect(subject.response_status).to eq(400) }
66
74
  it { expect(subject.response_headers).to be_nil }
67
75
  it { expect(subject.response_body).to be_nil }
@@ -67,4 +67,147 @@ RSpec.describe Faraday::Middleware do
67
67
  end
68
68
  end
69
69
  end
70
+
71
+ describe '::default_options' do
72
+ let(:subclass_no_options) { FaradayMiddlewareSubclasses::SubclassNoOptions }
73
+ let(:subclass_one_option) { FaradayMiddlewareSubclasses::SubclassOneOption }
74
+ let(:subclass_two_options) { FaradayMiddlewareSubclasses::SubclassTwoOptions }
75
+
76
+ def build_conn(resp_middleware)
77
+ Faraday.new do |c|
78
+ c.adapter :test do |stub|
79
+ stub.get('/success') { [200, {}, 'ok'] }
80
+ end
81
+ c.response resp_middleware
82
+ end
83
+ end
84
+
85
+ RSpec.shared_context 'reset @default_options' do
86
+ before(:each) do
87
+ FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
88
+ FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
89
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
90
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
91
+ end
92
+ end
93
+
94
+ after(:all) do
95
+ FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
96
+ FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
97
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
98
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
99
+ end
100
+
101
+ context 'with subclass DEFAULT_OPTIONS defined' do
102
+ include_context 'reset @default_options'
103
+
104
+ context 'and without application options configured' do
105
+ let(:resp1) { build_conn(:one_option).get('/success') }
106
+
107
+ it 'has only subclass defaults' do
108
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
109
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
110
+ expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
111
+ expect(subclass_two_options.default_options).to eq(subclass_two_options::DEFAULT_OPTIONS)
112
+ end
113
+
114
+ it { expect(resp1.body).to eq('ok') }
115
+ end
116
+
117
+ context "and with one application's options changed" do
118
+ let(:resp2) { build_conn(:two_options).get('/success') }
119
+
120
+ before(:each) do
121
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
122
+ end
123
+
124
+ it 'only updates default options of target subclass' do
125
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
126
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
127
+ expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
128
+ expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
129
+ end
130
+
131
+ it { expect(resp2.body).to eq('ok') }
132
+ end
133
+
134
+ context "and with two applications' options changed" do
135
+ let(:resp1) { build_conn(:one_option).get('/success') }
136
+ let(:resp2) { build_conn(:two_options).get('/success') }
137
+
138
+ before(:each) do
139
+ FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
140
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
141
+ end
142
+
143
+ it 'updates subclasses and parent independent of each other' do
144
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
145
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
146
+ expect(subclass_one_option.default_options).to eq({ some_other_option: true })
147
+ expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
148
+ end
149
+
150
+ it { expect(resp1.body).to eq('ok') }
151
+ it { expect(resp2.body).to eq('ok') }
152
+ end
153
+ end
154
+
155
+ context 'with FARADAY::MIDDLEWARE DEFAULT_OPTIONS and with Subclass DEFAULT_OPTIONS' do
156
+ before(:each) do
157
+ stub_const('Faraday::Middleware::DEFAULT_OPTIONS', { its_magic: false })
158
+ end
159
+
160
+ # Must stub Faraday::Middleware::DEFAULT_OPTIONS before resetting default options
161
+ include_context 'reset @default_options'
162
+
163
+ context 'and without application options configured' do
164
+ let(:resp1) { build_conn(:one_option).get('/success') }
165
+
166
+ it 'has only subclass defaults' do
167
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
168
+ expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
169
+ expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: false })
170
+ expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: true, some_other_option: false })
171
+ end
172
+
173
+ it { expect(resp1.body).to eq('ok') }
174
+ end
175
+
176
+ context "and with two applications' options changed" do
177
+ let(:resp1) { build_conn(:one_option).get('/success') }
178
+ let(:resp2) { build_conn(:two_options).get('/success') }
179
+
180
+ before(:each) do
181
+ FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
182
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
183
+ end
184
+
185
+ it 'updates subclasses and parent independent of each other' do
186
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
187
+ expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
188
+ expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: true })
189
+ expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: false, some_other_option: false })
190
+ end
191
+
192
+ it { expect(resp1.body).to eq('ok') }
193
+ it { expect(resp2.body).to eq('ok') }
194
+ end
195
+ end
196
+
197
+ describe 'default_options input validation' do
198
+ include_context 'reset @default_options'
199
+
200
+ it 'raises error if Faraday::Middleware option does not exist' do
201
+ expect { Faraday::Middleware.default_options = { something_special: true } }.to raise_error(Faraday::InitializationError) do |e|
202
+ expect(e.message).to eq('Invalid options provided. Keys not found in Faraday::Middleware::DEFAULT_OPTIONS: something_special')
203
+ end
204
+ end
205
+
206
+ it 'raises error if subclass option does not exist' do
207
+ expect { subclass_one_option.default_options = { this_is_a_typo: true } }.to raise_error(Faraday::InitializationError) do |e|
208
+ expect(e.message).to eq('Invalid options provided. Keys not found in FaradayMiddlewareSubclasses::SubclassOneOption::DEFAULT_OPTIONS: this_is_a_typo')
209
+ end
210
+ end
211
+ end
212
+ end
70
213
  end
@@ -62,7 +62,8 @@ RSpec.describe Faraday::NestedParamsEncoder do
62
62
  it 'encodes rack compat' do
63
63
  params = { a: [{ one: '1', two: '2' }, '3', ''] }
64
64
  result = Faraday::Utils.unescape(Faraday::NestedParamsEncoder.encode(params)).split('&')
65
- expected = Rack::Utils.build_nested_query(params).split('&')
65
+ escaped = Rack::Utils.build_nested_query(params)
66
+ expected = Rack::Utils.unescape(escaped).split('&')
66
67
  expect(result).to match_array(expected)
67
68
  end
68
69
 
@@ -184,6 +184,23 @@ RSpec.describe Faraday::Response::Json, type: :response do
184
184
  response = process(body)
185
185
  expect(response.body).to eq(result)
186
186
  end
187
+
188
+ it 'passes relevant options to JSON parse even when nil responds to :load' do
189
+ original_allow_message_expectations_on_nil = RSpec::Mocks.configuration.allow_message_expectations_on_nil
190
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
191
+ allow(nil).to receive(:respond_to?)
192
+ .with(:load)
193
+ .and_return(true)
194
+
195
+ expect(JSON).to receive(:parse)
196
+ .with(body, { symbolize_names: true })
197
+ .and_return(result)
198
+
199
+ response = process(body)
200
+ expect(response.body).to eq(result)
201
+ ensure
202
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = original_allow_message_expectations_on_nil
203
+ end
187
204
  end
188
205
  end
189
206
  end
@@ -55,6 +55,26 @@ RSpec.describe Faraday::Response::Logger do
55
55
  end
56
56
  end
57
57
 
58
+ context 'when logger with program name' do
59
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
60
+
61
+ it 'logs with program name' do
62
+ conn.get '/hello'
63
+
64
+ expect(string_io.string).to match('-- my_best_program: request:')
65
+ expect(string_io.string).to match('-- my_best_program: response:')
66
+ end
67
+ end
68
+
69
+ context 'when logger without program name' do
70
+ it 'logs without program name' do
71
+ conn.get '/hello'
72
+
73
+ expect(string_io.string).to match('-- : request:')
74
+ expect(string_io.string).to match('-- : response:')
75
+ end
76
+ end
77
+
58
78
  context 'with default formatter' do
59
79
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
60
80
 
@@ -194,18 +194,82 @@ RSpec.describe Faraday::Response::RaiseError do
194
194
  end
195
195
  end
196
196
 
197
- context 'when the include_request option is set to false' do
198
- let(:middleware_options) { { include_request: false } }
199
-
200
- it 'does not include request info in the exception' do
201
- expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
202
- expect(ex.response.keys).to contain_exactly(
203
- :status,
204
- :headers,
205
- :body
206
- )
197
+ describe 'DEFAULT_OPTION: include_request' do
198
+ before(:each) do
199
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
200
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
201
+ end
202
+
203
+ after(:all) do
204
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
205
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
206
+ end
207
+
208
+ context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
209
+ it 'includes request info in the exception' do
210
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
211
+ expect(ex.response.keys).to contain_exactly(
212
+ :status,
213
+ :headers,
214
+ :body,
215
+ :request
216
+ )
217
+ end
207
218
  end
208
219
  end
220
+
221
+ context 'when application sets default_options `include_request: false`' do
222
+ before(:each) do
223
+ Faraday::Response::RaiseError.default_options = { include_request: false }
224
+ end
225
+
226
+ context 'and when include_request option is omitted' do
227
+ it 'does not include request info in the exception' do
228
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
229
+ expect(ex.response.keys).to contain_exactly(
230
+ :status,
231
+ :headers,
232
+ :body
233
+ )
234
+ end
235
+ end
236
+ end
237
+
238
+ context 'and when include_request option is explicitly set for instance' do
239
+ let(:middleware_options) { { include_request: true } }
240
+
241
+ it 'includes request info in the exception' do
242
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
243
+ expect(ex.response.keys).to contain_exactly(
244
+ :status,
245
+ :headers,
246
+ :body,
247
+ :request
248
+ )
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ describe 'allowing certain status codes' do
257
+ let(:conn) do
258
+ Faraday.new do |b|
259
+ b.response :raise_error, allowed_statuses: [404]
260
+ b.adapter :test do |stub|
261
+ stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
262
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
263
+ end
264
+ end
265
+ end
266
+
267
+ it 'raises an error for status codes that are not explicitly allowed' do
268
+ expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
269
+ end
270
+
271
+ it 'does not raise an error for allowed status codes' do
272
+ expect { conn.get('not-found') }.not_to raise_error
209
273
  end
210
274
  end
211
275
  end
@@ -56,6 +56,15 @@ RSpec.describe Faraday::Utils::Headers do
56
56
  it { expect(subject.delete('content-type')).to be_nil }
57
57
  end
58
58
 
59
+ describe '#dig' do
60
+ before { subject['Content-Type'] = 'application/json' }
61
+
62
+ it { expect(subject&.dig('Content-Type')).to eq('application/json') }
63
+ it { expect(subject&.dig('CONTENT-TYPE')).to eq('application/json') }
64
+ it { expect(subject&.dig(:content_type)).to eq('application/json') }
65
+ it { expect(subject&.dig('invalid')).to be_nil }
66
+ end
67
+
59
68
  describe '#parse' do
60
69
  context 'when response headers leave http status line out' do
61
70
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
@@ -103,7 +103,8 @@ RSpec.describe Faraday::Utils do
103
103
  version: '2',
104
104
  min_version: nil,
105
105
  max_version: nil,
106
- verify_hostname: nil
106
+ verify_hostname: nil,
107
+ ciphers: nil
107
108
  }
108
109
  end
109
110
 
data/spec/faraday_spec.rb CHANGED
@@ -19,7 +19,9 @@ RSpec.describe Faraday do
19
19
 
20
20
  it 'uses method_missing on Faraday if there is no proxyable method' do
21
21
  expected_message =
22
- if RUBY_VERSION >= '3.3'
22
+ if RUBY_VERSION >= '3.4'
23
+ "undefined method 'this_method_does_not_exist' for module Faraday"
24
+ elsif RUBY_VERSION >= '3.3'
23
25
  "undefined method `this_method_does_not_exist' for module Faraday"
24
26
  else
25
27
  "undefined method `this_method_does_not_exist' for Faraday:Module"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaradayMiddlewareSubclasses
4
+ class SubclassNoOptions < Faraday::Middleware
5
+ end
6
+
7
+ class SubclassOneOption < Faraday::Middleware
8
+ DEFAULT_OPTIONS = { some_other_option: false }.freeze
9
+ end
10
+
11
+ class SubclassTwoOptions < Faraday::Middleware
12
+ DEFAULT_OPTIONS = { some_option: true, some_other_option: false }.freeze
13
+ end
14
+ end
15
+
16
+ Faraday::Response.register_middleware(no_options: FaradayMiddlewareSubclasses::SubclassNoOptions)
17
+ Faraday::Response.register_middleware(one_option: FaradayMiddlewareSubclasses::SubclassOneOption)
18
+ Faraday::Response.register_middleware(two_options: FaradayMiddlewareSubclasses::SubclassTwoOptions)
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@technoweenie"
8
8
  - "@iMacTia"
9
9
  - "@olleolleolle"
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-01-09 00:00:00.000000000 Z
13
+ date: 2024-12-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: faraday-net_http
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '2.0'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '3.2'
24
+ version: '3.5'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,8 +31,36 @@ dependencies:
31
31
  version: '2.0'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '3.2'
35
- description:
34
+ version: '3.5'
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ - !ruby/object:Gem::Dependency
50
+ name: logger
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description:
36
64
  email: technoweenie@gmail.com
37
65
  executables: []
38
66
  extensions: []
@@ -107,6 +135,7 @@ files:
107
135
  - spec/spec_helper.rb
108
136
  - spec/support/disabling_stub.rb
109
137
  - spec/support/fake_safe_buffer.rb
138
+ - spec/support/faraday_middleware_subclasses.rb
110
139
  - spec/support/helper_methods.rb
111
140
  - spec/support/shared_examples/adapter.rb
112
141
  - spec/support/shared_examples/params_encoder.rb
@@ -117,10 +146,11 @@ licenses:
117
146
  - MIT
118
147
  metadata:
119
148
  homepage_uri: https://lostisland.github.io/faraday
120
- changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.9.0
149
+ changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.12.2
121
150
  source_code_uri: https://github.com/lostisland/faraday
122
151
  bug_tracker_uri: https://github.com/lostisland/faraday/issues
123
- post_install_message:
152
+ rubygems_mfa_required: 'true'
153
+ post_install_message:
124
154
  rdoc_options: []
125
155
  require_paths:
126
156
  - lib
@@ -136,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
166
  - !ruby/object:Gem::Version
137
167
  version: '0'
138
168
  requirements: []
139
- rubygems_version: 3.5.3
140
- signing_key:
169
+ rubygems_version: 3.5.22
170
+ signing_key:
141
171
  specification_version: 4
142
172
  summary: HTTP/REST API client library.
143
173
  test_files: []