faraday 1.10.4 → 2.13.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +198 -4
  3. data/LICENSE.md +1 -1
  4. data/README.md +34 -20
  5. data/Rakefile +6 -1
  6. data/examples/client_spec.rb +41 -19
  7. data/examples/client_test.rb +48 -22
  8. data/lib/faraday/adapter/test.rb +62 -13
  9. data/lib/faraday/adapter.rb +6 -10
  10. data/lib/faraday/connection.rb +72 -150
  11. data/lib/faraday/encoders/nested_params_encoder.rb +14 -7
  12. data/lib/faraday/error.rb +24 -5
  13. data/lib/faraday/logging/formatter.rb +29 -16
  14. data/lib/faraday/middleware.rb +43 -2
  15. data/lib/faraday/middleware_registry.rb +17 -63
  16. data/lib/faraday/options/connection_options.rb +7 -6
  17. data/lib/faraday/options/env.rb +85 -62
  18. data/lib/faraday/options/proxy_options.rb +11 -5
  19. data/lib/faraday/options/request_options.rb +7 -6
  20. data/lib/faraday/options/ssl_options.rb +62 -45
  21. data/lib/faraday/options.rb +7 -6
  22. data/lib/faraday/rack_builder.rb +43 -40
  23. data/lib/faraday/request/authorization.rb +33 -41
  24. data/lib/faraday/request/instrumentation.rb +5 -1
  25. data/lib/faraday/request/json.rb +18 -3
  26. data/lib/faraday/request/url_encoded.rb +5 -1
  27. data/lib/faraday/request.rb +15 -30
  28. data/lib/faraday/response/json.rb +25 -5
  29. data/lib/faraday/response/logger.rb +11 -3
  30. data/lib/faraday/response/raise_error.rb +45 -18
  31. data/lib/faraday/response.rb +9 -21
  32. data/lib/faraday/utils/headers.rb +15 -4
  33. data/lib/faraday/utils.rb +11 -7
  34. data/lib/faraday/version.rb +1 -1
  35. data/lib/faraday.rb +8 -44
  36. data/spec/faraday/adapter/test_spec.rb +65 -0
  37. data/spec/faraday/connection_spec.rb +165 -93
  38. data/spec/faraday/error_spec.rb +39 -6
  39. data/spec/faraday/middleware_registry_spec.rb +31 -0
  40. data/spec/faraday/middleware_spec.rb +161 -0
  41. data/spec/faraday/options/env_spec.rb +8 -2
  42. data/spec/faraday/options/options_spec.rb +1 -1
  43. data/spec/faraday/options/proxy_options_spec.rb +35 -0
  44. data/spec/faraday/params_encoders/nested_spec.rb +10 -1
  45. data/spec/faraday/rack_builder_spec.rb +26 -54
  46. data/spec/faraday/request/authorization_spec.rb +50 -28
  47. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  48. data/spec/faraday/request/json_spec.rb +88 -0
  49. data/spec/faraday/request/url_encoded_spec.rb +12 -2
  50. data/spec/faraday/request_spec.rb +5 -15
  51. data/spec/faraday/response/json_spec.rb +93 -6
  52. data/spec/faraday/response/logger_spec.rb +77 -4
  53. data/spec/faraday/response/raise_error_spec.rb +111 -5
  54. data/spec/faraday/response_spec.rb +3 -1
  55. data/spec/faraday/utils/headers_spec.rb +31 -4
  56. data/spec/faraday/utils_spec.rb +65 -1
  57. data/spec/faraday_spec.rb +10 -4
  58. data/spec/spec_helper.rb +5 -6
  59. data/spec/support/fake_safe_buffer.rb +1 -1
  60. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  61. data/spec/support/helper_methods.rb +0 -37
  62. data/spec/support/shared_examples/adapter.rb +2 -2
  63. data/spec/support/shared_examples/request_method.rb +22 -21
  64. metadata +24 -149
  65. data/lib/faraday/adapter/typhoeus.rb +0 -15
  66. data/lib/faraday/autoload.rb +0 -89
  67. data/lib/faraday/dependency_loader.rb +0 -39
  68. data/lib/faraday/deprecate.rb +0 -110
  69. data/lib/faraday/request/basic_authentication.rb +0 -20
  70. data/lib/faraday/request/token_authentication.rb +0 -20
  71. data/spec/faraday/adapter/em_http_spec.rb +0 -49
  72. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
  73. data/spec/faraday/adapter/excon_spec.rb +0 -49
  74. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  75. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  76. data/spec/faraday/adapter/patron_spec.rb +0 -18
  77. data/spec/faraday/adapter/rack_spec.rb +0 -8
  78. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  79. data/spec/faraday/composite_read_io_spec.rb +0 -80
  80. data/spec/faraday/deprecate_spec.rb +0 -147
  81. data/spec/faraday/response/middleware_spec.rb +0 -68
  82. data/spec/support/webmock_rack_app.rb +0 -68
@@ -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
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  RSpec.describe Faraday::Request::UrlEncoded do
4
6
  let(:conn) do
5
7
  Faraday.new do |b|
6
- b.request :multipart
7
8
  b.request :url_encoded
8
9
  b.adapter :test do |stub|
9
10
  stub.post('/echo') do |env|
10
11
  posted_as = env[:request_headers]['Content-Type']
11
- [200, { 'Content-Type' => posted_as }, env[:body]]
12
+ body = env[:body]
13
+ if body.respond_to?(:read)
14
+ body = body.read
15
+ end
16
+ [200, { 'Content-Type' => posted_as }, body]
12
17
  end
13
18
  end
14
19
  end
@@ -68,6 +73,11 @@ RSpec.describe Faraday::Request::UrlEncoded do
68
73
  expect(response.body).to eq('a%5Bb%5D%5Bc%5D%5B%5D=d')
69
74
  end
70
75
 
76
+ it 'works with files' do
77
+ response = conn.post('/echo', StringIO.new('str=apple'))
78
+ expect(response.body).to eq('str=apple')
79
+ end
80
+
71
81
  context 'customising default_space_encoding' do
72
82
  around do |example|
73
83
  Faraday::Utils.default_space_encoding = '%20'
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe Faraday::Request do
4
4
  let(:conn) do
5
- Faraday.new(url: 'http://sushi.com/api',
5
+ Faraday.new(url: 'http://httpbingo.org/api',
6
6
  headers: { 'Mime-Version' => '1.0' },
7
7
  request: { oauth: { consumer_key: 'anonymous' } })
8
8
  end
@@ -14,6 +14,7 @@ RSpec.describe Faraday::Request do
14
14
  context 'when nothing particular is configured' do
15
15
  it { expect(subject.http_method).to eq(:get) }
16
16
  it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
17
+ it { expect(subject.to_env(conn).ssl.verify_hostname).to be_falsey }
17
18
  end
18
19
 
19
20
  context 'when HTTP method is post' do
@@ -22,23 +23,12 @@ RSpec.describe Faraday::Request do
22
23
  it { expect(subject.http_method).to eq(:post) }
23
24
  end
24
25
 
25
- describe 'deprecate method for HTTP method' do
26
- let(:http_method) { :post }
27
- let(:expected_warning) do
28
- %r{NOTE: Faraday::Request#method is deprecated; use http_method instead\. It will be removed in or after version 2.0 \nFaraday::Request#method called from .+/spec/faraday/request_spec.rb:\d+.}
29
- end
30
-
31
- it { expect(subject.method).to eq(:post) }
32
-
33
- it { expect { subject.method }.to output(expected_warning).to_stderr }
34
- end
35
-
36
26
  context 'when setting the url on setup with a URI' do
37
27
  let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
38
28
 
39
29
  it { expect(subject.path).to eq(URI.parse('foo.json')) }
40
30
  it { expect(subject.params).to eq('a' => '1') }
41
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
31
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
42
32
  end
43
33
 
44
34
  context 'when setting the url on setup with a string path and params' do
@@ -46,7 +36,7 @@ RSpec.describe Faraday::Request do
46
36
 
47
37
  it { expect(subject.path).to eq('foo.json') }
48
38
  it { expect(subject.params).to eq('a' => 1) }
49
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
39
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
50
40
  end
51
41
 
52
42
  context 'when setting the url on setup with a path including params' do
@@ -54,7 +44,7 @@ RSpec.describe Faraday::Request do
54
44
 
55
45
  it { expect(subject.path).to eq('foo.json') }
56
46
  it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
57
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1&b=2') }
47
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
58
48
  end
59
49
 
60
50
  context 'when setting a header on setup with []= syntax' do
@@ -76,12 +76,10 @@ RSpec.describe Faraday::Response::Json, type: :response do
76
76
  end
77
77
 
78
78
  it 'includes the response on the ParsingError instance' do
79
- begin
80
- process('{') { |env| env[:response] = Faraday::Response.new }
81
- raise 'Parsing should have failed.'
82
- rescue Faraday::ParsingError => e
83
- expect(e.response).to be_a(Faraday::Response)
84
- end
79
+ process('{') { |env| env[:response] = Faraday::Response.new }
80
+ raise 'Parsing should have failed.'
81
+ rescue Faraday::ParsingError => e
82
+ expect(e.response).to be_a(Faraday::Response)
85
83
  end
86
84
 
87
85
  context 'HEAD responses' do
@@ -116,4 +114,93 @@ RSpec.describe Faraday::Response::Json, type: :response do
116
114
  expect(response.body).to eq(result)
117
115
  end
118
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
119
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
@@ -54,6 +55,26 @@ RSpec.describe Faraday::Response::Logger do
54
55
  end
55
56
  end
56
57
 
58
+ context 'when logger with program name' do
59
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
60
+
61
+ it 'logs with program name' do
62
+ conn.get '/hello'
63
+
64
+ expect(string_io.string).to match('-- my_best_program: request:')
65
+ expect(string_io.string).to match('-- my_best_program: response:')
66
+ end
67
+ end
68
+
69
+ context 'when logger without program name' do
70
+ it 'logs without program name' do
71
+ conn.get '/hello'
72
+
73
+ expect(string_io.string).to match('-- : request:')
74
+ expect(string_io.string).to match('-- : response:')
75
+ end
76
+ end
77
+
57
78
  context 'with default formatter' do
58
79
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
59
80
 
@@ -64,6 +85,15 @@ RSpec.describe Faraday::Response::Logger do
64
85
  expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65
86
  conn.get '/hello'
66
87
  end
88
+
89
+ context 'when no route' do
90
+ it 'delegates logging to the formatter' do
91
+ expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
92
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
93
+
94
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
95
+ end
96
+ end
67
97
  end
68
98
 
69
99
  context 'with custom formatter' do
@@ -94,6 +124,16 @@ RSpec.describe Faraday::Response::Logger do
94
124
  expect(string_io.string).to match('GET http:/hello')
95
125
  end
96
126
 
127
+ it 'logs status' do
128
+ conn.get '/hello', nil, accept: 'text/html'
129
+ expect(string_io.string).to match('Status 200')
130
+ end
131
+
132
+ it 'does not log error message by default' do
133
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
134
+ expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
135
+ end
136
+
97
137
  it 'logs request headers by default' do
98
138
  conn.get '/hello', nil, accept: 'text/html'
99
139
  expect(string_io.string).to match(%(Accept: "text/html))
@@ -149,7 +189,7 @@ RSpec.describe Faraday::Response::Logger do
149
189
  context 'when logging request body' do
150
190
  let(:logger_options) { { bodies: { request: true } } }
151
191
 
152
- it 'log only request body' do
192
+ it 'logs only request body' do
153
193
  conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
154
194
  expect(string_io.string).to match(%(name=Tamago))
155
195
  expect(string_io.string).not_to match(%(pebbles))
@@ -159,7 +199,7 @@ RSpec.describe Faraday::Response::Logger do
159
199
  context 'when logging response body' do
160
200
  let(:logger_options) { { bodies: { response: true } } }
161
201
 
162
- it 'log only response body' do
202
+ it 'logs only response body' do
163
203
  conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
164
204
  expect(string_io.string).to match(%(pebbles))
165
205
  expect(string_io.string).not_to match(%(name=Hamachi))
@@ -169,13 +209,13 @@ RSpec.describe Faraday::Response::Logger do
169
209
  context 'when logging request and response bodies' do
170
210
  let(:logger_options) { { bodies: true } }
171
211
 
172
- it 'log request and response body' do
212
+ it 'logs request and response body' do
173
213
  conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
174
214
  expect(string_io.string).to match(%(name=Ebi))
175
215
  expect(string_io.string).to match(%(pebbles))
176
216
  end
177
217
 
178
- it 'log response body object' do
218
+ it 'logs response body object' do
179
219
  conn.get '/rubbles', nil, accept: 'text/html'
180
220
  expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
181
221
  end
@@ -188,6 +228,39 @@ RSpec.describe Faraday::Response::Logger do
188
228
  end
189
229
  end
190
230
 
231
+ context 'when bodies are logged by default' do
232
+ before do
233
+ described_class.default_options = { bodies: true }
234
+ end
235
+
236
+ it 'logs response body' do
237
+ conn.post '/ohai'
238
+ expect(string_io.string).to match(%(fred))
239
+ end
240
+
241
+ after do
242
+ described_class.default_options = { bodies: false }
243
+ end
244
+ end
245
+
246
+ context 'when logging errors' do
247
+ let(:logger_options) { { errors: true } }
248
+
249
+ it 'logs error message' do
250
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
251
+ expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
252
+ end
253
+ end
254
+
255
+ context 'when logging headers and errors' do
256
+ let(:logger_options) { { headers: true, errors: true } }
257
+
258
+ it 'logs error message' do
259
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
260
+ expect(string_io.string).to match(%(Failed to open TCP connection))
261
+ end
262
+ end
263
+
191
264
  context 'when using log_level' do
192
265
  let(:logger_options) { { bodies: true, log_level: :debug } }
193
266
 
@@ -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,21 +161,24 @@ 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
- stub.post('request?full=true', request_body, request_headers) do
166
+ stub.post(url, request_body, request_headers) do
143
167
  [400, { 'X-Reason' => 'because' }, 'keep looking']
144
168
  end
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' } }
175
+ let(:url_path) { 'request' }
176
+ let(:query_params) { 'full=true' }
177
+ let(:url) { "#{url_path}?#{query_params}" }
150
178
 
151
179
  subject(:perform_request) do
152
- conn.post 'request' do |req|
180
+ conn.post url do |req|
153
181
  req.headers['Authorization'] = 'Basic 123'
154
- req.params[:full] = true
155
182
  req.body = request_body
156
183
  end
157
184
  end
@@ -159,11 +186,90 @@ RSpec.describe Faraday::Response::RaiseError do
159
186
  it 'returns the request info in the exception' do
160
187
  expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
161
188
  expect(ex.response[:request][:method]).to eq(:post)
162
- expect(ex.response[:request][:url_path]).to eq('/request')
189
+ expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
190
+ expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
163
191
  expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
164
192
  expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
165
193
  expect(ex.response[:request][:body]).to eq(request_body)
166
194
  end
167
195
  end
196
+
197
+ describe 'DEFAULT_OPTION: include_request' do
198
+ before(:each) do
199
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
200
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
201
+ end
202
+
203
+ after(:all) do
204
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
205
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
206
+ end
207
+
208
+ context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
209
+ it 'includes request info in the exception' do
210
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
211
+ expect(ex.response.keys).to contain_exactly(
212
+ :status,
213
+ :headers,
214
+ :body,
215
+ :request
216
+ )
217
+ end
218
+ end
219
+ end
220
+
221
+ context 'when application sets default_options `include_request: false`' do
222
+ before(:each) do
223
+ Faraday::Response::RaiseError.default_options = { include_request: false }
224
+ end
225
+
226
+ context 'and when include_request option is omitted' do
227
+ it 'does not include request info in the exception' do
228
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
229
+ expect(ex.response.keys).to contain_exactly(
230
+ :status,
231
+ :headers,
232
+ :body
233
+ )
234
+ end
235
+ end
236
+ end
237
+
238
+ context 'and when include_request option is explicitly set for instance' do
239
+ let(:middleware_options) { { include_request: true } }
240
+
241
+ it 'includes request info in the exception' do
242
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
243
+ expect(ex.response.keys).to contain_exactly(
244
+ :status,
245
+ :headers,
246
+ :body,
247
+ :request
248
+ )
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ describe 'allowing certain status codes' do
257
+ let(:conn) do
258
+ Faraday.new do |b|
259
+ b.response :raise_error, allowed_statuses: [404]
260
+ b.adapter :test do |stub|
261
+ stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
262
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
263
+ end
264
+ end
265
+ end
266
+
267
+ it 'raises an error for status codes that are not explicitly allowed' do
268
+ expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
269
+ end
270
+
271
+ it 'does not raise an error for allowed status codes' do
272
+ expect { conn.get('not-found') }.not_to raise_error
273
+ end
168
274
  end
169
275
  end
@@ -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