faraday 2.6.0 → 2.7.10

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: d9ba15c64f037b4f2c0194ae0d6340af38e79ded9d85b37a36f2ca3cde44d2f0
4
- data.tar.gz: b4ae3fe514db4cba5db21446e4b50d2d8b58ae3f3c077c7e288b4eb0a64637e1
3
+ metadata.gz: e4aa57af93476ba31117b14ed37bea950bf9b677d4dcca13ca1296a3ac2c3ff7
4
+ data.tar.gz: 98b3cb7b7c5c08eba9f58ad96181757d6cd7fc7431ac47ae19e8c95a25de12f9
5
5
  SHA512:
6
- metadata.gz: 20feaa462add0a309d25ceefbd6e5c4481f08a0e1f201d15a2f7459ea83611363002bfc40ba35b143b99d9ef29455edf10e40557455f2d7b057a8440d2f4e3ba
7
- data.tar.gz: f82680b983f1059eba7191b4ac7f17bc21f8b33736c6e1130c684c695d32fae3f3f278ea7e7b13adb92f89208befaf35fe7dc485dc5e5eb3f582943bde5a2b37
6
+ metadata.gz: 28f98f568e844799353a00d5ff49511f7bdb759a50edd3de2d4194fbb348dc14d1ef0294d854016ccdcbec915546225ebb2cf6dec354d6e7fbbf707b00cfc170
7
+ data.tar.gz: 3506cebb9ad1fbd1dd38c1804e18cfd7d7352796d11fa564cde9ce0eee1b1a8ba6d07acda4b179662e5b6eb23baf6f70173240cf8a6b69834d2db9c740a80e25
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2022 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
@@ -13,7 +12,8 @@ requests. Instead, you probably want to have a look at [Awesome Faraday][awesome
13
12
  ## Getting Started
14
13
 
15
14
  The best starting point is the [Faraday Website][website], with its introduction and explanation.
16
- Need more details? See the [Faraday API Documentation][apidoc] to see how it works internally.
15
+
16
+ Need more details? See the [Faraday API Documentation][apidoc] to see how it works internally, or take a look at [Advanced techniques for calling HTTP APIs in Ruby](https://mattbrictson.com/blog/advanced-http-techniques-in-ruby) blog post from @mattbrictson 🚀
17
17
 
18
18
  ## Supported Ruby versions
19
19
 
@@ -42,14 +42,15 @@ Open the issues page and check for the `help wanted` label!
42
42
  But before you start coding, please read our [Contributing Guide][contributing]
43
43
 
44
44
  ## Copyright
45
- © 2009 - 2022, the [Faraday Team][faraday_team]. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
46
45
 
47
- [awesome]: https://github.com/lostisland/awesome-faraday/#adapters
48
- [website]: https://lostisland.github.io/faraday
46
+ © 2009 - 2023, the [Faraday Team][faraday_team]. Website and branding design by [Elena Lo Piccolo](https://elelopic.design).
47
+
48
+ [awesome]: https://github.com/lostisland/awesome-faraday/#adapters
49
+ [website]: https://lostisland.github.io/faraday
49
50
  [faraday_team]: https://lostisland.github.io/faraday/team
50
51
  [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
52
+ [apidoc]: https://www.rubydoc.info/github/lostisland/faraday
53
+ [actions]: https://github.com/lostisland/faraday/actions
54
+ [jruby]: http://jruby.org/
55
+ [rubinius]: http://rubini.us/
56
+ [license]: LICENSE.md
data/Rakefile CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'rspec/core/rake_task'
4
4
 
5
- RSpec::Core::RakeTask.new(:spec)
5
+ RSpec::Core::RakeTask.new(:spec) do |task|
6
+ task.ruby_opts = %w[-W]
7
+ end
6
8
 
7
9
  task default: :spec
@@ -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
@@ -182,7 +184,7 @@ module Faraday
182
184
  end
183
185
 
184
186
  # Stub request
185
- class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
187
+ Stub = Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) do
186
188
  # @param env [Faraday::Env]
187
189
  def matches?(env)
188
190
  request_host = env[:url].host
@@ -238,7 +240,7 @@ module Faraday
238
240
  end
239
241
 
240
242
  def body_match?(request_body)
241
- return true if body.to_s.size.zero?
243
+ return true if body.to_s.empty?
242
244
 
243
245
  case body
244
246
  when Proc
@@ -273,15 +275,26 @@ module Faraday
273
275
 
274
276
  unless stub
275
277
  raise Stubs::NotFound, "no stubbed request for #{env[:method]} " \
276
- "#{env[:url]} #{env[:body]}"
278
+ "#{env[:url]} #{env[:body]} #{env[:headers]}"
277
279
  end
278
280
 
279
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
+
280
291
  status, headers, body =
281
- if block_arity >= 0
282
- 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
283
296
  else
284
- stub.block.call(env, meta)
297
+ stub.block.call(*params)
285
298
  end
286
299
 
287
300
  # We need to explicitly pass `reason_phrase = nil` here to avoid keyword args conflicts.
@@ -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.
@@ -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|
@@ -261,14 +261,13 @@ module Faraday
261
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
@@ -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)
@@ -515,22 +514,17 @@ module Faraday
515
514
  return if Faraday.ignore_env_proxy
516
515
 
517
516
  uri = nil
518
- if URI.parse('').respond_to?(:find_proxy)
519
- case url
520
- when String
521
- uri = Utils.URI(url)
522
- uri = if uri.host.nil?
523
- find_default_proxy
524
- else
525
- URI.parse("#{uri.scheme}://#{uri.host}").find_proxy
526
- end
527
- when URI
528
- uri = url.find_proxy
529
- when nil
530
- uri = find_default_proxy
531
- end
532
- else
533
- warn 'no_proxy is unsupported' if ENV['no_proxy'] || ENV['NO_PROXY']
517
+ case url
518
+ when String
519
+ uri = Utils.URI(url)
520
+ uri = if uri.host.nil?
521
+ find_default_proxy
522
+ else
523
+ URI.parse("#{uri.scheme}://#{uri.host}").find_proxy
524
+ end
525
+ when URI
526
+ uri = url.find_proxy
527
+ when nil
534
528
  uri = find_default_proxy
535
529
  end
536
530
  ProxyOptions.from(uri) if uri
data/lib/faraday/error.rb CHANGED
@@ -29,15 +29,21 @@ module Faraday
29
29
  end
30
30
 
31
31
  def response_status
32
- @response[:status] if @response
32
+ return unless @response
33
+
34
+ @response.is_a?(Faraday::Response) ? @response.status : @response[:status]
33
35
  end
34
36
 
35
37
  def response_headers
36
- @response[:headers] if @response
38
+ return unless @response
39
+
40
+ @response.is_a?(Faraday::Response) ? @response.headers : @response[:headers]
37
41
  end
38
42
 
39
43
  def response_body
40
- @response[:body] if @response
44
+ return unless @response
45
+
46
+ @response.is_a?(Faraday::Response) ? @response.body : @response[:body]
41
47
  end
42
48
 
43
49
  protected
@@ -106,6 +112,10 @@ module Faraday
106
112
  class ProxyAuthError < ClientError
107
113
  end
108
114
 
115
+ # Raised by Faraday::Response::RaiseError in case of a 408 response.
116
+ class RequestTimeoutError < ClientError
117
+ end
118
+
109
119
  # Raised by Faraday::Response::RaiseError in case of a 409 response.
110
120
  class ConflictError < ClientError
111
121
  end
@@ -8,35 +8,47 @@ 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:)
15
15
  @logger = logger
16
- @filter = []
17
16
  @options = DEFAULT_OPTIONS.merge(options)
17
+ unless %i[debug info warn error fatal].include?(@options[:log_level])
18
+ @options[:log_level] = :info
19
+ end
20
+ @filter = []
18
21
  end
19
22
 
20
23
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
21
24
 
22
25
  def request(env)
23
- request_log = proc do
26
+ public_send(log_level, 'request') do
24
27
  "#{env.method.upcase} #{apply_filters(env.url.to_s)}"
25
28
  end
26
- public_send(log_level, 'request', &request_log)
27
29
 
28
30
  log_headers('request', env.request_headers) if log_headers?(:request)
29
31
  log_body('request', env[:body]) if env[:body] && log_body?(:request)
30
32
  end
31
33
 
32
34
  def response(env)
33
- status = proc { "Status #{env.status}" }
34
- public_send(log_level, 'response', &status)
35
+ public_send(log_level, 'response') { "Status #{env.status}" }
35
36
 
36
37
  log_headers('response', env.response_headers) if log_headers?(:response)
37
38
  log_body('response', env[:body]) if env[:body] && log_body?(:response)
38
39
  end
39
40
 
41
+ def exception(exc)
42
+ return unless log_errors?
43
+
44
+ public_send(log_level, 'error') { exc.full_message }
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
@@ -44,6 +56,8 @@ module Faraday
44
56
  private
45
57
 
46
58
  def dump_headers(headers)
59
+ return if headers.nil?
60
+
47
61
  headers.map { |k, v| "#{k}: #{v.inspect}" }.join("\n")
48
62
  end
49
63
 
@@ -77,6 +91,10 @@ module Faraday
77
91
  end
78
92
  end
79
93
 
94
+ def log_errors?
95
+ @options[:errors]
96
+ end
97
+
80
98
  def apply_filters(output)
81
99
  @filter.each do |pattern, replacement|
82
100
  output = output.to_s.gsub(pattern, replacement)
@@ -85,21 +103,15 @@ module Faraday
85
103
  end
86
104
 
87
105
  def log_level
88
- unless %i[debug info warn error fatal].include?(@options[:log_level])
89
- return :info
90
- end
91
-
92
106
  @options[:log_level]
93
107
  end
94
108
 
95
109
  def log_headers(type, headers)
96
- headers_log = proc { apply_filters(dump_headers(headers)) }
97
- public_send(log_level, type, &headers_log)
110
+ public_send(log_level, type) { apply_filters(dump_headers(headers)) }
98
111
  end
99
112
 
100
113
  def log_body(type, body)
101
- body_log = proc { apply_filters(dump_body(body)) }
102
- public_send(log_level, type, &body_log)
114
+ public_send(log_level, type) { apply_filters(dump_body(body)) }
103
115
  end
104
116
  end
105
117
  end
@@ -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
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- # ConnectionOptions contains the configurable properties for a Faraday
5
- # connection object.
6
- class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
7
- :parallel_manager, :params, :headers,
8
- :builder_class)
9
-
4
+ # @!parse
5
+ # # ConnectionOptions contains the configurable properties for a Faraday
6
+ # # connection object.
7
+ # class ConnectionOptions < Options; end
8
+ ConnectionOptions = Options.new(:request, :proxy, :ssl, :builder, :url,
9
+ :parallel_manager, :params, :headers,
10
+ :builder_class) do
10
11
  options request: RequestOptions, ssl: SSLOptions
11
12
 
12
13
  memoized(:request) { self.class.options_for(:request).new }
@@ -1,65 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- # @!attribute method
5
- # @return [Symbol] HTTP method (`:get`, `:post`)
6
- #
7
- # @!attribute body
8
- # @return [String] The request body that will eventually be converted to a
9
- # string.
10
- #
11
- # @!attribute url
12
- # @return [URI] URI instance for the current request.
13
- #
14
- # @!attribute request
15
- # @return [Hash] options for configuring the request.
16
- # Options for configuring the request.
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
25
- #
26
- # @!attribute request_headers
27
- # @return [Hash] HTTP Headers to be sent to the server.
28
- #
29
- # @!attribute ssl
30
- # @return [Hash] options for configuring SSL requests
31
- #
32
- # @!attribute parallel_manager
33
- # @return [Object] sent if the connection is in parallel mode
34
- #
35
- # @!attribute params
36
- # @return [Hash]
37
- #
38
- # @!attribute response
39
- # @return [Response]
40
- #
41
- # @!attribute response_headers
42
- # @return [Hash] HTTP headers from the server
43
- #
44
- # @!attribute status
45
- # @return [Integer] HTTP response status code
46
- #
47
- # @!attribute reason_phrase
48
- # @return [String]
49
- class Env < Options.new(:method, :request_body, :url, :request,
50
- :request_headers, :ssl, :parallel_manager, :params,
51
- :response, :response_headers, :status,
52
- :reason_phrase, :response_body)
53
-
54
- # rubocop:disable Naming/ConstantName
55
- ContentLength = 'Content-Length'
56
- StatusesWithoutBody = Set.new [204, 304]
57
- SuccessfulStatuses = (200..299).freeze
58
- # rubocop:enable Naming/ConstantName
4
+ # @!parse
5
+ # # @!attribute method
6
+ # # @return [Symbol] HTTP method (`:get`, `:post`)
7
+ # #
8
+ # # @!attribute body
9
+ # # @return [String] The request body that will eventually be converted to a
10
+ # # string.
11
+ # #
12
+ # # @!attribute url
13
+ # # @return [URI] URI instance for the current request.
14
+ # #
15
+ # # @!attribute request
16
+ # # @return [Hash] options for configuring the request.
17
+ # # Options for configuring the request.
18
+ # #
19
+ # # - `:timeout` - time limit for the entire request (Integer in
20
+ # # seconds)
21
+ # # - `:open_timeout` - time limit for just the connection phase (e.g.
22
+ # # handshake) (Integer in seconds)
23
+ # # - `:read_timeout` - time limit for the first response byte received from
24
+ # # the server (Integer in seconds)
25
+ # # - `:write_timeout` - time limit for the client to send the request to the
26
+ # # server (Integer in seconds)
27
+ # # - `:on_data` - Proc for streaming
28
+ # # - `:proxy` - Hash of proxy options
29
+ # # - `:uri` - Proxy server URI
30
+ # # - `:user` - Proxy server username
31
+ # # - `:password` - Proxy server password
32
+ # #
33
+ # # @!attribute request_headers
34
+ # # @return [Hash] HTTP Headers to be sent to the server.
35
+ # #
36
+ # # @!attribute ssl
37
+ # # @return [Hash] options for configuring SSL requests
38
+ # #
39
+ # # @!attribute parallel_manager
40
+ # # @return [Object] sent if the connection is in parallel mode
41
+ # #
42
+ # # @!attribute params
43
+ # # @return [Hash]
44
+ # #
45
+ # # @!attribute response
46
+ # # @return [Response]
47
+ # #
48
+ # # @!attribute response_headers
49
+ # # @return [Hash] HTTP headers from the server
50
+ # #
51
+ # # @!attribute status
52
+ # # @return [Integer] HTTP response status code
53
+ # #
54
+ # # @!attribute reason_phrase
55
+ # # @return [String]
56
+ # class Env < Options; end
57
+ Env = Options.new(:method, :request_body, :url, :request,
58
+ :request_headers, :ssl, :parallel_manager, :params,
59
+ :response, :response_headers, :status,
60
+ :reason_phrase, :response_body) do
61
+ const_set(:ContentLength, 'Content-Length')
62
+ const_set(:StatusesWithoutBody, Set.new([204, 304]))
63
+ const_set(:SuccessfulStatuses, (200..299).freeze)
59
64
 
60
65
  # A Set of HTTP verbs that typically send a body. If no body is set for
61
66
  # these requests, the Content-Length header is set to 0.
62
- MethodsWithBodies = Set.new(Faraday::METHODS_WITH_BODY.map(&:to_sym))
67
+ const_set(:MethodsWithBodies, Set.new(Faraday::METHODS_WITH_BODY.map(&:to_sym)))
63
68
 
64
69
  options request: RequestOptions,
65
70
  request_headers: Utils::Headers, response_headers: Utils::Headers
@@ -120,25 +125,25 @@ module Faraday
120
125
 
121
126
  # @return [Boolean] true if status is in the set of {SuccessfulStatuses}.
122
127
  def success?
123
- SuccessfulStatuses.include?(status)
128
+ Env::SuccessfulStatuses.include?(status)
124
129
  end
125
130
 
126
131
  # @return [Boolean] true if there's no body yet, and the method is in the
127
- # set of {MethodsWithBodies}.
132
+ # set of {Env::MethodsWithBodies}.
128
133
  def needs_body?
129
- !body && MethodsWithBodies.include?(method)
134
+ !body && Env::MethodsWithBodies.include?(method)
130
135
  end
131
136
 
132
137
  # Sets content length to zero and the body to the empty string.
133
138
  def clear_body
134
- request_headers[ContentLength] = '0'
139
+ request_headers[Env::ContentLength] = '0'
135
140
  self.body = +''
136
141
  end
137
142
 
138
143
  # @return [Boolean] true if the status isn't in the set of
139
- # {StatusesWithoutBody}.
144
+ # {Env::StatusesWithoutBody}.
140
145
  def parse_body?
141
- !StatusesWithoutBody.include?(status)
146
+ !Env::StatusesWithoutBody.include?(status)
142
147
  end
143
148
 
144
149
  # @return [Boolean] true if there is a parallel_manager
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- # ProxyOptions contains the configurable properties for the proxy
5
- # configuration used when making an HTTP request.
6
- class ProxyOptions < Options.new(:uri, :user, :password)
4
+ # @!parse
5
+ # # ProxyOptions contains the configurable properties for the proxy
6
+ # # configuration used when making an HTTP request.
7
+ # class ProxyOptions < Options; end
8
+ ProxyOptions = Options.new(:uri, :user, :password) do
7
9
  extend Forwardable
8
10
  def_delegators :uri, :scheme, :scheme=, :host, :host=, :port, :port=,
9
11
  :path, :path=
10
12
 
11
13
  def self.from(value)
12
14
  case value
15
+ when ''
16
+ value = nil
13
17
  when String
14
18
  # URIs without a scheme should default to http (like 'example:123').
15
19
  # This fixes #1282 and prevents a silent failure in some adapters.
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- # RequestOptions contains the configurable properties for a Faraday request.
5
- class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
6
- :timeout, :open_timeout, :read_timeout,
7
- :write_timeout, :boundary, :oauth,
8
- :context, :on_data)
9
-
4
+ # @!parse
5
+ # # RequestOptions contains the configurable properties for a Faraday request.
6
+ # class RequestOptions < Options; end
7
+ RequestOptions = Options.new(:params_encoder, :proxy, :bind,
8
+ :timeout, :open_timeout, :read_timeout,
9
+ :write_timeout, :boundary, :oauth,
10
+ :context, :on_data) do
10
11
  def []=(key, value)
11
12
  if key && key.to_sym == :proxy
12
13
  super(key, value ? ProxyOptions.from(value) : nil)
@@ -1,56 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- # SSL-related options.
5
- #
6
- # @!attribute verify
7
- # @return [Boolean] whether to verify SSL certificates or not
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
- #
13
- # @!attribute ca_file
14
- # @return [String] CA file
15
- #
16
- # @!attribute ca_path
17
- # @return [String] CA path
18
- #
19
- # @!attribute verify_mode
20
- # @return [Integer] Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html)
21
- #
22
- # @!attribute cert_store
23
- # @return [OpenSSL::X509::Store] certificate store
24
- #
25
- # @!attribute client_cert
26
- # @return [String, OpenSSL::X509::Certificate] client certificate
27
- #
28
- # @!attribute client_key
29
- # @return [String, OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] client key
30
- #
31
- # @!attribute certificate
32
- # @return [OpenSSL::X509::Certificate] certificate (Excon only)
33
- #
34
- # @!attribute private_key
35
- # @return [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] private key (Excon only)
36
- #
37
- # @!attribute verify_depth
38
- # @return [Integer] maximum depth for the certificate chain verification
39
- #
40
- # @!attribute version
41
- # @return [String, Symbol] SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D)
42
- #
43
- # @!attribute min_version
44
- # @return [String, Symbol] minimum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D)
45
- #
46
- # @!attribute max_version
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)
48
- class SSLOptions < Options.new(:verify, :verify_hostname,
49
- :ca_file, :ca_path, :verify_mode,
50
- :cert_store, :client_cert, :client_key,
51
- :certificate, :private_key, :verify_depth,
52
- :version, :min_version, :max_version)
53
-
4
+ # @!parse
5
+ # # SSL-related options.
6
+ # #
7
+ # # @!attribute verify
8
+ # # @return [Boolean] whether to verify SSL certificates or not
9
+ # #
10
+ # # @!attribute verify_hostname
11
+ # # @return [Boolean] whether to enable hostname verification on server certificates
12
+ # # during the handshake or not (see https://github.com/ruby/openssl/pull/60)
13
+ # #
14
+ # # @!attribute ca_file
15
+ # # @return [String] CA file
16
+ # #
17
+ # # @!attribute ca_path
18
+ # # @return [String] CA path
19
+ # #
20
+ # # @!attribute verify_mode
21
+ # # @return [Integer] Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html)
22
+ # #
23
+ # # @!attribute cert_store
24
+ # # @return [OpenSSL::X509::Store] certificate store
25
+ # #
26
+ # # @!attribute client_cert
27
+ # # @return [String, OpenSSL::X509::Certificate] client certificate
28
+ # #
29
+ # # @!attribute client_key
30
+ # # @return [String, OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] client key
31
+ # #
32
+ # # @!attribute certificate
33
+ # # @return [OpenSSL::X509::Certificate] certificate (Excon only)
34
+ # #
35
+ # # @!attribute private_key
36
+ # # @return [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA] private key (Excon only)
37
+ # #
38
+ # # @!attribute verify_depth
39
+ # # @return [Integer] maximum depth for the certificate chain verification
40
+ # #
41
+ # # @!attribute version
42
+ # # @return [String, Symbol] SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D)
43
+ # #
44
+ # # @!attribute min_version
45
+ # # @return [String, Symbol] minimum SSL version (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html#method-i-min_version-3D)
46
+ # #
47
+ # # @!attribute max_version
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
+ # class SSLOptions < Options; end
50
+ SSLOptions = Options.new(:verify, :verify_hostname,
51
+ :ca_file, :ca_path, :verify_mode,
52
+ :cert_store, :client_cert, :client_key,
53
+ :certificate, :private_key, :verify_depth,
54
+ :version, :min_version, :max_version) do
54
55
  # @return [Boolean] true if should verify
55
56
  def verify?
56
57
  verify != false
@@ -174,6 +174,7 @@ module Faraday
174
174
 
175
175
  memoized_attributes[key.to_sym] = block
176
176
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
177
+ remove_method(key) if method_defined?(key, false)
177
178
  def #{key}() self[:#{key}]; end
178
179
  RUBY
179
180
  end
@@ -5,12 +5,14 @@ module Faraday
5
5
  # Middleware for instrumenting Requests.
6
6
  class Instrumentation < Faraday::Middleware
7
7
  # Options class used in Request::Instrumentation class.
8
- class Options < Faraday::Options.new(:name, :instrumenter)
8
+ Options = Faraday::Options.new(:name, :instrumenter) do
9
+ remove_method :name
9
10
  # @return [String]
10
11
  def name
11
12
  self[:name] ||= 'request.faraday'
12
13
  end
13
14
 
15
+ remove_method :instrumenter
14
16
  # @return [Class]
15
17
  def instrumenter
16
18
  self[:instrumenter] ||= ActiveSupport::Notifications
@@ -40,7 +40,16 @@ module Faraday
40
40
  end
41
41
 
42
42
  def body?(env)
43
- (body = env[:body]) && !(body.respond_to?(:to_str) && body.empty?)
43
+ body = env[:body]
44
+ case body
45
+ when true, false
46
+ true
47
+ when nil
48
+ # NOTE: nil can be converted to `"null"`, but this middleware doesn't process `nil` for the compatibility.
49
+ false
50
+ else
51
+ !(body.respond_to?(:to_str) && body.empty?)
52
+ end
44
53
  end
45
54
 
46
55
  def request_type(env)
@@ -24,13 +24,14 @@ module Faraday
24
24
  # @return [String] body
25
25
  # @!attribute options
26
26
  # @return [RequestOptions] options
27
- #
28
- # rubocop:disable Style/StructInheritance
29
- class Request < Struct.new(:http_method, :path, :params, :headers, :body, :options)
30
- # rubocop:enable Style/StructInheritance
31
-
27
+ Request = Struct.new(:http_method, :path, :params, :headers, :body, :options) do
32
28
  extend MiddlewareRegistry
33
29
 
30
+ alias_method :member_get, :[]
31
+ private :member_get
32
+ alias_method :member_set, :[]=
33
+ private :member_set
34
+
34
35
  # @param request_method [String]
35
36
  # @yield [request] for block customization, if block given
36
37
  # @yieldparam request [Request]
@@ -41,6 +42,7 @@ module Faraday
41
42
  end
42
43
  end
43
44
 
45
+ remove_method :params=
44
46
  # Replace params, preserving the existing hash type.
45
47
  #
46
48
  # @param hash [Hash] new params
@@ -48,10 +50,11 @@ module Faraday
48
50
  if params
49
51
  params.replace hash
50
52
  else
51
- super
53
+ member_set(:params, hash)
52
54
  end
53
55
  end
54
56
 
57
+ remove_method :headers=
55
58
  # Replace request headers, preserving the existing hash type.
56
59
  #
57
60
  # @param hash [Hash] new headers
@@ -59,7 +62,7 @@ module Faraday
59
62
  if headers
60
63
  headers.replace hash
61
64
  else
62
- super
65
+ member_set(:headers, hash)
63
66
  end
64
67
  end
65
68
 
@@ -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
@@ -24,6 +24,8 @@ module Faraday
24
24
  # mimic the behavior that we get with proxy requests with HTTPS
25
25
  msg = %(407 "Proxy Authentication Required")
26
26
  raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
+ when 408
28
+ raise Faraday::RequestTimeoutError, response_values(env)
27
29
  when 409
28
30
  raise Faraday::ConflictError, response_values(env)
29
31
  when 422
@@ -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.6.0'
4
+ VERSION = '2.7.10'
5
5
  end
@@ -410,4 +410,33 @@ RSpec.describe Faraday::Adapter::Test do
410
410
  end
411
411
  end
412
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
441
+ end
413
442
  end
@@ -310,6 +310,21 @@ RSpec.describe Faraday::Connection do
310
310
  expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
311
311
  end
312
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
313
328
  end
314
329
 
315
330
  describe '#build_url' do
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe Faraday::ClientError do
3
+ RSpec.describe Faraday::Error do
4
4
  describe '.initialize' do
5
5
  subject { described_class.new(exception, response) }
6
6
  let(:response) { nil }
@@ -12,8 +12,10 @@ RSpec.describe Faraday::ClientError do
12
12
  it { expect(subject.response).to be_nil }
13
13
  it { expect(subject.message).to eq(exception.message) }
14
14
  it { expect(subject.backtrace).to eq(exception.backtrace) }
15
- it { expect(subject.inspect).to eq('#<Faraday::ClientError wrapped=#<RuntimeError: test>>') }
15
+ it { expect(subject.inspect).to eq('#<Faraday::Error wrapped=#<RuntimeError: test>>') }
16
16
  it { expect(subject.response_status).to be_nil }
17
+ it { expect(subject.response_headers).to be_nil }
18
+ it { expect(subject.response_body).to be_nil }
17
19
  end
18
20
 
19
21
  context 'with response hash' do
@@ -22,8 +24,10 @@ RSpec.describe Faraday::ClientError do
22
24
  it { expect(subject.wrapped_exception).to be_nil }
23
25
  it { expect(subject.response).to eq(exception) }
24
26
  it { expect(subject.message).to eq('the server responded with status 400') }
25
- it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
27
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
26
28
  it { expect(subject.response_status).to eq(400) }
29
+ it { expect(subject.response_headers).to be_nil }
30
+ it { expect(subject.response_body).to be_nil }
27
31
  end
28
32
 
29
33
  context 'with string' do
@@ -32,8 +36,10 @@ RSpec.describe Faraday::ClientError do
32
36
  it { expect(subject.wrapped_exception).to be_nil }
33
37
  it { expect(subject.response).to be_nil }
34
38
  it { expect(subject.message).to eq('custom message') }
35
- it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: custom message>>') }
39
+ it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: custom message>>') }
36
40
  it { expect(subject.response_status).to be_nil }
41
+ it { expect(subject.response_headers).to be_nil }
42
+ it { expect(subject.response_body).to be_nil }
37
43
  end
38
44
 
39
45
  context 'with anything else #to_s' do
@@ -42,8 +48,10 @@ RSpec.describe Faraday::ClientError do
42
48
  it { expect(subject.wrapped_exception).to be_nil }
43
49
  it { expect(subject.response).to be_nil }
44
50
  it { expect(subject.message).to eq('["error1", "error2"]') }
45
- it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: ["error1", "error2"]>>') }
51
+ it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: ["error1", "error2"]>>') }
46
52
  it { expect(subject.response_status).to be_nil }
53
+ it { expect(subject.response_headers).to be_nil }
54
+ it { expect(subject.response_body).to be_nil }
47
55
  end
48
56
 
49
57
  context 'with exception string and response hash' do
@@ -53,8 +61,25 @@ RSpec.describe Faraday::ClientError do
53
61
  it { expect(subject.wrapped_exception).to be_nil }
54
62
  it { expect(subject.response).to eq(response) }
55
63
  it { expect(subject.message).to eq('custom message') }
56
- it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
64
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
57
65
  it { expect(subject.response_status).to eq(400) }
66
+ it { expect(subject.response_headers).to be_nil }
67
+ it { expect(subject.response_body).to be_nil }
68
+ end
69
+
70
+ context 'with exception and response object' do
71
+ let(:exception) { RuntimeError.new('test') }
72
+ let(:body) { { test: 'test' } }
73
+ let(:headers) { { 'Content-Type' => 'application/json' } }
74
+ let(:response) { Faraday::Response.new(status: 400, response_headers: headers, response_body: body) }
75
+
76
+ it { expect(subject.wrapped_exception).to eq(exception) }
77
+ it { expect(subject.response).to eq(response) }
78
+ it { expect(subject.message).to eq(exception.message) }
79
+ it { expect(subject.backtrace).to eq(exception.backtrace) }
80
+ it { expect(subject.response_status).to eq(400) }
81
+ it { expect(subject.response_headers).to eq(headers) }
82
+ it { expect(subject.response_body).to eq(body) }
58
83
  end
59
84
  end
60
85
  end
@@ -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
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe Faraday::Options do
4
4
  SubOptions = Class.new(Faraday::Options.new(:sub_a, :sub_b))
5
- class ParentOptions < Faraday::Options.new(:a, :b, :c)
5
+ ParentOptions = Faraday::Options.new(:a, :b, :c) do
6
6
  options c: SubOptions
7
7
  end
8
8
 
@@ -32,6 +32,14 @@ RSpec.describe Faraday::ProxyOptions do
32
32
  expect(proxy.user).to be_nil
33
33
  expect(proxy.password).to be_nil
34
34
  end
35
+
36
+ it 'treats empty string as nil' do
37
+ proxy = nil
38
+ proxy_string = proxy.to_s # => empty string
39
+ options = Faraday::ProxyOptions.from proxy_string
40
+ expect(options).to be_a_kind_of(Faraday::ProxyOptions)
41
+ expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
42
+ end
35
43
  end
36
44
 
37
45
  it 'allows hash access' do
@@ -73,6 +73,30 @@ RSpec.describe Faraday::Request::Json do
73
73
  end
74
74
  end
75
75
 
76
+ context 'true body' do
77
+ let(:result) { process(true) }
78
+
79
+ it 'encodes body' do
80
+ expect(result_body).to eq('true')
81
+ end
82
+
83
+ it 'adds content type' do
84
+ expect(result_type).to eq('application/json')
85
+ end
86
+ end
87
+
88
+ context 'false body' do
89
+ let(:result) { process(false) }
90
+
91
+ it 'encodes body' do
92
+ expect(result_body).to eq('false')
93
+ end
94
+
95
+ it 'adds content type' do
96
+ expect(result_type).to eq('application/json')
97
+ end
98
+ end
99
+
76
100
  context 'object body with json type' do
77
101
  let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
78
102
 
@@ -25,6 +25,7 @@ RSpec.describe Faraday::Response::Logger do
25
25
  stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
26
26
  stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
27
27
  stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
28
+ stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
28
29
  end
29
30
  end
30
31
  end
@@ -64,6 +65,15 @@ RSpec.describe Faraday::Response::Logger do
64
65
  expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65
66
  conn.get '/hello'
66
67
  end
68
+
69
+ context 'when no route' do
70
+ it 'delegates logging to the formatter' do
71
+ expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
72
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
73
+
74
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
75
+ end
76
+ end
67
77
  end
68
78
 
69
79
  context 'with custom formatter' do
@@ -94,6 +104,16 @@ RSpec.describe Faraday::Response::Logger do
94
104
  expect(string_io.string).to match('GET http:/hello')
95
105
  end
96
106
 
107
+ it 'logs status' do
108
+ conn.get '/hello', nil, accept: 'text/html'
109
+ expect(string_io.string).to match('Status 200')
110
+ end
111
+
112
+ it 'does not log error message by default' do
113
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
114
+ expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
115
+ end
116
+
97
117
  it 'logs request headers by default' do
98
118
  conn.get '/hello', nil, accept: 'text/html'
99
119
  expect(string_io.string).to match(%(Accept: "text/html))
@@ -188,6 +208,24 @@ RSpec.describe Faraday::Response::Logger do
188
208
  end
189
209
  end
190
210
 
211
+ context 'when logging errors' do
212
+ let(:logger_options) { { errors: true } }
213
+
214
+ it 'logs error message' do
215
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
216
+ expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
217
+ end
218
+ end
219
+
220
+ context 'when logging headers and errors' do
221
+ let(:logger_options) { { headers: true, errors: true } }
222
+
223
+ it 'logs error message' do
224
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
225
+ expect(string_io.string).to match(%(Failed to open TCP connection))
226
+ end
227
+ end
228
+
191
229
  context 'when using log_level' do
192
230
  let(:logger_options) { { bodies: true, log_level: :debug } }
193
231
 
@@ -11,6 +11,7 @@ RSpec.describe Faraday::Response::RaiseError do
11
11
  stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
12
12
  stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
13
13
  stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
14
+ stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] }
14
15
  stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
15
16
  stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
16
17
  stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
@@ -79,6 +80,17 @@ RSpec.describe Faraday::Response::RaiseError do
79
80
  end
80
81
  end
81
82
 
83
+ it 'raises Faraday::RequestTimeoutError for 408 responses' do
84
+ expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
85
+ expect(ex.message).to eq('the server responded with status 408')
86
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
87
+ expect(ex.response[:status]).to eq(408)
88
+ expect(ex.response_status).to eq(408)
89
+ expect(ex.response_body).to eq('keep looking')
90
+ expect(ex.response_headers['X-Reason']).to eq('because')
91
+ end
92
+ end
93
+
82
94
  it 'raises Faraday::ConflictError for 409 responses' do
83
95
  expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
84
96
  expect(ex.message).to eq('the server responded with status 409')
@@ -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
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.6.0
4
+ version: 2.7.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@technoweenie"
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-10-03 00:00:00.000000000 Z
13
+ date: 2023-07-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: faraday-net_http
@@ -131,7 +131,7 @@ licenses:
131
131
  - MIT
132
132
  metadata:
133
133
  homepage_uri: https://lostisland.github.io/faraday
134
- changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.6.0
134
+ changelog_uri: https://github.com/lostisland/faraday/releases/tag/v2.7.10
135
135
  source_code_uri: https://github.com/lostisland/faraday
136
136
  bug_tracker_uri: https://github.com/lostisland/faraday/issues
137
137
  post_install_message: