faraday 2.12.0 → 2.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22c3a83dd32f012eb6d4bf4fe79b3e95ce44979ac19aee42a533d88f5c27848e
4
- data.tar.gz: c07d8eda18dcde723ee2ab365a5d46272c88dab63cbadecd7f3d4af6e6133f9e
3
+ metadata.gz: ecc5e9592d7434d7e39bc5cc827be5b62af99e258582468c8b6fd78e0122bf7e
4
+ data.tar.gz: 4f5728ac524a3456a800c8d0159b4569173b1c841f80fae7d0d2704e261b6588
5
5
  SHA512:
6
- metadata.gz: 728dafd34ea29e515627f14bb254c83729ca66470961505d332994bf9ee52de85d43b97969fd0ce5cb9db22fbdf43b8ac0d64ee9447c336f5fcc6be6adc0d737
7
- data.tar.gz: 1f28a98be9151d77ec729cabc40faa0c11cc27283382925452e6076e20cc5321b63732492d9dc10fb65a35977b18b957e636e72e22686c2467d91de9a038fae8
6
+ metadata.gz: 12567175cd7d08a95ccf8203bffcb9bebf8d1b52f279ce034c3f3e17dc5efc81e08259c2992ff792560ed626dd59ae305c163cb43018860b3c0ae833c3f68855
7
+ data.tar.gz: bb786c688b4629c6bc43e4715715fcd42de120eee3802a3614eca60e17627ff77f40298af500a431158b1f1de8bd0d0bd58817a8793bab92e11bac9aa9b482f2
@@ -26,7 +26,7 @@ class Client
26
26
  end
27
27
 
28
28
  # Example API client test
29
- class ClientTest < Test::Unit::TestCase
29
+ class ClientTest < Test::Unit::TestCase # rubocop:disable Style/OneClassPerFile
30
30
  def test_httpbingo_name
31
31
  stubs = Faraday::Adapter::Test::Stubs.new
32
32
  stubs.get('/api') do |env|
@@ -127,6 +127,14 @@ module Faraday
127
127
  new_stub(:options, path, headers, &block)
128
128
  end
129
129
 
130
+ # Removes all stubs, including the ones that have already been consumed.
131
+ def clear
132
+ @stubs_mutex.synchronize do
133
+ @stack.clear
134
+ @consumed.clear
135
+ end
136
+ end
137
+
130
138
  # Raises an error if any of the stubbed calls have not been made.
131
139
  def verify_stubbed_calls
132
140
  failed_stubs = []
@@ -8,6 +8,12 @@ module Faraday
8
8
 
9
9
  CONTENT_LENGTH = 'Content-Length'
10
10
 
11
+ TIMEOUT_KEYS = {
12
+ read: :read_timeout,
13
+ open: :open_timeout,
14
+ write: :write_timeout
15
+ }.freeze
16
+
11
17
  # This module marks an Adapter as supporting parallel requests.
12
18
  module Parallelism
13
19
  attr_writer :supports_parallel
@@ -23,6 +29,7 @@ module Faraday
23
29
  end
24
30
 
25
31
  extend Parallelism
32
+
26
33
  self.supports_parallel = false
27
34
 
28
35
  def initialize(_app = nil, opts = {}, &block)
@@ -89,12 +96,6 @@ module Faraday
89
96
  end
90
97
  options[key] || options[:timeout]
91
98
  end
92
-
93
- TIMEOUT_KEYS = {
94
- read: :read_timeout,
95
- open: :open_timeout,
96
- write: :write_timeout
97
- }.freeze
98
99
  end
99
100
  end
100
101
 
@@ -481,8 +481,11 @@ module Faraday
481
481
  if url && !base.path.end_with?('/')
482
482
  base.path = "#{base.path}/" # ensure trailing slash
483
483
  end
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://', '/', './', '../')
484
+ url = url.to_s if url.respond_to?(:host)
485
+ # Ensure relative url will be parsed correctly (such as `service:search` or `//evil.com`)
486
+ url = "./#{url}" if url.respond_to?(:start_with?) &&
487
+ (url.start_with?('//') ||
488
+ !url.start_with?('http://', 'https://', '/', './', '../'))
486
489
  uri = url ? base + url : base
487
490
  if params
488
491
  uri.query = params.to_query(params_encoder || options.params_encoder)
@@ -6,6 +6,7 @@ module Faraday
6
6
  module FlatParamsEncoder
7
7
  class << self
8
8
  extend Forwardable
9
+
9
10
  def_delegators :'Faraday::Utils', :escape, :unescape
10
11
  end
11
12
 
@@ -76,9 +77,9 @@ module Faraday
76
77
 
77
78
  empty_accumulator = {}
78
79
 
79
- split_query = (query.split('&').map do |pair|
80
+ split_query = query.split('&').filter_map do |pair|
80
81
  pair.split('=', 2) if pair && !pair.empty?
81
- end).compact
82
+ end
82
83
  split_query.each_with_object(empty_accumulator.dup) do |pair, accu|
83
84
  pair[0] = unescape(pair[0])
84
85
  pair[1] = true if pair[1].nil?
@@ -106,6 +106,8 @@ module Faraday
106
106
 
107
107
  def decode_pair(key, value, context)
108
108
  subkeys = key.scan(SUBKEYS_REGEX)
109
+ validate_params_depth!(subkeys.length)
110
+
109
111
  subkeys.each_with_index do |subkey, i|
110
112
  is_array = subkey =~ /[\[\]]+\Z/
111
113
  subkey = Regexp.last_match.pre_match if is_array
@@ -145,6 +147,12 @@ module Faraday
145
147
  is_array ? context << value : context[subkey] = value
146
148
  end
147
149
 
150
+ def validate_params_depth!(depth)
151
+ return unless @param_depth_limit && depth > @param_depth_limit
152
+
153
+ raise Faraday::Error, "exceeded nested parameter depth limit of #{@param_depth_limit}"
154
+ end
155
+
148
156
  # Internal: convert a nested hash with purely numeric keys into an array.
149
157
  # FIXME: this is not compatible with Rack::Utils.parse_nested_query
150
158
  # @!visibility private
@@ -167,15 +175,17 @@ module Faraday
167
175
  # for your requests.
168
176
  module NestedParamsEncoder
169
177
  class << self
170
- attr_accessor :sort_params, :array_indices
178
+ attr_accessor :sort_params, :array_indices, :param_depth_limit
171
179
 
172
180
  extend Forwardable
181
+
173
182
  def_delegators :'Faraday::Utils', :escape, :unescape
174
183
  end
175
184
 
176
185
  # Useful default for OAuth and caching.
177
186
  @sort_params = true
178
187
  @array_indices = false
188
+ @param_depth_limit = 100
179
189
 
180
190
  extend EncodeMethods
181
191
  extend DecodeMethods
data/lib/faraday/error.rb CHANGED
@@ -79,12 +79,46 @@ module Faraday
79
79
 
80
80
  # Pulls out potential parent exception and response hash.
81
81
  def exc_msg_and_response(exc, response = nil)
82
- return [exc, exc.message, response] if exc.respond_to?(:backtrace)
82
+ case exc
83
+ when Exception
84
+ [exc, exc.message, response]
85
+ when Hash
86
+ [nil, build_error_message_from_hash(exc), exc]
87
+ when Faraday::Env
88
+ [nil, build_error_message_from_env(exc), exc]
89
+ else
90
+ [nil, exc.to_s, response]
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def build_error_message_from_hash(hash)
97
+ # Be defensive with external Hash objects - they might be missing keys
98
+ status = hash.fetch(:status, nil)
99
+ request = hash.fetch(:request, nil)
100
+
101
+ return fallback_error_message(status) if request.nil?
83
102
 
84
- return [nil, "the server responded with status #{exc[:status]}", exc] \
85
- if exc.respond_to?(:each_key)
103
+ method = request.fetch(:method, nil)
104
+ url = request.fetch(:url, nil)
105
+ build_status_error_message(status, method, url)
106
+ end
107
+
108
+ def build_error_message_from_env(env)
109
+ # Faraday::Env is internal - we can make reasonable assumptions about its structure
110
+ build_status_error_message(env.status, env.method, env.url)
111
+ end
86
112
 
87
- [nil, exc.to_s, response]
113
+ def build_status_error_message(status, method, url)
114
+ method_str = method ? method.to_s.upcase : ''
115
+ url_str = url ? url.to_s : ''
116
+ "the server responded with status #{status} for #{method_str} #{url_str}"
117
+ end
118
+
119
+ def fallback_error_message(status)
120
+ "the server responded with status #{status} - method and url are not available " \
121
+ 'due to include_request: false on Faraday::Response::RaiseError middleware'
88
122
  end
89
123
  end
90
124
 
@@ -121,9 +155,12 @@ module Faraday
121
155
  end
122
156
 
123
157
  # Raised by Faraday::Response::RaiseError in case of a 422 response.
124
- class UnprocessableEntityError < ClientError
158
+ class UnprocessableContentError < ClientError
125
159
  end
126
160
 
161
+ # Used to provide compatibility with legacy error name.
162
+ UnprocessableEntityError = UnprocessableContentError
163
+
127
164
  # Raised by Faraday::Response::RaiseError in case of a 429 response.
128
165
  class TooManyRequestsError < ClientError
129
166
  end
@@ -135,7 +172,7 @@ module Faraday
135
172
  # A unified client error for timeouts.
136
173
  class TimeoutError < ServerError
137
174
  def initialize(exc = 'timeout', response = nil)
138
- super(exc, response)
175
+ super
139
176
  end
140
177
  end
141
178
 
@@ -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)
@@ -63,7 +63,7 @@ module Faraday
63
63
 
64
64
  def dump_body(body)
65
65
  if body.respond_to?(:to_str)
66
- body.to_str
66
+ body.to_str.encode(::Encoding::UTF_8, undef: :replace, invalid: :replace)
67
67
  else
68
68
  pretty_inspect(body)
69
69
  end
@@ -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
@@ -65,7 +65,7 @@ module Faraday
65
65
  if app.respond_to?(:close)
66
66
  app.close
67
67
  else
68
- warn "#{app} does not implement \#close!"
68
+ warn "#{app} does not implement #close!"
69
69
  end
70
70
  end
71
71
  end
@@ -60,7 +60,7 @@ module Faraday
60
60
  :reason_phrase, :response_body) do
61
61
  const_set(:ContentLength, 'Content-Length')
62
62
  const_set(:StatusesWithoutBody, Set.new([204, 304]))
63
- const_set(:SuccessfulStatuses, (200..299))
63
+ const_set(:SuccessfulStatuses, 200..299)
64
64
 
65
65
  # A Set of HTTP verbs that typically send a body. If no body is set for
66
66
  # these requests, the Content-Length header is set to 0.
@@ -78,7 +78,7 @@ module Faraday
78
78
  # @param value [Object] a value fitting Option.from(v).
79
79
  # @return [Env] from given value
80
80
  def self.from(value)
81
- env = super(value)
81
+ env = super
82
82
  if value.respond_to?(:custom_members)
83
83
  env.custom_members.update(value.custom_members)
84
84
  end
@@ -90,7 +90,7 @@ module Faraday
90
90
  return self[current_body] if key == :body
91
91
 
92
92
  if in_member_set?(key)
93
- super(key)
93
+ super
94
94
  else
95
95
  custom_members[key]
96
96
  end
@@ -105,7 +105,7 @@ module Faraday
105
105
  end
106
106
 
107
107
  if in_member_set?(key)
108
- super(key, value)
108
+ super
109
109
  else
110
110
  custom_members[key] = value
111
111
  end
@@ -7,6 +7,7 @@ module Faraday
7
7
  # class ProxyOptions < Options; end
8
8
  ProxyOptions = Options.new(:uri, :user, :password) do
9
9
  extend Forwardable
10
+
10
11
  def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=,
11
12
  :path, :path=
12
13
 
@@ -22,12 +23,14 @@ module Faraday
22
23
  when URI
23
24
  value = { uri: value }
24
25
  when Hash, Options
25
- if (uri = value.delete(:uri))
26
- value[:uri] = Utils.URI(uri)
26
+ if value[:uri]
27
+ value = value.dup.tap do |duped|
28
+ duped[:uri] = Utils.URI(duped[:uri])
29
+ end
27
30
  end
28
31
  end
29
32
 
30
- super(value)
33
+ super
31
34
  end
32
35
 
33
36
  memoized(:user) { uri&.user && Utils.unescape(uri.user) }
@@ -12,7 +12,7 @@ module Faraday
12
12
  if key && key.to_sym == :proxy
13
13
  super(key, value ? ProxyOptions.from(value) : nil)
14
14
  else
15
- super(key, value)
15
+ super
16
16
  end
17
17
  end
18
18
 
@@ -11,6 +11,9 @@ module Faraday
11
11
  # # @return [Boolean] whether to enable hostname verification on server certificates
12
12
  # # during the handshake or not (see https://github.com/ruby/openssl/pull/60)
13
13
  # #
14
+ # # @!attribute hostname
15
+ # # @return [String] Server hostname used for SNI (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLSocket.html#method-i-hostname-3D)
16
+ # #
14
17
  # # @!attribute ca_file
15
18
  # # @return [String] CA file
16
19
  # #
@@ -50,7 +53,7 @@ module Faraday
50
53
  # # @!attribute ciphers
51
54
  # # @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)
52
55
  # class SSLOptions < Options; end
53
- SSLOptions = Options.new(:verify, :verify_hostname,
56
+ SSLOptions = Options.new(:verify, :verify_hostname, :hostname,
54
57
  :ca_file, :ca_path, :verify_mode,
55
58
  :cert_store, :client_cert, :client_key,
56
59
  :certificate, :private_key, :verify_depth,
@@ -186,7 +186,7 @@ module Faraday
186
186
  def [](key)
187
187
  key = key.to_sym
188
188
  if (method = self.class.memoized_attributes[key])
189
- super(key) || (self[key] = instance_eval(&method))
189
+ super || (self[key] = instance_eval(&method))
190
190
  else
191
191
  super
192
192
  end
@@ -15,6 +15,11 @@ module Faraday
15
15
  # Used to detect missing arguments
16
16
  NO_ARGUMENT = Object.new
17
17
 
18
+ LOCK_ERR = "can't modify middleware stack after making a request"
19
+ MISSING_ADAPTER_ERROR = "An attempt to run a request with a Faraday::Connection without adapter has been made.\n" \
20
+ "Please set Faraday.default_adapter or provide one when initializing the connection.\n" \
21
+ 'For more info, check https://lostisland.github.io/faraday/usage/.'
22
+
18
23
  attr_accessor :handlers
19
24
 
20
25
  # Error raised when trying to modify the stack after calling `lock!`
@@ -27,10 +32,11 @@ module Faraday
27
32
 
28
33
  attr_reader :name
29
34
 
30
- ruby2_keywords def initialize(klass, *args, &block)
35
+ def initialize(klass, *args, **kwargs, &block)
31
36
  @name = klass.to_s
32
37
  REGISTRY.set(klass) if klass.respond_to?(:name)
33
38
  @args = args
39
+ @kwargs = kwargs
34
40
  @block = block
35
41
  end
36
42
 
@@ -53,7 +59,7 @@ module Faraday
53
59
  end
54
60
 
55
61
  def build(app = nil)
56
- klass.new(app, *@args, &@block)
62
+ klass.new(app, *@args, **@kwargs, &@block)
57
63
  end
58
64
  end
59
65
 
@@ -88,52 +94,52 @@ module Faraday
88
94
  @handlers.frozen?
89
95
  end
90
96
 
91
- ruby2_keywords def use(klass, *args, &block)
97
+ def use(klass, ...)
92
98
  if klass.is_a? Symbol
93
- use_symbol(Faraday::Middleware, klass, *args, &block)
99
+ use_symbol(Faraday::Middleware, klass, ...)
94
100
  else
95
101
  raise_if_locked
96
102
  raise_if_adapter(klass)
97
- @handlers << self.class::Handler.new(klass, *args, &block)
103
+ @handlers << self.class::Handler.new(klass, ...)
98
104
  end
99
105
  end
100
106
 
101
- ruby2_keywords def request(key, *args, &block)
102
- use_symbol(Faraday::Request, key, *args, &block)
107
+ def request(key, ...)
108
+ use_symbol(Faraday::Request, key, ...)
103
109
  end
104
110
 
105
- ruby2_keywords def response(key, *args, &block)
106
- use_symbol(Faraday::Response, key, *args, &block)
111
+ def response(...)
112
+ use_symbol(Faraday::Response, ...)
107
113
  end
108
114
 
109
- ruby2_keywords def adapter(klass = NO_ARGUMENT, *args, &block)
115
+ def adapter(klass = NO_ARGUMENT, *args, **kwargs, &block)
110
116
  return @adapter if klass == NO_ARGUMENT || klass.nil?
111
117
 
112
118
  klass = Faraday::Adapter.lookup_middleware(klass) if klass.is_a?(Symbol)
113
- @adapter = self.class::Handler.new(klass, *args, &block)
119
+ @adapter = self.class::Handler.new(klass, *args, **kwargs, &block)
114
120
  end
115
121
 
116
122
  ## methods to push onto the various positions in the stack:
117
123
 
118
- ruby2_keywords def insert(index, *args, &block)
124
+ def insert(index, ...)
119
125
  raise_if_locked
120
126
  index = assert_index(index)
121
- handler = self.class::Handler.new(*args, &block)
127
+ handler = self.class::Handler.new(...)
122
128
  @handlers.insert(index, handler)
123
129
  end
124
130
 
125
131
  alias insert_before insert
126
132
 
127
- ruby2_keywords def insert_after(index, *args, &block)
133
+ def insert_after(index, ...)
128
134
  index = assert_index(index)
129
- insert(index + 1, *args, &block)
135
+ insert(index + 1, ...)
130
136
  end
131
137
 
132
- ruby2_keywords def swap(index, *args, &block)
138
+ def swap(index, ...)
133
139
  raise_if_locked
134
140
  index = assert_index(index)
135
141
  @handlers.delete_at(index)
136
- insert(index, *args, &block)
142
+ insert(index, ...)
137
143
  end
138
144
 
139
145
  def delete(handler)
@@ -210,17 +216,12 @@ module Faraday
210
216
 
211
217
  private
212
218
 
213
- LOCK_ERR = "can't modify middleware stack after making a request"
214
- MISSING_ADAPTER_ERROR = "An attempt to run a request with a Faraday::Connection without adapter has been made.\n" \
215
- "Please set Faraday.default_adapter or provide one when initializing the connection.\n" \
216
- 'For more info, check https://lostisland.github.io/faraday/usage/.'
217
-
218
219
  def raise_if_locked
219
220
  raise StackLocked, LOCK_ERR if locked?
220
221
  end
221
222
 
222
223
  def raise_if_adapter(klass)
223
- return unless is_adapter?(klass)
224
+ return unless klass <= Faraday::Adapter
224
225
 
225
226
  raise 'Adapter should be set using the `adapter` method, not `use`'
226
227
  end
@@ -233,12 +234,8 @@ module Faraday
233
234
  !@adapter.nil?
234
235
  end
235
236
 
236
- def is_adapter?(klass) # rubocop:disable Naming/PredicateName
237
- klass <= Faraday::Adapter
238
- end
239
-
240
- ruby2_keywords def use_symbol(mod, key, *args, &block)
241
- use(mod.lookup_middleware(key), *args, &block)
237
+ def use_symbol(mod, key, ...)
238
+ use(mod.lookup_middleware(key), ...)
242
239
  end
243
240
 
244
241
  def assert_index(index)
@@ -10,11 +10,13 @@ module Faraday
10
10
  # lifecycle to a given Logger object. By default, this logs to STDOUT. See
11
11
  # Faraday::Logging::Formatter to see specifically what is logged.
12
12
  class Logger < Middleware
13
+ DEFAULT_OPTIONS = { formatter: Logging::Formatter }.merge(Logging::Formatter::DEFAULT_OPTIONS).freeze
14
+
13
15
  def initialize(app, logger = nil, options = {})
14
- super(app)
16
+ super(app, options)
15
17
  logger ||= ::Logger.new($stdout)
16
- formatter_class = options.delete(:formatter) || Logging::Formatter
17
- @formatter = formatter_class.new(logger: logger, options: options)
18
+ formatter_class = @options.delete(:formatter)
19
+ @formatter = formatter_class.new(logger: logger, options: @options)
18
20
  yield @formatter if block_given?
19
21
  end
20
22
 
@@ -15,7 +15,7 @@ module Faraday
15
15
  404 => Faraday::ResourceNotFound,
16
16
  408 => Faraday::RequestTimeoutError,
17
17
  409 => Faraday::ConflictError,
18
- 422 => Faraday::UnprocessableEntityError,
18
+ 422 => Faraday::UnprocessableContentError,
19
19
  429 => Faraday::TooManyRequestsError
20
20
  }.freeze
21
21
  # rubocop:enable Naming/ConstantName
@@ -33,6 +33,10 @@ module Faraday
33
33
  finished? ? env.body : nil
34
34
  end
35
35
 
36
+ def url
37
+ finished? ? env.url : nil
38
+ end
39
+
36
40
  def finished?
37
41
  !!env
38
42
  end
@@ -60,9 +64,9 @@ module Faraday
60
64
 
61
65
  def to_hash
62
66
  {
63
- status: env.status, body: env.body,
64
- response_headers: env.response_headers,
65
- url: env.url
67
+ status: status, body: body,
68
+ response_headers: headers,
69
+ url: url
66
70
  }
67
71
  end
68
72
 
@@ -51,7 +51,7 @@ module Faraday
51
51
 
52
52
  def [](key)
53
53
  key = KeyMap[key]
54
- super(key) || super(@names[key.downcase])
54
+ super || super(@names[key.downcase])
55
55
  end
56
56
 
57
57
  def []=(key, val)
@@ -59,13 +59,13 @@ module Faraday
59
59
  key = (@names[key.downcase] ||= key)
60
60
  # join multiple values with a comma
61
61
  val = val.to_ary.join(', ') if val.respond_to?(:to_ary)
62
- super(key, val)
62
+ super
63
63
  end
64
64
 
65
65
  def fetch(key, ...)
66
66
  key = KeyMap[key]
67
67
  key = @names.fetch(key.downcase, key)
68
- super(key, ...)
68
+ super
69
69
  end
70
70
 
71
71
  def delete(key)
@@ -74,13 +74,13 @@ module Faraday
74
74
  return unless key
75
75
 
76
76
  @names.delete key.downcase
77
- super(key)
77
+ super
78
78
  end
79
79
 
80
80
  def dig(key, *rest)
81
81
  key = KeyMap[key]
82
82
  key = @names.fetch(key.downcase, key)
83
- super(key, *rest)
83
+ super
84
84
  end
85
85
 
86
86
  def include?(key)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.12.0'
4
+ VERSION = '2.14.3'
5
5
  end
data/lib/faraday.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cgi'
3
+ require 'cgi/escape'
4
+ require 'cgi/util' if RUBY_VERSION < '3.5'
4
5
  require 'date'
5
6
  require 'set'
6
7
  require 'forwardable'
@@ -439,4 +439,22 @@ RSpec.describe Faraday::Adapter::Test do
439
439
  end
440
440
  end
441
441
  end
442
+
443
+ describe '#clear' do
444
+ it 'removes pending stubs' do
445
+ stubs.clear
446
+ expect { connection.get('/hello') }.to raise_error(described_class::Stubs::NotFound)
447
+ end
448
+
449
+ it 'removes already consumed stubs' do
450
+ expect(connection.get('/hello').body).to eq('hello')
451
+ stubs.clear
452
+ expect { connection.get('/hello') }.to raise_error(described_class::Stubs::NotFound)
453
+ end
454
+
455
+ it 'leaves the stubs empty' do
456
+ stubs.clear
457
+ expect(stubs).to be_empty
458
+ end
459
+ end
442
460
  end
@@ -311,6 +311,46 @@ RSpec.describe Faraday::Connection do
311
311
  end
312
312
  end
313
313
 
314
+ context 'with protocol-relative URL (GHSA-33mh-2634-fwr2)' do
315
+ it 'does not allow host override with //evil.com/path' do
316
+ conn.url_prefix = 'http://httpbingo.org/api'
317
+ uri = conn.build_exclusive_url('//evil.com/path')
318
+ expect(uri.host).to eq('httpbingo.org')
319
+ end
320
+
321
+ it 'does not allow host override with URI("//evil.com/path")' do
322
+ conn.url_prefix = 'http://httpbingo.org/api'
323
+ uri = conn.build_exclusive_url(URI('//evil.com/path?token=1'))
324
+ expect(uri.host).to eq('httpbingo.org')
325
+ expect(uri.query).to eq('token=1')
326
+ end
327
+
328
+ it 'does not allow host override with //evil.com:8080/path' do
329
+ conn.url_prefix = 'http://httpbingo.org/api'
330
+ uri = conn.build_exclusive_url('//evil.com:8080/path')
331
+ expect(uri.host).to eq('httpbingo.org')
332
+ end
333
+
334
+ it 'does not allow host override with //user:pass@evil.com/path' do
335
+ conn.url_prefix = 'http://httpbingo.org/api'
336
+ uri = conn.build_exclusive_url('//user:pass@evil.com/path')
337
+ expect(uri.host).to eq('httpbingo.org')
338
+ end
339
+
340
+ it 'does not allow host override with ///evil.com' do
341
+ conn.url_prefix = 'http://httpbingo.org/api'
342
+ uri = conn.build_exclusive_url('///evil.com')
343
+ expect(uri.host).to eq('httpbingo.org')
344
+ end
345
+
346
+ it 'still allows single-slash absolute paths' do
347
+ conn.url_prefix = 'http://httpbingo.org/api'
348
+ uri = conn.build_exclusive_url('/safe/path')
349
+ expect(uri.host).to eq('httpbingo.org')
350
+ expect(uri.path).to eq('/safe/path')
351
+ end
352
+ end
353
+
314
354
  context 'with a custom `default_uri_parser`' do
315
355
  let(:url) { 'http://httpbingo.org' }
316
356
  let(:parser) { Addressable::URI }
@@ -340,6 +380,18 @@ RSpec.describe Faraday::Connection do
340
380
  url = conn.build_url(nil, b: 2, c: 3)
341
381
  expect(url.to_s).to eq('http://httpbingo.org/nigiri?a=1&b=2&c=3')
342
382
  end
383
+
384
+ it 'raises a controlled error when URL query params exceed the nested depth limit' do
385
+ original_param_depth_limit = Faraday::NestedParamsEncoder.param_depth_limit
386
+ Faraday::NestedParamsEncoder.param_depth_limit = 2
387
+
388
+ expect { conn.build_url('/nigiri?a[b][c]=1') }.to raise_error(
389
+ Faraday::Error,
390
+ 'exceeded nested parameter depth limit of 2'
391
+ )
392
+ ensure
393
+ Faraday::NestedParamsEncoder.param_depth_limit = original_param_depth_limit
394
+ end
343
395
  end
344
396
 
345
397
  describe '#build_request' do
@@ -23,8 +23,12 @@ RSpec.describe Faraday::Error do
23
23
 
24
24
  it { expect(subject.wrapped_exception).to be_nil }
25
25
  it { expect(subject.response).to eq(exception) }
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}>') }
26
+ it { expect(subject.message).to eq('the server responded with status 400 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
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 }
@@ -81,5 +89,87 @@ RSpec.describe Faraday::Error do
81
89
  it { expect(subject.response_headers).to eq(headers) }
82
90
  it { expect(subject.response_body).to eq(body) }
83
91
  end
92
+
93
+ context 'with hash missing status key' do
94
+ let(:exception) { { body: 'error body' } }
95
+
96
+ it { expect(subject.wrapped_exception).to be_nil }
97
+ it { expect(subject.response).to eq(exception) }
98
+ it { expect(subject.message).to eq('the server responded with status - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
99
+ end
100
+
101
+ context 'with hash with status but missing request data' do
102
+ let(:exception) { { status: 404, body: 'not found' } } # missing request key
103
+
104
+ it { expect(subject.wrapped_exception).to be_nil }
105
+ it { expect(subject.response).to eq(exception) }
106
+ it { expect(subject.message).to eq('the server responded with status 404 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
107
+ end
108
+
109
+ context 'with hash with status and request but missing method in request' do
110
+ let(:exception) { { status: 404, body: 'not found', request: { url: 'http://example.com/test' } } } # missing method
111
+
112
+ it { expect(subject.wrapped_exception).to be_nil }
113
+ it { expect(subject.response).to eq(exception) }
114
+ it { expect(subject.message).to eq('the server responded with status 404 for http://example.com/test') }
115
+ end
116
+
117
+ context 'with hash with status and request but missing url in request' do
118
+ let(:exception) { { status: 404, body: 'not found', request: { method: :get } } } # missing url
119
+
120
+ it { expect(subject.wrapped_exception).to be_nil }
121
+ it { expect(subject.response).to eq(exception) }
122
+ it { expect(subject.message).to eq('the server responded with status 404 for GET ') }
123
+ end
124
+
125
+ context 'with properly formed Faraday::Env' do
126
+ # This represents the normal case - a well-formed Faraday::Env object
127
+ # with all the standard properties populated as they would be during
128
+ # a typical HTTP request/response cycle
129
+ let(:exception) { Faraday::Env.new }
130
+
131
+ before do
132
+ exception.status = 500
133
+ exception.method = :post
134
+ exception.url = URI('https://api.example.com/users')
135
+ exception.request = Faraday::RequestOptions.new
136
+ exception.response_headers = { 'content-type' => 'application/json' }
137
+ exception.response_body = '{"error": "Internal server error"}'
138
+ exception.request_headers = { 'authorization' => 'Bearer token123' }
139
+ exception.request_body = '{"name": "John"}'
140
+ end
141
+
142
+ it { expect(subject.wrapped_exception).to be_nil }
143
+ it { expect(subject.response).to eq(exception) }
144
+ it { expect(subject.message).to eq('the server responded with status 500 for POST https://api.example.com/users') }
145
+ end
146
+
147
+ context 'with Faraday::Env missing status key' do
148
+ let(:exception) { Faraday::Env.new }
149
+
150
+ before do
151
+ exception[:body] = 'error body'
152
+ # Intentionally not setting status
153
+ end
154
+
155
+ it { expect(subject.wrapped_exception).to be_nil }
156
+ it { expect(subject.response).to eq(exception) }
157
+ it { expect(subject.message).to eq('the server responded with status for ') }
158
+ end
159
+
160
+ context 'with Faraday::Env with direct method and url properties' do
161
+ let(:exception) { Faraday::Env.new }
162
+
163
+ before do
164
+ exception.status = 404
165
+ exception.method = :get
166
+ exception.url = URI('http://example.com/test')
167
+ exception[:body] = 'not found'
168
+ end
169
+
170
+ it { expect(subject.wrapped_exception).to be_nil }
171
+ it { expect(subject.response).to eq(exception) }
172
+ it { expect(subject.message).to eq('the server responded with status 404 for GET http://example.com/test') }
173
+ end
84
174
  end
85
175
  end
@@ -52,14 +52,14 @@ RSpec.describe Faraday::Middleware do
52
52
  end
53
53
 
54
54
  describe '#close' do
55
- context "with app that doesn't support \#close" do
55
+ context "with app that doesn't support #close" do
56
56
  it 'should issue warning' do
57
57
  is_expected.to receive(:warn)
58
58
  subject.close
59
59
  end
60
60
  end
61
61
 
62
- context "with app that supports \#close" do
62
+ context 'with app that supports #close' do
63
63
  it 'should issue warning' do
64
64
  expect(app).to receive(:close)
65
65
  is_expected.to_not receive(:warn)
@@ -27,6 +27,33 @@ RSpec.describe Faraday::ProxyOptions do
27
27
  expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
28
28
  end
29
29
 
30
+ it 'works with hash' do
31
+ hash = { user: 'user', password: 'pass', uri: 'http://@example.org' }
32
+ options = Faraday::ProxyOptions.from(hash)
33
+ expect(options.user).to eq('user')
34
+ expect(options.password).to eq('pass')
35
+ expect(options.uri).to be_a_kind_of(URI)
36
+ expect(options.path).to eq('')
37
+ expect(options.port).to eq(80)
38
+ expect(options.host).to eq('example.org')
39
+ expect(options.scheme).to eq('http')
40
+ expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
41
+ end
42
+
43
+ it 'works with option' do
44
+ opt_arg = { user: 'user', password: 'pass', uri: 'http://@example.org' }
45
+ option = Faraday::ConnectionOptions.from(proxy: opt_arg)
46
+ options = Faraday::ProxyOptions.from(option.proxy)
47
+ expect(options.user).to eq('user')
48
+ expect(options.password).to eq('pass')
49
+ expect(options.uri).to be_a_kind_of(URI)
50
+ expect(options.path).to eq('')
51
+ expect(options.port).to eq(80)
52
+ expect(options.host).to eq('example.org')
53
+ expect(options.scheme).to eq('http')
54
+ expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
55
+ end
56
+
30
57
  it 'works with no auth' do
31
58
  proxy = Faraday::ProxyOptions.from 'http://example.org'
32
59
  expect(proxy.user).to be_nil
@@ -5,6 +5,13 @@ require 'rack/utils'
5
5
  RSpec.describe Faraday::NestedParamsEncoder do
6
6
  it_behaves_like 'a params encoder'
7
7
 
8
+ around do |example|
9
+ original_param_depth_limit = described_class.param_depth_limit
10
+ example.run
11
+ ensure
12
+ described_class.param_depth_limit = original_param_depth_limit
13
+ end
14
+
8
15
  it 'decodes arrays' do
9
16
  query = 'a[1]=one&a[2]=two&a[3]=three'
10
17
  expected = { 'a' => %w[one two three] }
@@ -53,6 +60,27 @@ RSpec.describe Faraday::NestedParamsEncoder do
53
60
  expect(subject.decode(query)).to eq(expected)
54
61
  end
55
62
 
63
+ it 'allows nested params within the configured depth limit' do
64
+ described_class.param_depth_limit = 3
65
+
66
+ expect(subject.decode('a[b][c]=1')).to eq({ 'a' => { 'b' => { 'c' => '1' } } })
67
+ end
68
+
69
+ it 'raises a controlled error when nested params exceed the depth limit' do
70
+ described_class.param_depth_limit = 2
71
+
72
+ expect { subject.decode('a[b][c]=1') }.to raise_error(
73
+ Faraday::Error,
74
+ 'exceeded nested parameter depth limit of 2'
75
+ )
76
+ end
77
+
78
+ it 'allows disabling the nested params depth limit' do
79
+ described_class.param_depth_limit = nil
80
+
81
+ expect(subject.decode('a[b][c][d]=1')).to eq({ 'a' => { 'b' => { 'c' => { 'd' => '1' } } } })
82
+ end
83
+
56
84
  it 'decodes nested final value overrides any type' do
57
85
  query = 'a[b][c]=1&a[b]=2'
58
86
  expected = { 'a' => { 'b' => '2' } }
@@ -31,6 +31,15 @@ RSpec.describe Faraday::Request do
31
31
  it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
32
32
  end
33
33
 
34
+ context 'when setting the url on setup with a protocol-relative URI' do
35
+ let(:block) { proc { |req| req.url URI.parse('//evil.com/path?token=1') } }
36
+ let(:url) { subject.to_env(conn).url }
37
+
38
+ it { expect(url.host).to eq('httpbingo.org') }
39
+ it { expect(url.path).to eq('/api///evil.com/path') }
40
+ it { expect(url.query).to eq('token=1') }
41
+ end
42
+
34
43
  context 'when setting the url on setup with a string path and params' do
35
44
  let(:block) { proc { |req| req.url 'foo.json', 'a' => 1 } }
36
45
 
@@ -106,7 +106,7 @@ RSpec.describe Faraday::Response::Json, type: :response do
106
106
  end
107
107
 
108
108
  it 'passes relevant options to JSON parse' do
109
- expect(::JSON).to receive(:parse)
109
+ expect(JSON).to receive(:parse)
110
110
  .with(body, options[:parser_options])
111
111
  .and_return(result)
112
112
 
@@ -21,6 +21,7 @@ RSpec.describe Faraday::Response::Logger do
21
21
  stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] }
22
22
  stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] }
23
23
  stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] }
24
+ stubs.get('/8bit') { [200, { 'Content-Type' => 'text/html' }, (+'café!').force_encoding(Encoding::ASCII_8BIT)] }
24
25
  stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] }
25
26
  stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
26
27
  stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
@@ -55,6 +56,26 @@ RSpec.describe Faraday::Response::Logger do
55
56
  end
56
57
  end
57
58
 
59
+ context 'when logger with program name' do
60
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
61
+
62
+ it 'logs with program name' do
63
+ conn.get '/hello'
64
+
65
+ expect(string_io.string).to match('-- my_best_program: request:')
66
+ expect(string_io.string).to match('-- my_best_program: response:')
67
+ end
68
+ end
69
+
70
+ context 'when logger without program name' do
71
+ it 'logs without program name' do
72
+ conn.get '/hello'
73
+
74
+ expect(string_io.string).to match('-- : request:')
75
+ expect(string_io.string).to match('-- : response:')
76
+ end
77
+ end
78
+
58
79
  context 'with default formatter' do
59
80
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
60
81
 
@@ -169,7 +190,7 @@ RSpec.describe Faraday::Response::Logger do
169
190
  context 'when logging request body' do
170
191
  let(:logger_options) { { bodies: { request: true } } }
171
192
 
172
- it 'log only request body' do
193
+ it 'logs only request body' do
173
194
  conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
174
195
  expect(string_io.string).to match(%(name=Tamago))
175
196
  expect(string_io.string).not_to match(%(pebbles))
@@ -179,7 +200,7 @@ RSpec.describe Faraday::Response::Logger do
179
200
  context 'when logging response body' do
180
201
  let(:logger_options) { { bodies: { response: true } } }
181
202
 
182
- it 'log only response body' do
203
+ it 'logs only response body' do
183
204
  conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
184
205
  expect(string_io.string).to match(%(pebbles))
185
206
  expect(string_io.string).not_to match(%(name=Hamachi))
@@ -189,15 +210,15 @@ RSpec.describe Faraday::Response::Logger do
189
210
  context 'when logging request and response bodies' do
190
211
  let(:logger_options) { { bodies: true } }
191
212
 
192
- it 'log request and response body' do
213
+ it 'logs request and response body' do
193
214
  conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
194
215
  expect(string_io.string).to match(%(name=Ebi))
195
216
  expect(string_io.string).to match(%(pebbles))
196
217
  end
197
218
 
198
- it 'log response body object' do
219
+ it 'logs response body object' do
199
220
  conn.get '/rubbles', nil, accept: 'text/html'
200
- expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
221
+ expect(string_io.string).to match(%(["Barney", "Betty", "Bam Bam"]\n))
201
222
  end
202
223
 
203
224
  it 'logs filter body' do
@@ -208,6 +229,26 @@ RSpec.describe Faraday::Response::Logger do
208
229
  end
209
230
  end
210
231
 
232
+ context 'when bodies are logged by default' do
233
+ before do
234
+ described_class.default_options = { bodies: true }
235
+ end
236
+
237
+ it 'logs response body' do
238
+ conn.post '/ohai'
239
+ expect(string_io.string).to match(%(fred))
240
+ end
241
+
242
+ it 'converts to UTF-8' do
243
+ conn.get '/8bit'
244
+ expect(string_io.string).to match(%(caf��!))
245
+ end
246
+
247
+ after do
248
+ described_class.default_options = { bodies: false }
249
+ end
250
+ end
251
+
211
252
  context 'when logging errors' do
212
253
  let(:logger_options) { { errors: true } }
213
254
 
@@ -13,7 +13,7 @@ RSpec.describe Faraday::Response::RaiseError do
13
13
  stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
14
14
  stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] }
15
15
  stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
16
- stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
16
+ stub.get('unprocessable-content') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
17
17
  stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] }
18
18
  stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
19
19
  stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
@@ -28,7 +28,7 @@ RSpec.describe Faraday::Response::RaiseError do
28
28
 
29
29
  it 'raises Faraday::BadRequestError for 400 responses' do
30
30
  expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
31
- expect(ex.message).to eq('the server responded with status 400')
31
+ expect(ex.message).to eq('the server responded with status 400 for GET http:/bad-request')
32
32
  expect(ex.response[:headers]['X-Reason']).to eq('because')
33
33
  expect(ex.response[:status]).to eq(400)
34
34
  expect(ex.response_status).to eq(400)
@@ -39,7 +39,7 @@ RSpec.describe Faraday::Response::RaiseError do
39
39
 
40
40
  it 'raises Faraday::UnauthorizedError for 401 responses' do
41
41
  expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
42
- expect(ex.message).to eq('the server responded with status 401')
42
+ expect(ex.message).to eq('the server responded with status 401 for GET http:/unauthorized')
43
43
  expect(ex.response[:headers]['X-Reason']).to eq('because')
44
44
  expect(ex.response[:status]).to eq(401)
45
45
  expect(ex.response_status).to eq(401)
@@ -50,7 +50,7 @@ RSpec.describe Faraday::Response::RaiseError do
50
50
 
51
51
  it 'raises Faraday::ForbiddenError for 403 responses' do
52
52
  expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
53
- expect(ex.message).to eq('the server responded with status 403')
53
+ expect(ex.message).to eq('the server responded with status 403 for GET http:/forbidden')
54
54
  expect(ex.response[:headers]['X-Reason']).to eq('because')
55
55
  expect(ex.response[:status]).to eq(403)
56
56
  expect(ex.response_status).to eq(403)
@@ -61,7 +61,7 @@ RSpec.describe Faraday::Response::RaiseError do
61
61
 
62
62
  it 'raises Faraday::ResourceNotFound for 404 responses' do
63
63
  expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
64
- expect(ex.message).to eq('the server responded with status 404')
64
+ expect(ex.message).to eq('the server responded with status 404 for GET http:/not-found')
65
65
  expect(ex.response[:headers]['X-Reason']).to eq('because')
66
66
  expect(ex.response[:status]).to eq(404)
67
67
  expect(ex.response_status).to eq(404)
@@ -83,7 +83,7 @@ RSpec.describe Faraday::Response::RaiseError do
83
83
 
84
84
  it 'raises Faraday::RequestTimeoutError for 408 responses' do
85
85
  expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
86
- expect(ex.message).to eq('the server responded with status 408')
86
+ expect(ex.message).to eq('the server responded with status 408 for GET http:/request-timeout')
87
87
  expect(ex.response[:headers]['X-Reason']).to eq('because')
88
88
  expect(ex.response[:status]).to eq(408)
89
89
  expect(ex.response_status).to eq(408)
@@ -94,7 +94,7 @@ RSpec.describe Faraday::Response::RaiseError do
94
94
 
95
95
  it 'raises Faraday::ConflictError for 409 responses' do
96
96
  expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
97
- expect(ex.message).to eq('the server responded with status 409')
97
+ expect(ex.message).to eq('the server responded with status 409 for GET http:/conflict')
98
98
  expect(ex.response[:headers]['X-Reason']).to eq('because')
99
99
  expect(ex.response[:status]).to eq(409)
100
100
  expect(ex.response_status).to eq(409)
@@ -103,9 +103,20 @@ RSpec.describe Faraday::Response::RaiseError do
103
103
  end
104
104
  end
105
105
 
106
- it 'raises Faraday::UnprocessableEntityError for 422 responses' do
107
- expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
108
- expect(ex.message).to eq('the server responded with status 422')
106
+ it 'raises legacy Faraday::UnprocessableEntityError for 422 responses' do
107
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
108
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
109
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
110
+ expect(ex.response[:status]).to eq(422)
111
+ expect(ex.response_status).to eq(422)
112
+ expect(ex.response_body).to eq('keep looking')
113
+ expect(ex.response_headers['X-Reason']).to eq('because')
114
+ end
115
+ end
116
+
117
+ it 'raises Faraday::UnprocessableContentError for 422 responses' do
118
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableContentError) do |ex|
119
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
109
120
  expect(ex.response[:headers]['X-Reason']).to eq('because')
110
121
  expect(ex.response[:status]).to eq(422)
111
122
  expect(ex.response_status).to eq(422)
@@ -116,7 +127,7 @@ RSpec.describe Faraday::Response::RaiseError do
116
127
 
117
128
  it 'raises Faraday::TooManyRequestsError for 429 responses' do
118
129
  expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
119
- expect(ex.message).to eq('the server responded with status 429')
130
+ expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
120
131
  expect(ex.response[:headers]['X-Reason']).to eq('because')
121
132
  expect(ex.response[:status]).to eq(429)
122
133
  expect(ex.response_status).to eq(429)
@@ -138,7 +149,7 @@ RSpec.describe Faraday::Response::RaiseError do
138
149
 
139
150
  it 'raises Faraday::ClientError for other 4xx responses' do
140
151
  expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
141
- expect(ex.message).to eq('the server responded with status 499')
152
+ expect(ex.message).to eq('the server responded with status 499 for GET http:/4xx')
142
153
  expect(ex.response[:headers]['X-Reason']).to eq('because')
143
154
  expect(ex.response[:status]).to eq(499)
144
155
  expect(ex.response_status).to eq(499)
@@ -149,7 +160,7 @@ RSpec.describe Faraday::Response::RaiseError do
149
160
 
150
161
  it 'raises Faraday::ServerError for 500 responses' do
151
162
  expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
152
- expect(ex.message).to eq('the server responded with status 500')
163
+ expect(ex.message).to eq('the server responded with status 500 for GET http:/server-error')
153
164
  expect(ex.response[:headers]['X-Error']).to eq('bailout')
154
165
  expect(ex.response[:status]).to eq(500)
155
166
  expect(ex.response_status).to eq(500)
@@ -13,6 +13,7 @@ RSpec.describe Faraday::Response do
13
13
  it { expect(subject.success?).to be_falsey }
14
14
  it { expect(subject.status).to eq(404) }
15
15
  it { expect(subject.body).to eq('yikes') }
16
+ it { expect(subject.url).to eq(URI('https://lostisland.github.io/faraday')) }
16
17
  it { expect(subject.headers['Content-Type']).to eq('text/plain') }
17
18
  it { expect(subject['content-type']).to eq('text/plain') }
18
19
 
@@ -31,6 +32,12 @@ RSpec.describe Faraday::Response do
31
32
  it { expect(hash[:response_headers]).to eq(subject.headers) }
32
33
  it { expect(hash[:body]).to eq(subject.body) }
33
34
  it { expect(hash[:url]).to eq(subject.env.url) }
35
+
36
+ context 'when response is not finished' do
37
+ subject { Faraday::Response.new.to_hash }
38
+
39
+ it { is_expected.to eq({ status: nil, body: nil, response_headers: {}, url: nil }) }
40
+ end
34
41
  end
35
42
 
36
43
  describe 'marshal serialization support' do
@@ -33,7 +33,7 @@ RSpec.describe Faraday::Utils do
33
33
  end
34
34
 
35
35
  it 'parses with URI' do
36
- with_default_uri_parser(::URI) do
36
+ with_default_uri_parser(URI) do
37
37
  uri = normalize(url)
38
38
  expect(uri.host).to eq('example.com')
39
39
  end
@@ -104,6 +104,7 @@ RSpec.describe Faraday::Utils do
104
104
  min_version: nil,
105
105
  max_version: nil,
106
106
  verify_hostname: nil,
107
+ hostname: nil,
107
108
  ciphers: nil
108
109
  }
109
110
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faraday
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.14.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@technoweenie"
8
8
  - "@iMacTia"
9
9
  - "@olleolleolle"
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2024-09-18 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: faraday-net_http
@@ -21,7 +20,7 @@ dependencies:
21
20
  version: '2.0'
22
21
  - - "<"
23
22
  - !ruby/object:Gem::Version
24
- version: '3.4'
23
+ version: '3.5'
25
24
  type: :runtime
26
25
  prerelease: false
27
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +30,7 @@ dependencies:
31
30
  version: '2.0'
32
31
  - - "<"
33
32
  - !ruby/object:Gem::Version
34
- version: '3.4'
33
+ version: '3.5'
35
34
  - !ruby/object:Gem::Dependency
36
35
  name: json
37
36
  requirement: !ruby/object:Gem::Requirement
@@ -60,7 +59,6 @@ dependencies:
60
59
  - - ">="
61
60
  - !ruby/object:Gem::Version
62
61
  version: '0'
63
- description:
64
62
  email: technoweenie@gmail.com
65
63
  executables: []
66
64
  extensions: []
@@ -146,11 +144,10 @@ licenses:
146
144
  - MIT
147
145
  metadata:
148
146
  homepage_uri: https://lostisland.github.io/faraday
149
- changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.12.0
147
+ changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.14.3
150
148
  source_code_uri: https://github.com/lostisland/faraday
151
149
  bug_tracker_uri: https://github.com/lostisland/faraday/issues
152
150
  rubygems_mfa_required: 'true'
153
- post_install_message:
154
151
  rdoc_options: []
155
152
  require_paths:
156
153
  - lib
@@ -166,8 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
163
  - !ruby/object:Gem::Version
167
164
  version: '0'
168
165
  requirements: []
169
- rubygems_version: 3.5.16
170
- signing_key:
166
+ rubygems_version: 3.6.9
171
167
  specification_version: 4
172
168
  summary: HTTP/REST API client library.
173
169
  test_files: []