faraday 2.5.2 → 2.9.2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +29 -17
  4. data/Rakefile +6 -1
  5. data/lib/faraday/adapter/test.rb +20 -7
  6. data/lib/faraday/adapter.rb +2 -3
  7. data/lib/faraday/connection.rb +49 -54
  8. data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
  9. data/lib/faraday/error.rb +17 -3
  10. data/lib/faraday/logging/formatter.rb +27 -15
  11. data/lib/faraday/middleware.rb +3 -0
  12. data/lib/faraday/options/connection_options.rb +7 -6
  13. data/lib/faraday/options/env.rb +68 -63
  14. data/lib/faraday/options/proxy_options.rb +7 -3
  15. data/lib/faraday/options/request_options.rb +7 -6
  16. data/lib/faraday/options/ssl_options.rb +51 -50
  17. data/lib/faraday/options.rb +4 -3
  18. data/lib/faraday/rack_builder.rb +0 -1
  19. data/lib/faraday/request/authorization.rb +8 -3
  20. data/lib/faraday/request/instrumentation.rb +3 -1
  21. data/lib/faraday/request/json.rb +18 -3
  22. data/lib/faraday/request.rb +11 -8
  23. data/lib/faraday/response/json.rb +21 -1
  24. data/lib/faraday/response/logger.rb +4 -0
  25. data/lib/faraday/response/raise_error.rb +24 -5
  26. data/lib/faraday/response.rb +2 -1
  27. data/lib/faraday/utils/headers.rb +14 -3
  28. data/lib/faraday/utils.rb +3 -4
  29. data/lib/faraday/version.rb +1 -1
  30. data/spec/faraday/adapter/test_spec.rb +29 -0
  31. data/spec/faraday/connection_spec.rb +17 -2
  32. data/spec/faraday/error_spec.rb +31 -6
  33. data/spec/faraday/middleware_spec.rb +18 -0
  34. data/spec/faraday/options/options_spec.rb +1 -1
  35. data/spec/faraday/options/proxy_options_spec.rb +8 -0
  36. data/spec/faraday/params_encoders/nested_spec.rb +2 -1
  37. data/spec/faraday/request/authorization_spec.rb +35 -0
  38. data/spec/faraday/request/json_spec.rb +88 -0
  39. data/spec/faraday/response/json_spec.rb +89 -0
  40. data/spec/faraday/response/logger_spec.rb +38 -0
  41. data/spec/faraday/response/raise_error_spec.rb +40 -1
  42. data/spec/faraday/response_spec.rb +3 -1
  43. data/spec/faraday/utils/headers_spec.rb +29 -2
  44. data/spec/faraday_spec.rb +10 -4
  45. data/spec/spec_helper.rb +6 -5
  46. metadata +7 -21
@@ -62,10 +62,10 @@ module Faraday
62
62
  super(key, val)
63
63
  end
64
64
 
65
- def fetch(key, *args, &block)
65
+ def fetch(key, ...)
66
66
  key = KeyMap[key]
67
67
  key = @names.fetch(key.downcase, key)
68
- super(key, *args, &block)
68
+ super(key, ...)
69
69
  end
70
70
 
71
71
  def delete(key)
@@ -77,6 +77,12 @@ module Faraday
77
77
  super(key)
78
78
  end
79
79
 
80
+ def dig(key, *rest)
81
+ key = KeyMap[key]
82
+ key = @names.fetch(key.downcase, key)
83
+ super(key, *rest)
84
+ end
85
+
80
86
  def include?(key)
81
87
  @names.include? key.downcase
82
88
  end
@@ -132,7 +138,12 @@ module Faraday
132
138
 
133
139
  # Join multiple values with a comma.
134
140
  def add_parsed(key, value)
135
- self[key] ? self[key] << ', ' << value : self[key] = value
141
+ if key?(key)
142
+ self[key] = self[key].to_s
143
+ self[key] << ', ' << value
144
+ else
145
+ self[key] = value
146
+ end
136
147
  end
137
148
  end
138
149
  end
data/lib/faraday/utils.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
3
  require 'uri'
5
4
  require 'faraday/utils/headers'
6
5
  require 'faraday/utils/params_hash'
@@ -26,7 +25,7 @@ module Faraday
26
25
  attr_writer :default_space_encoding
27
26
  end
28
27
 
29
- ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
28
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
30
29
 
31
30
  def escape(str)
32
31
  str.to_s.gsub(ESCAPE_RE) do |match|
@@ -38,7 +37,7 @@ module Faraday
38
37
  CGI.unescape str.to_s
39
38
  end
40
39
 
41
- DEFAULT_SEP = /[&;] */n.freeze
40
+ DEFAULT_SEP = /[&;] */n
42
41
 
43
42
  # Adapted from Rack
44
43
  def parse_query(query)
@@ -54,7 +53,7 @@ module Faraday
54
53
  end
55
54
 
56
55
  def basic_header_from(login, pass)
57
- value = Base64.encode64("#{login}:#{pass}")
56
+ value = ["#{login}:#{pass}"].pack('m') # Base64 encoding
58
57
  value.delete!("\n")
59
58
  "Basic #{value}"
60
59
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.5.2'
4
+ VERSION = '2.9.2'
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
@@ -300,14 +300,29 @@ RSpec.describe Faraday::Connection do
300
300
  it 'joins url to base when used relative path' do
301
301
  conn = Faraday.new(url: url)
302
302
  uri = conn.build_exclusive_url('service:search?limit=400')
303
- expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
303
+ expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
304
304
  end
305
305
 
306
306
  it 'joins url to base when used with path prefix' do
307
307
  conn = Faraday.new(url: url)
308
308
  conn.path_prefix = '/api'
309
309
  uri = conn.build_exclusive_url('service:search?limit=400')
310
- expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
310
+ expect(uri.to_s).to eq('http://service.com/api/service:search?limit=400')
311
+ end
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
311
326
  end
312
327
  end
313
328
  end
@@ -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
@@ -62,7 +62,8 @@ RSpec.describe Faraday::NestedParamsEncoder do
62
62
  it 'encodes rack compat' do
63
63
  params = { a: [{ one: '1', two: '2' }, '3', ''] }
64
64
  result = Faraday::Utils.unescape(Faraday::NestedParamsEncoder.encode(params)).split('&')
65
- expected = Rack::Utils.build_nested_query(params).split('&')
65
+ escaped = Rack::Utils.build_nested_query(params)
66
+ expected = Rack::Utils.unescape(escaped).split('&')
66
67
  expect(result).to match_array(expected)
67
68
  end
68
69
 
@@ -72,6 +72,41 @@ RSpec.describe Faraday::Request::Authorization do
72
72
  include_examples 'does not interfere with existing authentication'
73
73
  end
74
74
 
75
+ context 'with an argument' do
76
+ let(:response) { conn.get('/auth-echo', nil, 'middle' => 'crunchy surprise') }
77
+
78
+ context 'when passed a proc' do
79
+ let(:auth_config) { [proc { |env| "proc #{env.request_headers['middle']}" }] }
80
+
81
+ it { expect(response.body).to eq('Bearer proc crunchy surprise') }
82
+
83
+ include_examples 'does not interfere with existing authentication'
84
+ end
85
+
86
+ context 'when passed a lambda' do
87
+ let(:auth_config) { [->(env) { "lambda #{env.request_headers['middle']}" }] }
88
+
89
+ it { expect(response.body).to eq('Bearer lambda crunchy surprise') }
90
+
91
+ include_examples 'does not interfere with existing authentication'
92
+ end
93
+
94
+ context 'when passed a callable with an argument' do
95
+ let(:callable) do
96
+ Class.new do
97
+ def call(env)
98
+ "callable #{env.request_headers['middle']}"
99
+ end
100
+ end.new
101
+ end
102
+ let(:auth_config) { [callable] }
103
+
104
+ it { expect(response.body).to eq('Bearer callable crunchy surprise') }
105
+
106
+ include_examples 'does not interfere with existing authentication'
107
+ end
108
+ end
109
+
75
110
  context 'when passed too many arguments' do
76
111
  let(:auth_config) { %w[baz foo] }
77
112
 
@@ -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
 
@@ -108,4 +132,68 @@ RSpec.describe Faraday::Request::Json do
108
132
  expect(result_type).to eq('application/xml; charset=utf-8')
109
133
  end
110
134
  end
135
+
136
+ context 'with encoder' do
137
+ let(:encoder) do
138
+ double('Encoder').tap do |e|
139
+ allow(e).to receive(:dump) { |s, opts| JSON.generate(s, opts) }
140
+ end
141
+ end
142
+
143
+ let(:result) { process(a: 1) }
144
+
145
+ context 'when encoder is passed as object' do
146
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: encoder }) }
147
+
148
+ it 'calls specified JSON encoder\'s dump method' do
149
+ expect(encoder).to receive(:dump).with({ a: 1 })
150
+
151
+ result
152
+ end
153
+
154
+ it 'encodes body' do
155
+ expect(result_body).to eq('{"a":1}')
156
+ end
157
+
158
+ it 'adds content type' do
159
+ expect(result_type).to eq('application/json')
160
+ end
161
+ end
162
+
163
+ context 'when encoder is passed as an object-method pair' do
164
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: [encoder, :dump] }) }
165
+
166
+ it 'calls specified JSON encoder' do
167
+ expect(encoder).to receive(:dump).with({ a: 1 })
168
+
169
+ result
170
+ end
171
+
172
+ it 'encodes body' do
173
+ expect(result_body).to eq('{"a":1}')
174
+ end
175
+
176
+ it 'adds content type' do
177
+ expect(result_type).to eq('application/json')
178
+ end
179
+ end
180
+
181
+ context 'when encoder is not passed' do
182
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
183
+
184
+ it 'calls JSON.generate' do
185
+ expect(JSON).to receive(:generate).with({ a: 1 })
186
+
187
+ result
188
+ end
189
+
190
+ it 'encodes body' do
191
+ expect(result_body).to eq('{"a":1}')
192
+ end
193
+
194
+ it 'adds content type' do
195
+ expect(result_type).to eq('application/json')
196
+ end
197
+ end
198
+ end
111
199
  end
@@ -114,4 +114,93 @@ RSpec.describe Faraday::Response::Json, type: :response do
114
114
  expect(response.body).to eq(result)
115
115
  end
116
116
  end
117
+
118
+ context 'with decoder' do
119
+ let(:decoder) do
120
+ double('Decoder').tap do |e|
121
+ allow(e).to receive(:load) { |s, opts| JSON.parse(s, opts) }
122
+ end
123
+ end
124
+
125
+ let(:body) { '{"a": 1}' }
126
+ let(:result) { { a: 1 } }
127
+
128
+ context 'when decoder is passed as object' do
129
+ let(:options) do
130
+ {
131
+ parser_options: {
132
+ decoder: decoder,
133
+ option: :option_value,
134
+ symbolize_names: true
135
+ }
136
+ }
137
+ end
138
+
139
+ it 'passes relevant options to specified decoder\'s load method' do
140
+ expect(decoder).to receive(:load)
141
+ .with(body, { option: :option_value, symbolize_names: true })
142
+ .and_return(result)
143
+
144
+ response = process(body)
145
+ expect(response.body).to eq(result)
146
+ end
147
+ end
148
+
149
+ context 'when decoder is passed as an object-method pair' do
150
+ let(:options) do
151
+ {
152
+ parser_options: {
153
+ decoder: [decoder, :load],
154
+ option: :option_value,
155
+ symbolize_names: true
156
+ }
157
+ }
158
+ end
159
+
160
+ it 'passes relevant options to specified decoder\'s method' do
161
+ expect(decoder).to receive(:load)
162
+ .with(body, { option: :option_value, symbolize_names: true })
163
+ .and_return(result)
164
+
165
+ response = process(body)
166
+ expect(response.body).to eq(result)
167
+ end
168
+ end
169
+
170
+ context 'when decoder is not passed' do
171
+ let(:options) do
172
+ {
173
+ parser_options: {
174
+ symbolize_names: true
175
+ }
176
+ }
177
+ end
178
+
179
+ it 'passes relevant options to JSON parse' do
180
+ expect(JSON).to receive(:parse)
181
+ .with(body, { symbolize_names: true })
182
+ .and_return(result)
183
+
184
+ response = process(body)
185
+ expect(response.body).to eq(result)
186
+ end
187
+
188
+ it 'passes relevant options to JSON parse even when nil responds to :load' do
189
+ original_allow_message_expectations_on_nil = RSpec::Mocks.configuration.allow_message_expectations_on_nil
190
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
191
+ allow(nil).to receive(:respond_to?)
192
+ .with(:load)
193
+ .and_return(true)
194
+
195
+ expect(JSON).to receive(:parse)
196
+ .with(body, { symbolize_names: true })
197
+ .and_return(result)
198
+
199
+ response = process(body)
200
+ expect(response.body).to eq(result)
201
+ ensure
202
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = original_allow_message_expectations_on_nil
203
+ end
204
+ end
205
+ end
117
206
  end
@@ -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,8 +11,10 @@ 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'] }
17
+ stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] }
16
18
  stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
17
19
  stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
18
20
  stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
@@ -79,6 +81,17 @@ RSpec.describe Faraday::Response::RaiseError do
79
81
  end
80
82
  end
81
83
 
84
+ it 'raises Faraday::RequestTimeoutError for 408 responses' do
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')
87
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
88
+ expect(ex.response[:status]).to eq(408)
89
+ expect(ex.response_status).to eq(408)
90
+ expect(ex.response_body).to eq('keep looking')
91
+ expect(ex.response_headers['X-Reason']).to eq('because')
92
+ end
93
+ end
94
+
82
95
  it 'raises Faraday::ConflictError for 409 responses' do
83
96
  expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
84
97
  expect(ex.message).to eq('the server responded with status 409')
@@ -101,6 +114,17 @@ RSpec.describe Faraday::Response::RaiseError do
101
114
  end
102
115
  end
103
116
 
117
+ it 'raises Faraday::TooManyRequestsError for 429 responses' do
118
+ 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')
120
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
121
+ expect(ex.response[:status]).to eq(429)
122
+ expect(ex.response_status).to eq(429)
123
+ expect(ex.response_body).to eq('keep looking')
124
+ expect(ex.response_headers['X-Reason']).to eq('because')
125
+ end
126
+ end
127
+
104
128
  it 'raises Faraday::NilStatusError for nil status in response' do
105
129
  expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
106
130
  expect(ex.message).to eq('http status could not be derived from the server response')
@@ -137,7 +161,7 @@ RSpec.describe Faraday::Response::RaiseError do
137
161
  describe 'request info' do
138
162
  let(:conn) do
139
163
  Faraday.new do |b|
140
- b.response :raise_error
164
+ b.response :raise_error, **middleware_options
141
165
  b.adapter :test do |stub|
142
166
  stub.post(url, request_body, request_headers) do
143
167
  [400, { 'X-Reason' => 'because' }, 'keep looking']
@@ -145,6 +169,7 @@ RSpec.describe Faraday::Response::RaiseError do
145
169
  end
146
170
  end
147
171
  end
172
+ let(:middleware_options) { {} }
148
173
  let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
149
174
  let(:request_headers) { { 'Authorization' => 'Basic 123' } }
150
175
  let(:url_path) { 'request' }
@@ -168,5 +193,19 @@ RSpec.describe Faraday::Response::RaiseError do
168
193
  expect(ex.response[:request][:body]).to eq(request_body)
169
194
  end
170
195
  end
196
+
197
+ context 'when the include_request option is set to false' do
198
+ let(:middleware_options) { { include_request: false } }
199
+
200
+ it 'does not include request info in the exception' do
201
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
202
+ expect(ex.response.keys).to contain_exactly(
203
+ :status,
204
+ :headers,
205
+ :body
206
+ )
207
+ end
208
+ end
209
+ end
171
210
  end
172
211
  end