faraday 2.5.2 → 2.9.2

Sign up to get free protection for your applications and to get access to all the features.
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