faraday 0.15.0 → 1.3.0

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +350 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +18 -345
  5. data/Rakefile +7 -0
  6. data/examples/client_spec.rb +65 -0
  7. data/examples/client_test.rb +79 -0
  8. data/lib/faraday.rb +118 -190
  9. data/lib/faraday/adapter.rb +82 -22
  10. data/lib/faraday/adapter/em_http.rb +150 -104
  11. data/lib/faraday/adapter/em_http_ssl_patch.rb +24 -18
  12. data/lib/faraday/adapter/em_synchrony.rb +110 -63
  13. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +18 -15
  14. data/lib/faraday/adapter/excon.rb +98 -53
  15. data/lib/faraday/adapter/httpclient.rb +83 -59
  16. data/lib/faraday/adapter/net_http_persistent.rb +57 -29
  17. data/lib/faraday/adapter/patron.rb +80 -48
  18. data/lib/faraday/adapter/rack.rb +30 -13
  19. data/lib/faraday/adapter/test.rb +86 -53
  20. data/lib/faraday/adapter/typhoeus.rb +4 -1
  21. data/lib/faraday/adapter_registry.rb +30 -0
  22. data/lib/faraday/autoload.rb +46 -36
  23. data/lib/faraday/connection.rb +312 -182
  24. data/lib/faraday/dependency_loader.rb +37 -0
  25. data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
  26. data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
  27. data/lib/faraday/error.rb +123 -37
  28. data/lib/faraday/file_part.rb +128 -0
  29. data/lib/faraday/logging/formatter.rb +105 -0
  30. data/lib/faraday/methods.rb +6 -0
  31. data/lib/faraday/middleware.rb +19 -25
  32. data/lib/faraday/middleware_registry.rb +129 -0
  33. data/lib/faraday/options.rb +39 -193
  34. data/lib/faraday/options/connection_options.rb +22 -0
  35. data/lib/faraday/options/env.rb +181 -0
  36. data/lib/faraday/options/proxy_options.rb +28 -0
  37. data/lib/faraday/options/request_options.rb +22 -0
  38. data/lib/faraday/options/ssl_options.rb +59 -0
  39. data/lib/faraday/param_part.rb +53 -0
  40. data/lib/faraday/parameters.rb +4 -196
  41. data/lib/faraday/rack_builder.rb +77 -65
  42. data/lib/faraday/request.rb +94 -32
  43. data/lib/faraday/request/authorization.rb +44 -30
  44. data/lib/faraday/request/basic_authentication.rb +14 -7
  45. data/lib/faraday/request/instrumentation.rb +45 -27
  46. data/lib/faraday/request/multipart.rb +86 -48
  47. data/lib/faraday/request/retry.rb +198 -170
  48. data/lib/faraday/request/token_authentication.rb +15 -10
  49. data/lib/faraday/request/url_encoded.rb +43 -23
  50. data/lib/faraday/response.rb +27 -23
  51. data/lib/faraday/response/logger.rb +22 -69
  52. data/lib/faraday/response/raise_error.rb +49 -14
  53. data/lib/faraday/utils.rb +38 -247
  54. data/lib/faraday/utils/headers.rb +139 -0
  55. data/lib/faraday/utils/params_hash.rb +61 -0
  56. data/lib/faraday/version.rb +5 -0
  57. data/spec/external_adapters/faraday_specs_setup.rb +14 -0
  58. data/spec/faraday/adapter/em_http_spec.rb +47 -0
  59. data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
  60. data/spec/faraday/adapter/excon_spec.rb +49 -0
  61. data/spec/faraday/adapter/httpclient_spec.rb +73 -0
  62. data/spec/faraday/adapter/net_http_persistent_spec.rb +57 -0
  63. data/spec/faraday/adapter/net_http_spec.rb +64 -0
  64. data/spec/faraday/adapter/patron_spec.rb +18 -0
  65. data/spec/faraday/adapter/rack_spec.rb +8 -0
  66. data/spec/faraday/adapter/test_spec.rb +260 -0
  67. data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
  68. data/spec/faraday/adapter_registry_spec.rb +28 -0
  69. data/spec/faraday/adapter_spec.rb +55 -0
  70. data/spec/faraday/composite_read_io_spec.rb +80 -0
  71. data/spec/faraday/connection_spec.rb +691 -0
  72. data/spec/faraday/error_spec.rb +60 -0
  73. data/spec/faraday/middleware_spec.rb +52 -0
  74. data/spec/faraday/options/env_spec.rb +70 -0
  75. data/spec/faraday/options/options_spec.rb +297 -0
  76. data/spec/faraday/options/proxy_options_spec.rb +37 -0
  77. data/spec/faraday/options/request_options_spec.rb +19 -0
  78. data/spec/faraday/params_encoders/flat_spec.rb +42 -0
  79. data/spec/faraday/params_encoders/nested_spec.rb +142 -0
  80. data/spec/faraday/rack_builder_spec.rb +345 -0
  81. data/spec/faraday/request/authorization_spec.rb +88 -0
  82. data/spec/faraday/request/instrumentation_spec.rb +76 -0
  83. data/spec/faraday/request/multipart_spec.rb +302 -0
  84. data/spec/faraday/request/retry_spec.rb +242 -0
  85. data/spec/faraday/request/url_encoded_spec.rb +83 -0
  86. data/spec/faraday/request_spec.rb +120 -0
  87. data/spec/faraday/response/logger_spec.rb +220 -0
  88. data/spec/faraday/response/middleware_spec.rb +68 -0
  89. data/spec/faraday/response/raise_error_spec.rb +169 -0
  90. data/spec/faraday/response_spec.rb +75 -0
  91. data/spec/faraday/utils/headers_spec.rb +82 -0
  92. data/spec/faraday/utils_spec.rb +56 -0
  93. data/spec/faraday_spec.rb +37 -0
  94. data/spec/spec_helper.rb +132 -0
  95. data/spec/support/disabling_stub.rb +14 -0
  96. data/spec/support/fake_safe_buffer.rb +15 -0
  97. data/spec/support/helper_methods.rb +133 -0
  98. data/spec/support/shared_examples/adapter.rb +105 -0
  99. data/spec/support/shared_examples/params_encoder.rb +18 -0
  100. data/spec/support/shared_examples/request_method.rb +262 -0
  101. data/spec/support/streaming_response_checker.rb +35 -0
  102. data/spec/support/webmock_rack_app.rb +68 -0
  103. metadata +109 -10
  104. data/lib/faraday/adapter/net_http.rb +0 -137
  105. data/lib/faraday/upload_io.rb +0 -67
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Authorization do
4
+ let(:conn) do
5
+ Faraday.new do |b|
6
+ b.request auth_type, *auth_config
7
+ b.adapter :test do |stub|
8
+ stub.get('/auth-echo') do |env|
9
+ [200, {}, env[:request_headers]['Authorization']]
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ shared_examples 'does not interfere with existing authentication' do
16
+ context 'and request already has an authentication header' do
17
+ let(:response) { conn.get('/auth-echo', nil, authorization: 'Token token="bar"') }
18
+
19
+ it 'does not interfere with existing authorization' do
20
+ expect(response.body).to eq('Token token="bar"')
21
+ end
22
+ end
23
+ end
24
+
25
+ let(:response) { conn.get('/auth-echo') }
26
+
27
+ describe 'basic_auth' do
28
+ let(:auth_type) { :basic_auth }
29
+
30
+ context 'when passed correct params' do
31
+ let(:auth_config) { %w[aladdin opensesame] }
32
+
33
+ it { expect(response.body).to eq('Basic YWxhZGRpbjpvcGVuc2VzYW1l') }
34
+
35
+ include_examples 'does not interfere with existing authentication'
36
+ end
37
+
38
+ context 'when passed very long values' do
39
+ let(:auth_config) { ['A' * 255, ''] }
40
+
41
+ it { expect(response.body).to eq("Basic #{'QUFB' * 85}Og==") }
42
+
43
+ include_examples 'does not interfere with existing authentication'
44
+ end
45
+ end
46
+
47
+ describe 'token_auth' do
48
+ let(:auth_type) { :token_auth }
49
+
50
+ context 'when passed correct params' do
51
+ let(:auth_config) { 'quux' }
52
+
53
+ it { expect(response.body).to eq('Token token="quux"') }
54
+
55
+ include_examples 'does not interfere with existing authentication'
56
+ end
57
+
58
+ context 'when other values are provided' do
59
+ let(:auth_config) { ['baz', { foo: 42 }] }
60
+
61
+ it { expect(response.body).to match(/^Token /) }
62
+ it { expect(response.body).to match(/token="baz"/) }
63
+ it { expect(response.body).to match(/foo="42"/) }
64
+
65
+ include_examples 'does not interfere with existing authentication'
66
+ end
67
+ end
68
+
69
+ describe 'authorization' do
70
+ let(:auth_type) { :authorization }
71
+
72
+ context 'when passed two strings' do
73
+ let(:auth_config) { ['custom', 'abc def'] }
74
+
75
+ it { expect(response.body).to eq('custom abc def') }
76
+
77
+ include_examples 'does not interfere with existing authentication'
78
+ end
79
+
80
+ context 'when passed a string and a hash' do
81
+ let(:auth_config) { ['baz', { foo: 42 }] }
82
+
83
+ it { expect(response.body).to eq('baz foo="42"') }
84
+
85
+ include_examples 'does not interfere with existing authentication'
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Instrumentation do
4
+ class FakeInstrumenter
5
+ attr_reader :instrumentations
6
+
7
+ def initialize
8
+ @instrumentations = []
9
+ end
10
+
11
+ def instrument(name, env)
12
+ @instrumentations << [name, env]
13
+ yield
14
+ end
15
+ end
16
+
17
+ let(:config) { {} }
18
+ let(:options) { Faraday::Request::Instrumentation::Options.from config }
19
+ let(:instrumenter) { FakeInstrumenter.new }
20
+ let(:conn) do
21
+ Faraday.new do |f|
22
+ f.request :instrumentation, config.merge(instrumenter: instrumenter)
23
+ f.adapter :test do |stub|
24
+ stub.get '/' do
25
+ [200, {}, 'ok']
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ it { expect(options.name).to eq('request.faraday') }
32
+ it 'defaults to ActiveSupport::Notifications' do
33
+ begin
34
+ res = options.instrumenter
35
+ rescue NameError => e
36
+ expect(e.to_s).to match('ActiveSupport')
37
+ else
38
+ expect(res).to eq(ActiveSupport::Notifications)
39
+ end
40
+ end
41
+
42
+ it 'instruments with default name' do
43
+ expect(instrumenter.instrumentations.size).to eq(0)
44
+
45
+ res = conn.get '/'
46
+ expect(res.body).to eq('ok')
47
+ expect(instrumenter.instrumentations.size).to eq(1)
48
+
49
+ name, env = instrumenter.instrumentations.first
50
+ expect(name).to eq('request.faraday')
51
+ expect(env[:url].path).to eq('/')
52
+ end
53
+
54
+ context 'with custom name' do
55
+ let(:config) { { name: 'custom' } }
56
+
57
+ it { expect(options.name).to eq('custom') }
58
+ it 'instruments with custom name' do
59
+ expect(instrumenter.instrumentations.size).to eq(0)
60
+
61
+ res = conn.get '/'
62
+ expect(res.body).to eq('ok')
63
+ expect(instrumenter.instrumentations.size).to eq(1)
64
+
65
+ name, env = instrumenter.instrumentations.first
66
+ expect(name).to eq('custom')
67
+ expect(env[:url].path).to eq('/')
68
+ end
69
+ end
70
+
71
+ context 'with custom instrumenter' do
72
+ let(:config) { { instrumenter: :custom } }
73
+
74
+ it { expect(options.instrumenter).to eq(:custom) }
75
+ end
76
+ end
@@ -0,0 +1,302 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Faraday::Request::Multipart do
4
+ let(:options) { {} }
5
+ let(:conn) do
6
+ Faraday.new do |b|
7
+ b.request :multipart, options
8
+ b.request :url_encoded
9
+ b.adapter :test do |stub|
10
+ stub.post('/echo') do |env|
11
+ posted_as = env[:request_headers]['Content-Type']
12
+ expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
13
+ [200, { 'Content-Type' => posted_as }, env[:body].read]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ shared_examples 'a multipart request' do
20
+ it 'generates a unique boundary for each request' do
21
+ response1 = conn.post('/echo', payload)
22
+ response2 = conn.post('/echo', payload)
23
+
24
+ b1 = parse_multipart_boundary(response1.headers['Content-Type'])
25
+ b2 = parse_multipart_boundary(response2.headers['Content-Type'])
26
+ expect(b1).to_not eq(b2)
27
+ end
28
+ end
29
+
30
+ context 'FilePart: when multipart objects in param' do
31
+ let(:payload) do
32
+ {
33
+ a: 1,
34
+ b: {
35
+ c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
36
+ 'Content-Disposition' => 'form-data; foo=1'),
37
+ d: 2
38
+ }
39
+ }
40
+ end
41
+ it_behaves_like 'a multipart request'
42
+
43
+ it 'forms a multipart request' do
44
+ response = conn.post('/echo', payload)
45
+
46
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
47
+ result = parse_multipart(boundary, response.body)
48
+ expect(result[:errors]).to be_empty
49
+
50
+ part_a, body_a = result.part('a')
51
+ expect(part_a).to_not be_nil
52
+ expect(part_a.filename).to be_nil
53
+ expect(body_a).to eq('1')
54
+
55
+ part_bc, body_bc = result.part('b[c]')
56
+ expect(part_bc).to_not be_nil
57
+ expect(part_bc.filename).to eq('multipart_spec.rb')
58
+ expect(part_bc.headers['content-disposition'])
59
+ .to eq(
60
+ 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
61
+ )
62
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
63
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
64
+ expect(body_bc).to eq(File.read(__FILE__))
65
+
66
+ part_bd, body_bd = result.part('b[d]')
67
+ expect(part_bd).to_not be_nil
68
+ expect(part_bd.filename).to be_nil
69
+ expect(body_bd).to eq('2')
70
+ end
71
+ end
72
+
73
+ context 'FilePart: when providing json and IO content in the same payload' do
74
+ let(:io) { StringIO.new('io-content') }
75
+ let(:json) do
76
+ {
77
+ b: 1,
78
+ c: 2
79
+ }.to_json
80
+ end
81
+
82
+ let(:payload) do
83
+ {
84
+ json: Faraday::ParamPart.new(json, 'application/json'),
85
+ io: Faraday::FilePart.new(io, 'application/pdf')
86
+ }
87
+ end
88
+
89
+ it_behaves_like 'a multipart request'
90
+
91
+ it 'forms a multipart request' do
92
+ response = conn.post('/echo', payload)
93
+
94
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
95
+ result = parse_multipart(boundary, response.body)
96
+ expect(result[:errors]).to be_empty
97
+
98
+ part_json, body_json = result.part('json')
99
+ expect(part_json).to_not be_nil
100
+ expect(part_json.mime).to eq('application/json')
101
+ expect(part_json.filename).to be_nil
102
+ expect(body_json).to eq(json)
103
+
104
+ part_io, body_io = result.part('io')
105
+ expect(part_io).to_not be_nil
106
+ expect(part_io.mime).to eq('application/pdf')
107
+ expect(part_io.filename).to eq('local.path')
108
+ expect(body_io).to eq(io.string)
109
+ end
110
+ end
111
+
112
+ context 'FilePart: when multipart objects in array param' do
113
+ let(:payload) do
114
+ {
115
+ a: 1,
116
+ b: [{
117
+ c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
118
+ d: 2
119
+ }]
120
+ }
121
+ end
122
+
123
+ it_behaves_like 'a multipart request'
124
+
125
+ it 'forms a multipart request' do
126
+ response = conn.post('/echo', payload)
127
+
128
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
129
+ result = parse_multipart(boundary, response.body)
130
+ expect(result[:errors]).to be_empty
131
+
132
+ part_a, body_a = result.part('a')
133
+ expect(part_a).to_not be_nil
134
+ expect(part_a.filename).to be_nil
135
+ expect(body_a).to eq('1')
136
+
137
+ part_bc, body_bc = result.part('b[][c]')
138
+ expect(part_bc).to_not be_nil
139
+ expect(part_bc.filename).to eq('multipart_spec.rb')
140
+ expect(part_bc.headers['content-disposition'])
141
+ .to eq(
142
+ 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
143
+ )
144
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
145
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
146
+ expect(body_bc).to eq(File.read(__FILE__))
147
+
148
+ part_bd, body_bd = result.part('b[][d]')
149
+ expect(part_bd).to_not be_nil
150
+ expect(part_bd.filename).to be_nil
151
+ expect(body_bd).to eq('2')
152
+ end
153
+ end
154
+
155
+ context 'UploadIO: when multipart objects in param' do
156
+ let(:payload) do
157
+ {
158
+ a: 1,
159
+ b: {
160
+ c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
161
+ 'Content-Disposition' => 'form-data; foo=1'),
162
+ d: 2
163
+ }
164
+ }
165
+ end
166
+ it_behaves_like 'a multipart request'
167
+
168
+ it 'forms a multipart request' do
169
+ response = conn.post('/echo', payload)
170
+
171
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
172
+ result = parse_multipart(boundary, response.body)
173
+ expect(result[:errors]).to be_empty
174
+
175
+ part_a, body_a = result.part('a')
176
+ expect(part_a).to_not be_nil
177
+ expect(part_a.filename).to be_nil
178
+ expect(body_a).to eq('1')
179
+
180
+ part_bc, body_bc = result.part('b[c]')
181
+ expect(part_bc).to_not be_nil
182
+ expect(part_bc.filename).to eq('multipart_spec.rb')
183
+ expect(part_bc.headers['content-disposition'])
184
+ .to eq(
185
+ 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
186
+ )
187
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
188
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
189
+ expect(body_bc).to eq(File.read(__FILE__))
190
+
191
+ part_bd, body_bd = result.part('b[d]')
192
+ expect(part_bd).to_not be_nil
193
+ expect(part_bd.filename).to be_nil
194
+ expect(body_bd).to eq('2')
195
+ end
196
+ end
197
+
198
+ context 'UploadIO: when providing json and IO content in the same payload' do
199
+ let(:io) { StringIO.new('io-content') }
200
+ let(:json) do
201
+ {
202
+ b: 1,
203
+ c: 2
204
+ }.to_json
205
+ end
206
+
207
+ let(:payload) do
208
+ {
209
+ json: Faraday::ParamPart.new(json, 'application/json'),
210
+ io: Faraday::UploadIO.new(io, 'application/pdf')
211
+ }
212
+ end
213
+
214
+ it_behaves_like 'a multipart request'
215
+
216
+ it 'forms a multipart request' do
217
+ response = conn.post('/echo', payload)
218
+
219
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
220
+ result = parse_multipart(boundary, response.body)
221
+ expect(result[:errors]).to be_empty
222
+
223
+ part_json, body_json = result.part('json')
224
+ expect(part_json).to_not be_nil
225
+ expect(part_json.mime).to eq('application/json')
226
+ expect(part_json.filename).to be_nil
227
+ expect(body_json).to eq(json)
228
+
229
+ part_io, body_io = result.part('io')
230
+ expect(part_io).to_not be_nil
231
+ expect(part_io.mime).to eq('application/pdf')
232
+ expect(part_io.filename).to eq('local.path')
233
+ expect(body_io).to eq(io.string)
234
+ end
235
+ end
236
+
237
+ context 'UploadIO: when multipart objects in array param' do
238
+ let(:payload) do
239
+ {
240
+ a: 1,
241
+ b: [{
242
+ c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
243
+ d: 2
244
+ }]
245
+ }
246
+ end
247
+
248
+ it_behaves_like 'a multipart request'
249
+
250
+ it 'forms a multipart request' do
251
+ response = conn.post('/echo', payload)
252
+
253
+ boundary = parse_multipart_boundary(response.headers['Content-Type'])
254
+ result = parse_multipart(boundary, response.body)
255
+ expect(result[:errors]).to be_empty
256
+
257
+ part_a, body_a = result.part('a')
258
+ expect(part_a).to_not be_nil
259
+ expect(part_a.filename).to be_nil
260
+ expect(body_a).to eq('1')
261
+
262
+ part_bc, body_bc = result.part('b[][c]')
263
+ expect(part_bc).to_not be_nil
264
+ expect(part_bc.filename).to eq('multipart_spec.rb')
265
+ expect(part_bc.headers['content-disposition'])
266
+ .to eq(
267
+ 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
268
+ )
269
+ expect(part_bc.headers['content-type']).to eq('text/x-ruby')
270
+ expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
271
+ expect(body_bc).to eq(File.read(__FILE__))
272
+
273
+ part_bd, body_bd = result.part('b[][d]')
274
+ expect(part_bd).to_not be_nil
275
+ expect(part_bd.filename).to be_nil
276
+ expect(body_bd).to eq('2')
277
+ end
278
+ end
279
+
280
+ context 'when passing flat_encode=true option' do
281
+ let(:options) { { flat_encode: true } }
282
+ let(:io) { StringIO.new('io-content') }
283
+ let(:payload) do
284
+ {
285
+ a: 1,
286
+ b: [
287
+ Faraday::UploadIO.new(io, 'application/pdf'),
288
+ Faraday::UploadIO.new(io, 'application/pdf')
289
+ ]
290
+ }
291
+ end
292
+
293
+ it_behaves_like 'a multipart request'
294
+
295
+ it 'encode params using flat encoder' do
296
+ response = conn.post('/echo', payload)
297
+
298
+ expect(response.body).to include('name="b"')
299
+ expect(response.body).not_to include('name="b[]"')
300
+ end
301
+ end
302
+ end