faraday 1.4.1 → 2.14.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 (94) 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 +67 -13
  7. data/examples/client_test.rb +81 -16
  8. data/lib/faraday/adapter/test.rb +117 -52
  9. data/lib/faraday/adapter.rb +12 -20
  10. data/lib/faraday/connection.rb +86 -134
  11. data/lib/faraday/encoders/flat_params_encoder.rb +3 -2
  12. data/lib/faraday/encoders/nested_params_encoder.rb +15 -7
  13. data/lib/faraday/error.rb +65 -15
  14. data/lib/faraday/logging/formatter.rb +30 -17
  15. data/lib/faraday/middleware.rb +44 -3
  16. data/lib/faraday/middleware_registry.rb +17 -63
  17. data/lib/faraday/options/connection_options.rb +7 -6
  18. data/lib/faraday/options/env.rb +88 -65
  19. data/lib/faraday/options/proxy_options.rb +17 -6
  20. data/lib/faraday/options/request_options.rb +8 -7
  21. data/lib/faraday/options/ssl_options.rb +62 -45
  22. data/lib/faraday/options.rb +8 -7
  23. data/lib/faraday/rack_builder.rb +46 -47
  24. data/lib/faraday/request/authorization.rb +37 -38
  25. data/lib/faraday/request/instrumentation.rb +5 -1
  26. data/lib/faraday/request/json.rb +70 -0
  27. data/lib/faraday/request/url_encoded.rb +5 -1
  28. data/lib/faraday/request.rb +20 -37
  29. data/lib/faraday/response/json.rb +74 -0
  30. data/lib/faraday/response/logger.rb +13 -7
  31. data/lib/faraday/response/raise_error.rb +45 -18
  32. data/lib/faraday/response.rb +15 -21
  33. data/lib/faraday/utils/headers.rb +18 -7
  34. data/lib/faraday/utils.rb +11 -7
  35. data/lib/faraday/version.rb +1 -1
  36. data/lib/faraday.rb +12 -32
  37. data/spec/faraday/adapter/test_spec.rb +182 -0
  38. data/spec/faraday/connection_spec.rb +219 -92
  39. data/spec/faraday/error_spec.rb +122 -7
  40. data/spec/faraday/middleware_registry_spec.rb +31 -0
  41. data/spec/faraday/middleware_spec.rb +163 -2
  42. data/spec/faraday/options/env_spec.rb +8 -2
  43. data/spec/faraday/options/options_spec.rb +1 -1
  44. data/spec/faraday/options/proxy_options_spec.rb +42 -0
  45. data/spec/faraday/params_encoders/nested_spec.rb +10 -1
  46. data/spec/faraday/rack_builder_spec.rb +26 -54
  47. data/spec/faraday/request/authorization_spec.rb +54 -24
  48. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  49. data/spec/faraday/request/json_spec.rb +199 -0
  50. data/spec/faraday/request/url_encoded_spec.rb +12 -2
  51. data/spec/faraday/request_spec.rb +14 -15
  52. data/spec/faraday/response/json_spec.rb +206 -0
  53. data/spec/faraday/response/logger_spec.rb +84 -5
  54. data/spec/faraday/response/raise_error_spec.rb +133 -16
  55. data/spec/faraday/response_spec.rb +10 -1
  56. data/spec/faraday/utils/headers_spec.rb +31 -4
  57. data/spec/faraday/utils_spec.rb +66 -2
  58. data/spec/faraday_spec.rb +10 -4
  59. data/spec/spec_helper.rb +6 -5
  60. data/spec/support/fake_safe_buffer.rb +1 -1
  61. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  62. data/spec/support/helper_methods.rb +0 -37
  63. data/spec/support/shared_examples/adapter.rb +2 -2
  64. data/spec/support/shared_examples/request_method.rb +22 -21
  65. metadata +27 -81
  66. data/lib/faraday/adapter/em_http.rb +0 -289
  67. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
  68. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
  69. data/lib/faraday/adapter/em_synchrony.rb +0 -153
  70. data/lib/faraday/adapter/httpclient.rb +0 -152
  71. data/lib/faraday/adapter/patron.rb +0 -132
  72. data/lib/faraday/adapter/rack.rb +0 -75
  73. data/lib/faraday/adapter/typhoeus.rb +0 -15
  74. data/lib/faraday/autoload.rb +0 -92
  75. data/lib/faraday/dependency_loader.rb +0 -37
  76. data/lib/faraday/file_part.rb +0 -128
  77. data/lib/faraday/param_part.rb +0 -53
  78. data/lib/faraday/request/basic_authentication.rb +0 -20
  79. data/lib/faraday/request/multipart.rb +0 -106
  80. data/lib/faraday/request/retry.rb +0 -239
  81. data/lib/faraday/request/token_authentication.rb +0 -20
  82. data/spec/faraday/adapter/em_http_spec.rb +0 -47
  83. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -16
  84. data/spec/faraday/adapter/excon_spec.rb +0 -49
  85. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  86. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  87. data/spec/faraday/adapter/patron_spec.rb +0 -18
  88. data/spec/faraday/adapter/rack_spec.rb +0 -8
  89. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  90. data/spec/faraday/composite_read_io_spec.rb +0 -80
  91. data/spec/faraday/request/multipart_spec.rb +0 -302
  92. data/spec/faraday/request/retry_spec.rb +0 -242
  93. data/spec/faraday/response/middleware_spec.rb +0 -68
  94. data/spec/support/webmock_rack_app.rb +0 -68
@@ -21,10 +21,12 @@ RSpec.describe Faraday::Response::Logger do
21
21
  stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] }
22
22
  stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] }
23
23
  stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] }
24
+ stubs.get('/8bit') { [200, { 'Content-Type' => 'text/html' }, (+'café!').force_encoding(Encoding::ASCII_8BIT)] }
24
25
  stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] }
25
26
  stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
26
27
  stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
27
28
  stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
29
+ stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
28
30
  end
29
31
  end
30
32
  end
@@ -54,6 +56,26 @@ RSpec.describe Faraday::Response::Logger do
54
56
  end
55
57
  end
56
58
 
59
+ context 'when logger with program name' do
60
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
61
+
62
+ it 'logs with program name' do
63
+ conn.get '/hello'
64
+
65
+ expect(string_io.string).to match('-- my_best_program: request:')
66
+ expect(string_io.string).to match('-- my_best_program: response:')
67
+ end
68
+ end
69
+
70
+ context 'when logger without program name' do
71
+ it 'logs without program name' do
72
+ conn.get '/hello'
73
+
74
+ expect(string_io.string).to match('-- : request:')
75
+ expect(string_io.string).to match('-- : response:')
76
+ end
77
+ end
78
+
57
79
  context 'with default formatter' do
58
80
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
59
81
 
@@ -64,6 +86,15 @@ RSpec.describe Faraday::Response::Logger do
64
86
  expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65
87
  conn.get '/hello'
66
88
  end
89
+
90
+ context 'when no route' do
91
+ it 'delegates logging to the formatter' do
92
+ expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
93
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
94
+
95
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
96
+ end
97
+ end
67
98
  end
68
99
 
69
100
  context 'with custom formatter' do
@@ -94,6 +125,16 @@ RSpec.describe Faraday::Response::Logger do
94
125
  expect(string_io.string).to match('GET http:/hello')
95
126
  end
96
127
 
128
+ it 'logs status' do
129
+ conn.get '/hello', nil, accept: 'text/html'
130
+ expect(string_io.string).to match('Status 200')
131
+ end
132
+
133
+ it 'does not log error message by default' do
134
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
135
+ expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
136
+ end
137
+
97
138
  it 'logs request headers by default' do
98
139
  conn.get '/hello', nil, accept: 'text/html'
99
140
  expect(string_io.string).to match(%(Accept: "text/html))
@@ -149,7 +190,7 @@ RSpec.describe Faraday::Response::Logger do
149
190
  context 'when logging request body' do
150
191
  let(:logger_options) { { bodies: { request: true } } }
151
192
 
152
- it 'log only request body' do
193
+ it 'logs only request body' do
153
194
  conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
154
195
  expect(string_io.string).to match(%(name=Tamago))
155
196
  expect(string_io.string).not_to match(%(pebbles))
@@ -159,7 +200,7 @@ RSpec.describe Faraday::Response::Logger do
159
200
  context 'when logging response body' do
160
201
  let(:logger_options) { { bodies: { response: true } } }
161
202
 
162
- it 'log only response body' do
203
+ it 'logs only response body' do
163
204
  conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
164
205
  expect(string_io.string).to match(%(pebbles))
165
206
  expect(string_io.string).not_to match(%(name=Hamachi))
@@ -169,15 +210,15 @@ RSpec.describe Faraday::Response::Logger do
169
210
  context 'when logging request and response bodies' do
170
211
  let(:logger_options) { { bodies: true } }
171
212
 
172
- it 'log request and response body' do
213
+ it 'logs request and response body' do
173
214
  conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
174
215
  expect(string_io.string).to match(%(name=Ebi))
175
216
  expect(string_io.string).to match(%(pebbles))
176
217
  end
177
218
 
178
- it 'log response body object' do
219
+ it 'logs response body object' do
179
220
  conn.get '/rubbles', nil, accept: 'text/html'
180
- expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
221
+ expect(string_io.string).to match(%(["Barney", "Betty", "Bam Bam"]\n))
181
222
  end
182
223
 
183
224
  it 'logs filter body' do
@@ -188,6 +229,44 @@ RSpec.describe Faraday::Response::Logger do
188
229
  end
189
230
  end
190
231
 
232
+ context 'when bodies are logged by default' do
233
+ before do
234
+ described_class.default_options = { bodies: true }
235
+ end
236
+
237
+ it 'logs response body' do
238
+ conn.post '/ohai'
239
+ expect(string_io.string).to match(%(fred))
240
+ end
241
+
242
+ it 'converts to UTF-8' do
243
+ conn.get '/8bit'
244
+ expect(string_io.string).to match(%(caf��!))
245
+ end
246
+
247
+ after do
248
+ described_class.default_options = { bodies: false }
249
+ end
250
+ end
251
+
252
+ context 'when logging errors' do
253
+ let(:logger_options) { { errors: true } }
254
+
255
+ it 'logs error message' do
256
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
257
+ expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
258
+ end
259
+ end
260
+
261
+ context 'when logging headers and errors' do
262
+ let(:logger_options) { { headers: true, errors: true } }
263
+
264
+ it 'logs error message' do
265
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
266
+ expect(string_io.string).to match(%(Failed to open TCP connection))
267
+ end
268
+ end
269
+
191
270
  context 'when using log_level' do
192
271
  let(:logger_options) { { bodies: true, log_level: :debug } }
193
272
 
@@ -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
- stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
16
+ stub.get('unprocessable-content') { [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'] }
@@ -26,7 +28,7 @@ RSpec.describe Faraday::Response::RaiseError do
26
28
 
27
29
  it 'raises Faraday::BadRequestError for 400 responses' do
28
30
  expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
29
- expect(ex.message).to eq('the server responded with status 400')
31
+ expect(ex.message).to eq('the server responded with status 400 for GET http:/bad-request')
30
32
  expect(ex.response[:headers]['X-Reason']).to eq('because')
31
33
  expect(ex.response[:status]).to eq(400)
32
34
  expect(ex.response_status).to eq(400)
@@ -37,7 +39,7 @@ RSpec.describe Faraday::Response::RaiseError do
37
39
 
38
40
  it 'raises Faraday::UnauthorizedError for 401 responses' do
39
41
  expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
40
- expect(ex.message).to eq('the server responded with status 401')
42
+ expect(ex.message).to eq('the server responded with status 401 for GET http:/unauthorized')
41
43
  expect(ex.response[:headers]['X-Reason']).to eq('because')
42
44
  expect(ex.response[:status]).to eq(401)
43
45
  expect(ex.response_status).to eq(401)
@@ -48,7 +50,7 @@ RSpec.describe Faraday::Response::RaiseError do
48
50
 
49
51
  it 'raises Faraday::ForbiddenError for 403 responses' do
50
52
  expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
51
- expect(ex.message).to eq('the server responded with status 403')
53
+ expect(ex.message).to eq('the server responded with status 403 for GET http:/forbidden')
52
54
  expect(ex.response[:headers]['X-Reason']).to eq('because')
53
55
  expect(ex.response[:status]).to eq(403)
54
56
  expect(ex.response_status).to eq(403)
@@ -59,7 +61,7 @@ RSpec.describe Faraday::Response::RaiseError do
59
61
 
60
62
  it 'raises Faraday::ResourceNotFound for 404 responses' do
61
63
  expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
62
- expect(ex.message).to eq('the server responded with status 404')
64
+ expect(ex.message).to eq('the server responded with status 404 for GET http:/not-found')
63
65
  expect(ex.response[:headers]['X-Reason']).to eq('because')
64
66
  expect(ex.response[:status]).to eq(404)
65
67
  expect(ex.response_status).to eq(404)
@@ -79,9 +81,20 @@ 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 for GET http:/request-timeout')
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
- expect(ex.message).to eq('the server responded with status 409')
97
+ expect(ex.message).to eq('the server responded with status 409 for GET http:/conflict')
85
98
  expect(ex.response[:headers]['X-Reason']).to eq('because')
86
99
  expect(ex.response[:status]).to eq(409)
87
100
  expect(ex.response_status).to eq(409)
@@ -90,9 +103,20 @@ RSpec.describe Faraday::Response::RaiseError do
90
103
  end
91
104
  end
92
105
 
93
- it 'raises Faraday::UnprocessableEntityError for 422 responses' do
94
- expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
95
- expect(ex.message).to eq('the server responded with status 422')
106
+ it 'raises legacy Faraday::UnprocessableEntityError for 422 responses' do
107
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
108
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
109
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
110
+ expect(ex.response[:status]).to eq(422)
111
+ expect(ex.response_status).to eq(422)
112
+ expect(ex.response_body).to eq('keep looking')
113
+ expect(ex.response_headers['X-Reason']).to eq('because')
114
+ end
115
+ end
116
+
117
+ it 'raises Faraday::UnprocessableContentError for 422 responses' do
118
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableContentError) do |ex|
119
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
96
120
  expect(ex.response[:headers]['X-Reason']).to eq('because')
97
121
  expect(ex.response[:status]).to eq(422)
98
122
  expect(ex.response_status).to eq(422)
@@ -101,6 +125,17 @@ RSpec.describe Faraday::Response::RaiseError do
101
125
  end
102
126
  end
103
127
 
128
+ it 'raises Faraday::TooManyRequestsError for 429 responses' do
129
+ expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
130
+ expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
131
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
132
+ expect(ex.response[:status]).to eq(429)
133
+ expect(ex.response_status).to eq(429)
134
+ expect(ex.response_body).to eq('keep looking')
135
+ expect(ex.response_headers['X-Reason']).to eq('because')
136
+ end
137
+ end
138
+
104
139
  it 'raises Faraday::NilStatusError for nil status in response' do
105
140
  expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
106
141
  expect(ex.message).to eq('http status could not be derived from the server response')
@@ -114,7 +149,7 @@ RSpec.describe Faraday::Response::RaiseError do
114
149
 
115
150
  it 'raises Faraday::ClientError for other 4xx responses' do
116
151
  expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
117
- expect(ex.message).to eq('the server responded with status 499')
152
+ expect(ex.message).to eq('the server responded with status 499 for GET http:/4xx')
118
153
  expect(ex.response[:headers]['X-Reason']).to eq('because')
119
154
  expect(ex.response[:status]).to eq(499)
120
155
  expect(ex.response_status).to eq(499)
@@ -125,7 +160,7 @@ RSpec.describe Faraday::Response::RaiseError do
125
160
 
126
161
  it 'raises Faraday::ServerError for 500 responses' do
127
162
  expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
128
- expect(ex.message).to eq('the server responded with status 500')
163
+ expect(ex.message).to eq('the server responded with status 500 for GET http:/server-error')
129
164
  expect(ex.response[:headers]['X-Error']).to eq('bailout')
130
165
  expect(ex.response[:status]).to eq(500)
131
166
  expect(ex.response_status).to eq(500)
@@ -137,21 +172,24 @@ RSpec.describe Faraday::Response::RaiseError do
137
172
  describe 'request info' do
138
173
  let(:conn) do
139
174
  Faraday.new do |b|
140
- b.response :raise_error
175
+ b.response :raise_error, **middleware_options
141
176
  b.adapter :test do |stub|
142
- stub.post('request?full=true', request_body, request_headers) do
177
+ stub.post(url, request_body, request_headers) do
143
178
  [400, { 'X-Reason' => 'because' }, 'keep looking']
144
179
  end
145
180
  end
146
181
  end
147
182
  end
183
+ let(:middleware_options) { {} }
148
184
  let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
149
185
  let(:request_headers) { { 'Authorization' => 'Basic 123' } }
186
+ let(:url_path) { 'request' }
187
+ let(:query_params) { 'full=true' }
188
+ let(:url) { "#{url_path}?#{query_params}" }
150
189
 
151
190
  subject(:perform_request) do
152
- conn.post 'request' do |req|
191
+ conn.post url do |req|
153
192
  req.headers['Authorization'] = 'Basic 123'
154
- req.params[:full] = true
155
193
  req.body = request_body
156
194
  end
157
195
  end
@@ -159,11 +197,90 @@ RSpec.describe Faraday::Response::RaiseError do
159
197
  it 'returns the request info in the exception' do
160
198
  expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
161
199
  expect(ex.response[:request][:method]).to eq(:post)
162
- expect(ex.response[:request][:url_path]).to eq('/request')
200
+ expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
201
+ expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
163
202
  expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
164
203
  expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
165
204
  expect(ex.response[:request][:body]).to eq(request_body)
166
205
  end
167
206
  end
207
+
208
+ describe 'DEFAULT_OPTION: include_request' do
209
+ before(:each) do
210
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
211
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
212
+ end
213
+
214
+ after(:all) do
215
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
216
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
217
+ end
218
+
219
+ context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
220
+ it 'includes request info in the exception' do
221
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
222
+ expect(ex.response.keys).to contain_exactly(
223
+ :status,
224
+ :headers,
225
+ :body,
226
+ :request
227
+ )
228
+ end
229
+ end
230
+ end
231
+
232
+ context 'when application sets default_options `include_request: false`' do
233
+ before(:each) do
234
+ Faraday::Response::RaiseError.default_options = { include_request: false }
235
+ end
236
+
237
+ context 'and when include_request option is omitted' do
238
+ it 'does not include request info in the exception' do
239
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
240
+ expect(ex.response.keys).to contain_exactly(
241
+ :status,
242
+ :headers,
243
+ :body
244
+ )
245
+ end
246
+ end
247
+ end
248
+
249
+ context 'and when include_request option is explicitly set for instance' do
250
+ let(:middleware_options) { { include_request: true } }
251
+
252
+ it 'includes request info in the exception' do
253
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
254
+ expect(ex.response.keys).to contain_exactly(
255
+ :status,
256
+ :headers,
257
+ :body,
258
+ :request
259
+ )
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ describe 'allowing certain status codes' do
268
+ let(:conn) do
269
+ Faraday.new do |b|
270
+ b.response :raise_error, allowed_statuses: [404]
271
+ b.adapter :test do |stub|
272
+ stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
273
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
274
+ end
275
+ end
276
+ end
277
+
278
+ it 'raises an error for status codes that are not explicitly allowed' do
279
+ expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
280
+ end
281
+
282
+ it 'does not raise an error for allowed status codes' do
283
+ expect { conn.get('not-found') }.not_to raise_error
284
+ end
168
285
  end
169
286
  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
 
@@ -13,6 +13,7 @@ RSpec.describe Faraday::Response do
13
13
  it { expect(subject.success?).to be_falsey }
14
14
  it { expect(subject.status).to eq(404) }
15
15
  it { expect(subject.body).to eq('yikes') }
16
+ it { expect(subject.url).to eq(URI('https://lostisland.github.io/faraday')) }
16
17
  it { expect(subject.headers['Content-Type']).to eq('text/plain') }
17
18
  it { expect(subject['content-type']).to eq('text/plain') }
18
19
 
@@ -30,6 +31,13 @@ RSpec.describe Faraday::Response do
30
31
  it { expect(hash[:status]).to eq(subject.status) }
31
32
  it { expect(hash[:response_headers]).to eq(subject.headers) }
32
33
  it { expect(hash[:body]).to eq(subject.body) }
34
+ it { expect(hash[:url]).to eq(subject.env.url) }
35
+
36
+ context 'when response is not finished' do
37
+ subject { Faraday::Response.new.to_hash }
38
+
39
+ it { is_expected.to eq({ status: nil, body: nil, response_headers: {}, url: nil }) }
40
+ end
33
41
  end
34
42
 
35
43
  describe 'marshal serialization support' do
@@ -45,6 +53,7 @@ RSpec.describe Faraday::Response do
45
53
  it { expect(loaded.env[:body]).to eq(env[:body]) }
46
54
  it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
47
55
  it { expect(loaded.env[:status]).to eq(env[:status]) }
56
+ it { expect(loaded.env[:url]).to eq(env[:url]) }
48
57
  end
49
58
 
50
59
  describe '#on_complete' do
@@ -56,27 +56,54 @@ RSpec.describe Faraday::Utils::Headers do
56
56
  it { expect(subject.delete('content-type')).to be_nil }
57
57
  end
58
58
 
59
- describe '#parse' do
60
- before { subject.parse(headers) }
59
+ describe '#dig' do
60
+ before { subject['Content-Type'] = 'application/json' }
61
+
62
+ it { expect(subject&.dig('Content-Type')).to eq('application/json') }
63
+ it { expect(subject&.dig('CONTENT-TYPE')).to eq('application/json') }
64
+ it { expect(subject&.dig(:content_type)).to eq('application/json') }
65
+ it { expect(subject&.dig('invalid')).to be_nil }
66
+ end
61
67
 
68
+ describe '#parse' do
62
69
  context 'when response headers leave http status line out' do
63
70
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
64
71
 
72
+ before { subject.parse(headers) }
73
+
65
74
  it { expect(subject.keys).to eq(%w[Content-Type]) }
66
75
  it { expect(subject['Content-Type']).to eq('text/html') }
67
76
  it { expect(subject['content-type']).to eq('text/html') }
68
77
  end
69
78
 
70
79
  context 'when response headers values include a colon' do
71
- let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n" }
80
+ let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://httpbingo.org/\r\n\r\n" }
72
81
 
73
- it { expect(subject['location']).to eq('http://sushi.com/') }
82
+ before { subject.parse(headers) }
83
+
84
+ it { expect(subject['location']).to eq('http://httpbingo.org/') }
74
85
  end
75
86
 
76
87
  context 'when response headers include a blank line' do
77
88
  let(:headers) { "HTTP/1.1 200 OK\r\n\r\nContent-Type: text/html\r\n\r\n" }
78
89
 
90
+ before { subject.parse(headers) }
91
+
79
92
  it { expect(subject['content-type']).to eq('text/html') }
80
93
  end
94
+
95
+ context 'when response headers include already stored keys' do
96
+ let(:headers) { "HTTP/1.1 200 OK\r\nX-Numbers: 123\r\n\r\n" }
97
+
98
+ before do
99
+ h = subject
100
+ h[:x_numbers] = 8
101
+ h.parse(headers)
102
+ end
103
+
104
+ it do
105
+ expect(subject[:x_numbers]).to eq('8, 123')
106
+ end
107
+ end
81
108
  end
82
109
  end
@@ -4,7 +4,7 @@ RSpec.describe Faraday::Utils do
4
4
  describe 'headers parsing' do
5
5
  let(:multi_response_headers) do
6
6
  "HTTP/1.x 500 OK\r\nContent-Type: text/html; charset=UTF-8\r\n" \
7
- "HTTP/1.x 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
7
+ "HTTP/1.x 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
8
8
  end
9
9
 
10
10
  it 'parse headers for aggregated responses' do
@@ -33,7 +33,7 @@ RSpec.describe Faraday::Utils do
33
33
  end
34
34
 
35
35
  it 'parses with URI' do
36
- with_default_uri_parser(::URI) do
36
+ with_default_uri_parser(URI) do
37
37
  uri = normalize(url)
38
38
  expect(uri.host).to eq('example.com')
39
39
  end
@@ -53,4 +53,68 @@ RSpec.describe Faraday::Utils do
53
53
  expect(headers).not_to have_key('authorization')
54
54
  end
55
55
  end
56
+
57
+ describe '.deep_merge!' do
58
+ let(:connection_options) { Faraday::ConnectionOptions.new }
59
+ let(:url) do
60
+ {
61
+ url: 'http://example.com/abc',
62
+ headers: { 'Mime-Version' => '1.0' },
63
+ request: { oauth: { consumer_key: 'anonymous' } },
64
+ ssl: { version: '2' }
65
+ }
66
+ end
67
+
68
+ it 'recursively merges the headers' do
69
+ connection_options.headers = { user_agent: 'My Agent 1.0' }
70
+ deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
71
+
72
+ expect(deep_merge.headers).to eq('Mime-Version' => '1.0', user_agent: 'My Agent 1.0')
73
+ end
74
+
75
+ context 'when a target hash has an Options Struct value' do
76
+ let(:request) do
77
+ {
78
+ params_encoder: nil,
79
+ proxy: nil,
80
+ bind: nil,
81
+ timeout: nil,
82
+ open_timeout: nil,
83
+ read_timeout: nil,
84
+ write_timeout: nil,
85
+ boundary: nil,
86
+ oauth: { consumer_key: 'anonymous' },
87
+ context: nil,
88
+ on_data: nil
89
+ }
90
+ end
91
+ let(:ssl) do
92
+ {
93
+ verify: nil,
94
+ ca_file: nil,
95
+ ca_path: nil,
96
+ verify_mode: nil,
97
+ cert_store: nil,
98
+ client_cert: nil,
99
+ client_key: nil,
100
+ certificate: nil,
101
+ private_key: nil,
102
+ verify_depth: nil,
103
+ version: '2',
104
+ min_version: nil,
105
+ max_version: nil,
106
+ verify_hostname: nil,
107
+ hostname: nil,
108
+ ciphers: nil
109
+ }
110
+ end
111
+
112
+ it 'does not overwrite an Options Struct value' do
113
+ deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
114
+
115
+ expect(deep_merge.request.to_h).to eq(request)
116
+ expect(deep_merge.ssl.to_h).to eq(ssl)
117
+ end
118
+ end
119
+ end
56
120
  end
data/spec/faraday_spec.rb CHANGED
@@ -18,10 +18,16 @@ RSpec.describe Faraday do
18
18
  end
19
19
 
20
20
  it 'uses method_missing on Faraday if there is no proxyable method' do
21
- expect { Faraday.this_method_does_not_exist }.to raise_error(
22
- NoMethodError,
23
- "undefined method `this_method_does_not_exist' for Faraday:Module"
24
- )
21
+ expected_message =
22
+ if RUBY_VERSION >= '3.4'
23
+ "undefined method 'this_method_does_not_exist' for module Faraday"
24
+ elsif RUBY_VERSION >= '3.3'
25
+ "undefined method `this_method_does_not_exist' for module Faraday"
26
+ else
27
+ "undefined method `this_method_does_not_exist' for Faraday:Module"
28
+ end
29
+
30
+ expect { Faraday.this_method_does_not_exist }.to raise_error(NoMethodError, expected_message)
25
31
  end
26
32
 
27
33
  it 'proxied methods can be accessed' do
data/spec/spec_helper.rb CHANGED
@@ -29,14 +29,15 @@ SimpleCov.start do
29
29
  minimum_coverage_by_file 26
30
30
  end
31
31
 
32
- # Ensure all /lib files are loaded
33
- # so they will be included in the test coverage report.
34
- Dir['./lib/**/*.rb'].sort.each { |file| require file }
35
-
36
32
  require 'faraday'
37
33
  require 'pry'
38
34
 
39
- Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
35
+ # Ensure all /lib files are loaded
36
+ # so they will be included in the test coverage report.
37
+ Dir['./lib/**/*.rb'].each { |file| require file }
38
+
39
+ # Load all Rspec support files
40
+ Dir['./spec/support/**/*.rb'].each { |file| require file }
40
41
 
41
42
  RSpec.configure do |config|
42
43
  # rspec-expectations config goes here. You can use an alternate
@@ -8,7 +8,7 @@ FakeSafeBuffer = Struct.new(:string) do
8
8
 
9
9
  def gsub(regex)
10
10
  string.gsub(regex) do
11
- match, = $&, '' =~ /a/
11
+ match, = Regexp.last_match(0), '' =~ /a/ # rubocop:disable Performance/StringInclude
12
12
  yield(match)
13
13
  end
14
14
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaradayMiddlewareSubclasses
4
+ class SubclassNoOptions < Faraday::Middleware
5
+ end
6
+
7
+ class SubclassOneOption < Faraday::Middleware
8
+ DEFAULT_OPTIONS = { some_other_option: false }.freeze
9
+ end
10
+
11
+ class SubclassTwoOptions < Faraday::Middleware
12
+ DEFAULT_OPTIONS = { some_option: true, some_other_option: false }.freeze
13
+ end
14
+ end
15
+
16
+ Faraday::Response.register_middleware(no_options: FaradayMiddlewareSubclasses::SubclassNoOptions)
17
+ Faraday::Response.register_middleware(one_option: FaradayMiddlewareSubclasses::SubclassOneOption)
18
+ Faraday::Response.register_middleware(two_options: FaradayMiddlewareSubclasses::SubclassTwoOptions)