faraday 1.0.1 → 1.10.3

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +3 -5
  5. data/examples/client_spec.rb +35 -3
  6. data/examples/client_test.rb +41 -2
  7. data/lib/faraday/adapter/test.rb +59 -43
  8. data/lib/faraday/adapter.rb +2 -12
  9. data/lib/faraday/autoload.rb +2 -10
  10. data/lib/faraday/connection.rb +37 -9
  11. data/lib/faraday/dependency_loader.rb +3 -1
  12. data/lib/faraday/deprecate.rb +110 -0
  13. data/lib/faraday/encoders/flat_params_encoder.rb +9 -2
  14. data/lib/faraday/encoders/nested_params_encoder.rb +7 -2
  15. data/lib/faraday/error.rb +20 -6
  16. data/lib/faraday/methods.rb +6 -0
  17. data/lib/faraday/middleware.rb +14 -4
  18. data/lib/faraday/options/proxy_options.rb +4 -0
  19. data/lib/faraday/options.rb +4 -8
  20. data/lib/faraday/rack_builder.rb +13 -12
  21. data/lib/faraday/request/authorization.rb +14 -7
  22. data/lib/faraday/request/json.rb +55 -0
  23. data/lib/faraday/request.rb +19 -11
  24. data/lib/faraday/response/json.rb +54 -0
  25. data/lib/faraday/response/logger.rb +2 -4
  26. data/lib/faraday/response/raise_error.rb +12 -1
  27. data/lib/faraday/response.rb +3 -7
  28. data/lib/faraday/utils/headers.rb +2 -2
  29. data/lib/faraday/utils.rb +2 -2
  30. data/lib/faraday/version.rb +5 -0
  31. data/lib/faraday.rb +67 -40
  32. data/spec/faraday/adapter/em_http_spec.rb +39 -37
  33. data/spec/faraday/adapter/em_synchrony_spec.rb +11 -9
  34. data/spec/faraday/adapter/test_spec.rb +377 -0
  35. data/spec/faraday/connection_spec.rb +45 -0
  36. data/spec/faraday/deprecate_spec.rb +147 -0
  37. data/spec/faraday/error_spec.rb +15 -0
  38. data/spec/faraday/middleware_spec.rb +32 -6
  39. data/spec/faraday/options/proxy_options_spec.rb +7 -0
  40. data/spec/faraday/params_encoders/flat_spec.rb +8 -0
  41. data/spec/faraday/params_encoders/nested_spec.rb +8 -0
  42. data/spec/faraday/rack_builder_spec.rb +149 -0
  43. data/spec/faraday/request/authorization_spec.rb +10 -2
  44. data/spec/faraday/request/json_spec.rb +111 -0
  45. data/spec/faraday/request_spec.rb +16 -5
  46. data/spec/faraday/response/json_spec.rb +119 -0
  47. data/spec/faraday/response/raise_error_spec.rb +63 -0
  48. data/spec/spec_helper.rb +2 -0
  49. data/spec/support/shared_examples/adapter.rb +2 -1
  50. data/spec/support/shared_examples/request_method.rb +39 -11
  51. metadata +156 -30
  52. data/lib/faraday/adapter/em_http.rb +0 -286
  53. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
  54. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
  55. data/lib/faraday/adapter/em_synchrony.rb +0 -150
  56. data/lib/faraday/adapter/excon.rb +0 -124
  57. data/lib/faraday/adapter/httpclient.rb +0 -152
  58. data/lib/faraday/adapter/net_http.rb +0 -219
  59. data/lib/faraday/adapter/net_http_persistent.rb +0 -91
  60. data/lib/faraday/adapter/patron.rb +0 -132
  61. data/lib/faraday/adapter/rack.rb +0 -75
  62. data/lib/faraday/file_part.rb +0 -128
  63. data/lib/faraday/param_part.rb +0 -53
  64. data/lib/faraday/request/multipart.rb +0 -99
  65. data/lib/faraday/request/retry.rb +0 -239
  66. data/spec/faraday/adapter/net_http_persistent_spec.rb +0 -57
  67. data/spec/faraday/request/multipart_spec.rb +0 -274
  68. data/spec/faraday/request/retry_spec.rb +0 -242
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('url_encoded', __dir__)
4
- require 'securerandom'
5
-
6
- module Faraday
7
- class Request
8
- # Middleware for supporting multi-part requests.
9
- class Multipart < UrlEncoded
10
- self.mime_type = 'multipart/form-data'
11
- unless defined?(::Faraday::Request::Multipart::DEFAULT_BOUNDARY_PREFIX)
12
- DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
13
- end
14
-
15
- # Checks for files in the payload, otherwise leaves everything untouched.
16
- #
17
- # @param env [Faraday::Env]
18
- def call(env)
19
- match_content_type(env) do |params|
20
- env.request.boundary ||= unique_boundary
21
- env.request_headers[CONTENT_TYPE] +=
22
- "; boundary=#{env.request.boundary}"
23
- env.body = create_multipart(env, params)
24
- end
25
- @app.call env
26
- end
27
-
28
- # @param env [Faraday::Env]
29
- def process_request?(env)
30
- type = request_type(env)
31
- env.body.respond_to?(:each_key) && !env.body.empty? && (
32
- (type.empty? && has_multipart?(env.body)) ||
33
- (type == self.class.mime_type)
34
- )
35
- end
36
-
37
- # Returns true if obj is an enumerable with values that are multipart.
38
- #
39
- # @param obj [Object]
40
- # @return [Boolean]
41
- def has_multipart?(obj) # rubocop:disable Naming/PredicateName
42
- if obj.respond_to?(:each)
43
- (obj.respond_to?(:values) ? obj.values : obj).each do |val|
44
- return true if val.respond_to?(:content_type) || has_multipart?(val)
45
- end
46
- end
47
- false
48
- end
49
-
50
- # @param env [Faraday::Env]
51
- # @param params [Hash]
52
- def create_multipart(env, params)
53
- boundary = env.request.boundary
54
- parts = process_params(params) do |key, value|
55
- part(boundary, key, value)
56
- end
57
- parts << Faraday::Parts::EpiloguePart.new(boundary)
58
-
59
- body = Faraday::CompositeReadIO.new(parts)
60
- env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
61
- body
62
- end
63
-
64
- def part(boundary, key, value)
65
- if value.respond_to?(:to_part)
66
- value.to_part(boundary, key)
67
- else
68
- Faraday::Parts::Part.new(boundary, key, value)
69
- end
70
- end
71
-
72
- # @return [String]
73
- def unique_boundary
74
- "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
75
- end
76
-
77
- # @param params [Hash]
78
- # @param prefix [String]
79
- # @param pieces [Array]
80
- def process_params(params, prefix = nil, pieces = nil, &block)
81
- params.inject(pieces || []) do |all, (key, value)|
82
- key = "#{prefix}[#{key}]" if prefix
83
-
84
- case value
85
- when Array
86
- values = value.inject([]) { |a, v| a << [nil, v] }
87
- process_params(values, key, all, &block)
88
- when Hash
89
- process_params(value, key, all, &block)
90
- else
91
- # rubocop:disable Performance/RedundantBlockCall
92
- all << block.call(key, value)
93
- # rubocop:enable Performance/RedundantBlockCall
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
@@ -1,239 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Faraday
4
- class Request
5
- # Catches exceptions and retries each request a limited number of times.
6
- #
7
- # By default, it retries 2 times and handles only timeout exceptions. It can
8
- # be configured with an arbitrary number of retries, a list of exceptions to
9
- # handle, a retry interval, a percentage of randomness to add to the retry
10
- # interval, and a backoff factor.
11
- #
12
- # @example Configure Retry middleware using intervals
13
- # Faraday.new do |conn|
14
- # conn.request(:retry, max: 2,
15
- # interval: 0.05,
16
- # interval_randomness: 0.5,
17
- # backoff_factor: 2,
18
- # exceptions: [CustomException, 'Timeout::Error'])
19
- #
20
- # conn.adapter(:net_http) # NB: Last middleware must be the adapter
21
- # end
22
- #
23
- # This example will result in a first interval that is random between 0.05
24
- # and 0.075 and a second interval that is random between 0.1 and 0.15.
25
- class Retry < Faraday::Middleware
26
- DEFAULT_EXCEPTIONS = [
27
- Errno::ETIMEDOUT, 'Timeout::Error',
28
- Faraday::TimeoutError, Faraday::RetriableResponse
29
- ].freeze
30
- IDEMPOTENT_METHODS = %i[delete get head options put].freeze
31
-
32
- # Options contains the configurable parameters for the Retry middleware.
33
- class Options < Faraday::Options.new(:max, :interval, :max_interval,
34
- :interval_randomness,
35
- :backoff_factor, :exceptions,
36
- :methods, :retry_if, :retry_block,
37
- :retry_statuses)
38
-
39
- DEFAULT_CHECK = ->(_env, _exception) { false }
40
-
41
- def self.from(value)
42
- if value.is_a?(Integer)
43
- new(value)
44
- else
45
- super(value)
46
- end
47
- end
48
-
49
- def max
50
- (self[:max] ||= 2).to_i
51
- end
52
-
53
- def interval
54
- (self[:interval] ||= 0).to_f
55
- end
56
-
57
- def max_interval
58
- (self[:max_interval] ||= Float::MAX).to_f
59
- end
60
-
61
- def interval_randomness
62
- (self[:interval_randomness] ||= 0).to_f
63
- end
64
-
65
- def backoff_factor
66
- (self[:backoff_factor] ||= 1).to_f
67
- end
68
-
69
- def exceptions
70
- Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
71
- end
72
-
73
- def methods
74
- Array(self[:methods] ||= IDEMPOTENT_METHODS)
75
- end
76
-
77
- def retry_if
78
- self[:retry_if] ||= DEFAULT_CHECK
79
- end
80
-
81
- def retry_block
82
- self[:retry_block] ||= proc {}
83
- end
84
-
85
- def retry_statuses
86
- Array(self[:retry_statuses] ||= [])
87
- end
88
- end
89
-
90
- # @param app [#call]
91
- # @param options [Hash]
92
- # @option options [Integer] :max (2) Maximum number of retries
93
- # @option options [Integer] :interval (0) Pause in seconds between retries
94
- # @option options [Integer] :interval_randomness (0) The maximum random
95
- # interval amount expressed as a float between
96
- # 0 and 1 to use in addition to the interval.
97
- # @option options [Integer] :max_interval (Float::MAX) An upper limit
98
- # for the interval
99
- # @option options [Integer] :backoff_factor (1) The amount to multiply
100
- # each successive retry's interval amount by in order to provide backoff
101
- # @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
102
- # 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
103
- # The list of exceptions to handle. Exceptions can be given as
104
- # Class, Module, or String.
105
- # @option options [Array] :methods (the idempotent HTTP methods
106
- # in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
107
- # calling retry_if. Pass an empty Array to call retry_if
108
- # for all exceptions.
109
- # @option options [Block] :retry_if (false) block that will receive
110
- # the env object and the exception raised
111
- # and should decide if the code should retry still the action or
112
- # not independent of the retry count. This would be useful
113
- # if the exception produced is non-recoverable or if the
114
- # the HTTP method called is not idempotent.
115
- # @option options [Block] :retry_block block that is executed after
116
- # every retry. Request environment, middleware options, current number
117
- # of retries and the exception is passed to the block as parameters.
118
- # @option options [Array] :retry_statuses Array of Integer HTTP status
119
- # codes or a single Integer value that determines whether to raise
120
- # a Faraday::RetriableResponse exception based on the HTTP status code
121
- # of an HTTP response.
122
- def initialize(app, options = nil)
123
- super(app)
124
- @options = Options.from(options)
125
- @errmatch = build_exception_matcher(@options.exceptions)
126
- end
127
-
128
- def calculate_sleep_amount(retries, env)
129
- retry_after = calculate_retry_after(env)
130
- retry_interval = calculate_retry_interval(retries)
131
-
132
- return if retry_after && retry_after > @options.max_interval
133
-
134
- if retry_after && retry_after >= retry_interval
135
- retry_after
136
- else
137
- retry_interval
138
- end
139
- end
140
-
141
- # @param env [Faraday::Env]
142
- def call(env)
143
- retries = @options.max
144
- request_body = env[:body]
145
- begin
146
- # after failure env[:body] is set to the response body
147
- env[:body] = request_body
148
- @app.call(env).tap do |resp|
149
- if @options.retry_statuses.include?(resp.status)
150
- raise Faraday::RetriableResponse.new(nil, resp)
151
- end
152
- end
153
- rescue @errmatch => e
154
- if retries.positive? && retry_request?(env, e)
155
- retries -= 1
156
- rewind_files(request_body)
157
- @options.retry_block.call(env, @options, retries, e)
158
- if (sleep_amount = calculate_sleep_amount(retries + 1, env))
159
- sleep sleep_amount
160
- retry
161
- end
162
- end
163
-
164
- raise unless e.is_a?(Faraday::RetriableResponse)
165
-
166
- e.response
167
- end
168
- end
169
-
170
- # An exception matcher for the rescue clause can usually be any object
171
- # that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
172
- #
173
- # @param exceptions [Array]
174
- # @api private
175
- # @return [Module] an exception matcher
176
- def build_exception_matcher(exceptions)
177
- matcher = Module.new
178
- (
179
- class << matcher
180
- self
181
- end).class_eval do
182
- define_method(:===) do |error|
183
- exceptions.any? do |ex|
184
- if ex.is_a? Module
185
- error.is_a? ex
186
- else
187
- error.class.to_s == ex.to_s
188
- end
189
- end
190
- end
191
- end
192
- matcher
193
- end
194
-
195
- private
196
-
197
- def retry_request?(env, exception)
198
- @options.methods.include?(env[:method]) ||
199
- @options.retry_if.call(env, exception)
200
- end
201
-
202
- def rewind_files(body)
203
- return unless body.is_a?(Hash)
204
-
205
- body.each do |_, value|
206
- value.rewind if value.is_a?(UploadIO)
207
- end
208
- end
209
-
210
- # MDN spec for Retry-After header:
211
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
212
- def calculate_retry_after(env)
213
- response_headers = env[:response_headers]
214
- return unless response_headers
215
-
216
- retry_after_value = env[:response_headers]['Retry-After']
217
-
218
- # Try to parse date from the header value
219
- begin
220
- datetime = DateTime.rfc2822(retry_after_value)
221
- datetime.to_time - Time.now.utc
222
- rescue ArgumentError
223
- retry_after_value.to_f
224
- end
225
- end
226
-
227
- def calculate_retry_interval(retries)
228
- retry_index = @options.max - retries
229
- current_interval = @options.interval *
230
- (@options.backoff_factor**retry_index)
231
- current_interval = [current_interval, @options.max_interval].min
232
- random_interval = rand * @options.interval_randomness.to_f *
233
- @options.interval
234
-
235
- current_interval + random_interval
236
- end
237
- end
238
- end
239
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Faraday::Adapter::NetHttpPersistent do
4
- features :request_body_on_query_methods, :reason_phrase_parse, :compression, :trace_method
5
-
6
- it_behaves_like 'an adapter'
7
-
8
- it 'allows to provide adapter specific configs' do
9
- url = URI('https://example.com')
10
-
11
- adapter = described_class.new do |http|
12
- http.idle_timeout = 123
13
- end
14
-
15
- http = adapter.send(:connection, url: url, request: {})
16
- adapter.send(:configure_request, http, {})
17
-
18
- expect(http.idle_timeout).to eq(123)
19
- end
20
-
21
- it 'sets max_retries to 0' do
22
- url = URI('http://example.com')
23
-
24
- adapter = described_class.new
25
-
26
- http = adapter.send(:connection, url: url, request: {})
27
- adapter.send(:configure_request, http, {})
28
-
29
- # `max_retries=` is only present in Ruby 2.5
30
- expect(http.max_retries).to eq(0) if http.respond_to?(:max_retries=)
31
- end
32
-
33
- it 'allows to set pool_size on initialize' do
34
- url = URI('https://example.com')
35
-
36
- adapter = described_class.new(nil, pool_size: 5)
37
-
38
- http = adapter.send(:connection, url: url, request: {})
39
-
40
- # `pool` is only present in net_http_persistent >= 3.0
41
- expect(http.pool.size).to eq(5) if http.respond_to?(:pool)
42
- end
43
-
44
- context 'min_version' do
45
- it 'allows to set min_version in SSL settings' do
46
- url = URI('https://example.com')
47
-
48
- adapter = described_class.new(nil)
49
-
50
- http = adapter.send(:connection, url: url, request: {})
51
- adapter.send(:configure_ssl, http, min_version: :TLS1_2)
52
-
53
- # `min_version` is only present in net_http_persistent >= 3.1 (UNRELEASED)
54
- expect(http.min_version).to eq(:TLS1_2) if http.respond_to?(:min_version)
55
- end
56
- end
57
- end
@@ -1,274 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Faraday::Request::Multipart do
4
- let(:conn) do
5
- Faraday.new do |b|
6
- b.request :multipart
7
- b.request :url_encoded
8
- b.adapter :test do |stub|
9
- stub.post('/echo') do |env|
10
- posted_as = env[:request_headers]['Content-Type']
11
- expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
12
- [200, { 'Content-Type' => posted_as }, env[:body].read]
13
- end
14
- end
15
- end
16
- end
17
-
18
- shared_examples 'a multipart request' do
19
- it 'generates a unique boundary for each request' do
20
- response1 = conn.post('/echo', payload)
21
- response2 = conn.post('/echo', payload)
22
-
23
- b1 = parse_multipart_boundary(response1.headers['Content-Type'])
24
- b2 = parse_multipart_boundary(response2.headers['Content-Type'])
25
- expect(b1).to_not eq(b2)
26
- end
27
- end
28
-
29
- context 'FilePart: when multipart objects in param' do
30
- let(:payload) do
31
- {
32
- a: 1,
33
- b: {
34
- c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
35
- 'Content-Disposition' => 'form-data; foo=1'),
36
- d: 2
37
- }
38
- }
39
- end
40
- it_behaves_like 'a multipart request'
41
-
42
- it 'forms a multipart request' do
43
- response = conn.post('/echo', payload)
44
-
45
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
46
- result = parse_multipart(boundary, response.body)
47
- expect(result[:errors]).to be_empty
48
-
49
- part_a, body_a = result.part('a')
50
- expect(part_a).to_not be_nil
51
- expect(part_a.filename).to be_nil
52
- expect(body_a).to eq('1')
53
-
54
- part_bc, body_bc = result.part('b[c]')
55
- expect(part_bc).to_not be_nil
56
- expect(part_bc.filename).to eq('multipart_spec.rb')
57
- expect(part_bc.headers['content-disposition']).to eq(
58
- 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
59
- )
60
- expect(part_bc.headers['content-type']).to eq('text/x-ruby')
61
- expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
62
- expect(body_bc).to eq(File.read(__FILE__))
63
-
64
- part_bd, body_bd = result.part('b[d]')
65
- expect(part_bd).to_not be_nil
66
- expect(part_bd.filename).to be_nil
67
- expect(body_bd).to eq('2')
68
- end
69
- end
70
-
71
- context 'FilePart: when providing json and IO content in the same payload' do
72
- let(:io) { StringIO.new('io-content') }
73
- let(:json) do
74
- {
75
- b: 1,
76
- c: 2
77
- }.to_json
78
- end
79
-
80
- let(:payload) do
81
- {
82
- json: Faraday::ParamPart.new(json, 'application/json'),
83
- io: Faraday::FilePart.new(io, 'application/pdf')
84
- }
85
- end
86
-
87
- it_behaves_like 'a multipart request'
88
-
89
- it 'forms a multipart request' do
90
- response = conn.post('/echo', payload)
91
-
92
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
93
- result = parse_multipart(boundary, response.body)
94
- expect(result[:errors]).to be_empty
95
-
96
- part_json, body_json = result.part('json')
97
- expect(part_json).to_not be_nil
98
- expect(part_json.mime).to eq('application/json')
99
- expect(part_json.filename).to be_nil
100
- expect(body_json).to eq(json)
101
-
102
- part_io, body_io = result.part('io')
103
- expect(part_io).to_not be_nil
104
- expect(part_io.mime).to eq('application/pdf')
105
- expect(part_io.filename).to eq('local.path')
106
- expect(body_io).to eq(io.string)
107
- end
108
- end
109
-
110
- context 'FilePart: when multipart objects in array param' do
111
- let(:payload) do
112
- {
113
- a: 1,
114
- b: [{
115
- c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
116
- d: 2
117
- }]
118
- }
119
- end
120
-
121
- it_behaves_like 'a multipart request'
122
-
123
- it 'forms a multipart request' do
124
- response = conn.post('/echo', payload)
125
-
126
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
127
- result = parse_multipart(boundary, response.body)
128
- expect(result[:errors]).to be_empty
129
-
130
- part_a, body_a = result.part('a')
131
- expect(part_a).to_not be_nil
132
- expect(part_a.filename).to be_nil
133
- expect(body_a).to eq('1')
134
-
135
- part_bc, body_bc = result.part('b[][c]')
136
- expect(part_bc).to_not be_nil
137
- expect(part_bc.filename).to eq('multipart_spec.rb')
138
- expect(part_bc.headers['content-disposition']).to eq(
139
- 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
140
- )
141
- expect(part_bc.headers['content-type']).to eq('text/x-ruby')
142
- expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
143
- expect(body_bc).to eq(File.read(__FILE__))
144
-
145
- part_bd, body_bd = result.part('b[][d]')
146
- expect(part_bd).to_not be_nil
147
- expect(part_bd.filename).to be_nil
148
- expect(body_bd).to eq('2')
149
- end
150
- end
151
-
152
- context 'UploadIO: when multipart objects in param' do
153
- let(:payload) do
154
- {
155
- a: 1,
156
- b: {
157
- c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
158
- 'Content-Disposition' => 'form-data; foo=1'),
159
- d: 2
160
- }
161
- }
162
- end
163
- it_behaves_like 'a multipart request'
164
-
165
- it 'forms a multipart request' do
166
- response = conn.post('/echo', payload)
167
-
168
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
169
- result = parse_multipart(boundary, response.body)
170
- expect(result[:errors]).to be_empty
171
-
172
- part_a, body_a = result.part('a')
173
- expect(part_a).to_not be_nil
174
- expect(part_a.filename).to be_nil
175
- expect(body_a).to eq('1')
176
-
177
- part_bc, body_bc = result.part('b[c]')
178
- expect(part_bc).to_not be_nil
179
- expect(part_bc.filename).to eq('multipart_spec.rb')
180
- expect(part_bc.headers['content-disposition']).to eq(
181
- 'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
182
- )
183
- expect(part_bc.headers['content-type']).to eq('text/x-ruby')
184
- expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
185
- expect(body_bc).to eq(File.read(__FILE__))
186
-
187
- part_bd, body_bd = result.part('b[d]')
188
- expect(part_bd).to_not be_nil
189
- expect(part_bd.filename).to be_nil
190
- expect(body_bd).to eq('2')
191
- end
192
- end
193
-
194
- context 'UploadIO: when providing json and IO content in the same payload' do
195
- let(:io) { StringIO.new('io-content') }
196
- let(:json) do
197
- {
198
- b: 1,
199
- c: 2
200
- }.to_json
201
- end
202
-
203
- let(:payload) do
204
- {
205
- json: Faraday::ParamPart.new(json, 'application/json'),
206
- io: Faraday::UploadIO.new(io, 'application/pdf')
207
- }
208
- end
209
-
210
- it_behaves_like 'a multipart request'
211
-
212
- it 'forms a multipart request' do
213
- response = conn.post('/echo', payload)
214
-
215
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
216
- result = parse_multipart(boundary, response.body)
217
- expect(result[:errors]).to be_empty
218
-
219
- part_json, body_json = result.part('json')
220
- expect(part_json).to_not be_nil
221
- expect(part_json.mime).to eq('application/json')
222
- expect(part_json.filename).to be_nil
223
- expect(body_json).to eq(json)
224
-
225
- part_io, body_io = result.part('io')
226
- expect(part_io).to_not be_nil
227
- expect(part_io.mime).to eq('application/pdf')
228
- expect(part_io.filename).to eq('local.path')
229
- expect(body_io).to eq(io.string)
230
- end
231
- end
232
-
233
- context 'UploadIO: when multipart objects in array param' do
234
- let(:payload) do
235
- {
236
- a: 1,
237
- b: [{
238
- c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
239
- d: 2
240
- }]
241
- }
242
- end
243
-
244
- it_behaves_like 'a multipart request'
245
-
246
- it 'forms a multipart request' do
247
- response = conn.post('/echo', payload)
248
-
249
- boundary = parse_multipart_boundary(response.headers['Content-Type'])
250
- result = parse_multipart(boundary, response.body)
251
- expect(result[:errors]).to be_empty
252
-
253
- part_a, body_a = result.part('a')
254
- expect(part_a).to_not be_nil
255
- expect(part_a.filename).to be_nil
256
- expect(body_a).to eq('1')
257
-
258
- part_bc, body_bc = result.part('b[][c]')
259
- expect(part_bc).to_not be_nil
260
- expect(part_bc.filename).to eq('multipart_spec.rb')
261
- expect(part_bc.headers['content-disposition']).to eq(
262
- 'form-data; name="b[][c]"; filename="multipart_spec.rb"'
263
- )
264
- expect(part_bc.headers['content-type']).to eq('text/x-ruby')
265
- expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
266
- expect(body_bc).to eq(File.read(__FILE__))
267
-
268
- part_bd, body_bd = result.part('b[][d]')
269
- expect(part_bd).to_not be_nil
270
- expect(part_bd.filename).to be_nil
271
- expect(body_bd).to eq('2')
272
- end
273
- end
274
- end