faraday 1.7.1 → 2.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +193 -3
- data/README.md +11 -9
- data/examples/client_spec.rb +19 -19
- data/examples/client_test.rb +22 -22
- data/lib/faraday/adapter/test.rb +10 -4
- data/lib/faraday/adapter.rb +2 -5
- data/lib/faraday/connection.rb +17 -93
- data/lib/faraday/encoders/nested_params_encoder.rb +2 -2
- data/lib/faraday/error.rb +3 -8
- data/lib/faraday/logging/formatter.rb +1 -0
- data/lib/faraday/middleware.rb +0 -1
- data/lib/faraday/middleware_registry.rb +17 -63
- data/lib/faraday/options.rb +3 -3
- data/lib/faraday/rack_builder.rb +23 -20
- data/lib/faraday/request/authorization.rb +32 -38
- data/lib/faraday/request/instrumentation.rb +2 -0
- data/lib/faraday/request/json.rb +55 -0
- data/lib/faraday/request/url_encoded.rb +2 -0
- data/lib/faraday/request.rb +11 -31
- data/lib/faraday/response/json.rb +54 -0
- data/lib/faraday/response/logger.rb +4 -4
- data/lib/faraday/response/raise_error.rb +9 -1
- data/lib/faraday/response.rb +8 -19
- data/lib/faraday/utils/headers.rb +1 -1
- data/lib/faraday/utils.rb +10 -5
- data/lib/faraday/version.rb +1 -1
- data/lib/faraday.rb +11 -39
- data/spec/faraday/connection_spec.rb +136 -85
- data/spec/faraday/middleware_registry_spec.rb +31 -0
- data/spec/faraday/options/env_spec.rb +2 -2
- data/spec/faraday/rack_builder_spec.rb +26 -54
- data/spec/faraday/request/authorization_spec.rb +19 -24
- data/spec/faraday/request/instrumentation_spec.rb +5 -7
- data/spec/faraday/request/json_spec.rb +111 -0
- data/spec/faraday/request/url_encoded_spec.rb +0 -1
- data/spec/faraday/request_spec.rb +4 -15
- data/spec/faraday/response/json_spec.rb +117 -0
- data/spec/faraday/response/raise_error_spec.rb +7 -4
- data/spec/faraday/utils/headers_spec.rb +2 -2
- data/spec/faraday/utils_spec.rb +62 -1
- data/spec/support/fake_safe_buffer.rb +1 -1
- data/spec/support/helper_methods.rb +0 -37
- data/spec/support/shared_examples/adapter.rb +0 -1
- data/spec/support/shared_examples/request_method.rb +5 -18
- metadata +12 -147
- data/lib/faraday/adapter/typhoeus.rb +0 -15
- data/lib/faraday/autoload.rb +0 -87
- data/lib/faraday/dependency_loader.rb +0 -37
- data/lib/faraday/file_part.rb +0 -128
- data/lib/faraday/param_part.rb +0 -53
- data/lib/faraday/request/basic_authentication.rb +0 -20
- data/lib/faraday/request/multipart.rb +0 -106
- data/lib/faraday/request/retry.rb +0 -239
- data/lib/faraday/request/token_authentication.rb +0 -20
- data/spec/faraday/adapter/em_http_spec.rb +0 -49
- data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
- data/spec/faraday/adapter/excon_spec.rb +0 -49
- data/spec/faraday/adapter/httpclient_spec.rb +0 -73
- data/spec/faraday/adapter/net_http_spec.rb +0 -64
- data/spec/faraday/adapter/patron_spec.rb +0 -18
- data/spec/faraday/adapter/rack_spec.rb +0 -8
- data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
- data/spec/faraday/composite_read_io_spec.rb +0 -80
- data/spec/faraday/request/multipart_spec.rb +0 -302
- data/spec/faraday/request/retry_spec.rb +0 -242
- data/spec/faraday/response/middleware_spec.rb +0 -68
- data/spec/support/webmock_rack_app.rb +0 -68
@@ -3,7 +3,7 @@
|
|
3
3
|
RSpec.describe Faraday::Request::Authorization do
|
4
4
|
let(:conn) do
|
5
5
|
Faraday.new do |b|
|
6
|
-
b.request auth_type, *auth_config
|
6
|
+
b.request :authorization, auth_type, *auth_config
|
7
7
|
b.adapter :test do |stub|
|
8
8
|
stub.get('/auth-echo') do |env|
|
9
9
|
[200, {}, env[:request_headers]['Authorization']]
|
@@ -14,10 +14,10 @@ RSpec.describe Faraday::Request::Authorization do
|
|
14
14
|
|
15
15
|
shared_examples 'does not interfere with existing authentication' do
|
16
16
|
context 'and request already has an authentication header' do
|
17
|
-
let(:response) { conn.get('/auth-echo', nil, authorization: '
|
17
|
+
let(:response) { conn.get('/auth-echo', nil, authorization: 'OAuth oauth_token') }
|
18
18
|
|
19
19
|
it 'does not interfere with existing authorization' do
|
20
|
-
expect(response.body).to eq('
|
20
|
+
expect(response.body).to eq('OAuth oauth_token')
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -25,7 +25,7 @@ RSpec.describe Faraday::Request::Authorization do
|
|
25
25
|
let(:response) { conn.get('/auth-echo') }
|
26
26
|
|
27
27
|
describe 'basic_auth' do
|
28
|
-
let(:auth_type) { :
|
28
|
+
let(:auth_type) { :basic }
|
29
29
|
|
30
30
|
context 'when passed correct params' do
|
31
31
|
let(:auth_config) { %w[aladdin opensesame] }
|
@@ -44,43 +44,38 @@ RSpec.describe Faraday::Request::Authorization do
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
describe '
|
48
|
-
let(:auth_type) { :
|
47
|
+
describe 'authorization' do
|
48
|
+
let(:auth_type) { :Bearer }
|
49
49
|
|
50
|
-
context 'when passed
|
51
|
-
let(:auth_config) { '
|
50
|
+
context 'when passed a string' do
|
51
|
+
let(:auth_config) { ['custom'] }
|
52
52
|
|
53
|
-
it { expect(response.body).to eq('
|
53
|
+
it { expect(response.body).to eq('Bearer custom') }
|
54
54
|
|
55
55
|
include_examples 'does not interfere with existing authentication'
|
56
56
|
end
|
57
57
|
|
58
|
-
context 'when
|
59
|
-
let(:auth_config) { [
|
58
|
+
context 'when passed a proc' do
|
59
|
+
let(:auth_config) { [-> { 'custom_from_proc' }] }
|
60
60
|
|
61
|
-
it { expect(response.body).to
|
62
|
-
it { expect(response.body).to match(/token="baz"/) }
|
63
|
-
it { expect(response.body).to match(/foo="42"/) }
|
61
|
+
it { expect(response.body).to eq('Bearer custom_from_proc') }
|
64
62
|
|
65
63
|
include_examples 'does not interfere with existing authentication'
|
66
64
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe 'authorization' do
|
70
|
-
let(:auth_type) { :authorization }
|
71
65
|
|
72
|
-
context 'when passed
|
73
|
-
let(:
|
66
|
+
context 'when passed a callable' do
|
67
|
+
let(:callable) { double('Callable Authorizer', call: 'custom_from_callable') }
|
68
|
+
let(:auth_config) { [callable] }
|
74
69
|
|
75
|
-
it { expect(response.body).to eq('
|
70
|
+
it { expect(response.body).to eq('Bearer custom_from_callable') }
|
76
71
|
|
77
72
|
include_examples 'does not interfere with existing authentication'
|
78
73
|
end
|
79
74
|
|
80
|
-
context 'when passed
|
81
|
-
let(:auth_config) { [
|
75
|
+
context 'when passed too many arguments' do
|
76
|
+
let(:auth_config) { %w[baz foo] }
|
82
77
|
|
83
|
-
it { expect
|
78
|
+
it { expect { response }.to raise_error(ArgumentError) }
|
84
79
|
|
85
80
|
include_examples 'does not interfere with existing authentication'
|
86
81
|
end
|
@@ -30,13 +30,11 @@ RSpec.describe Faraday::Request::Instrumentation do
|
|
30
30
|
|
31
31
|
it { expect(options.name).to eq('request.faraday') }
|
32
32
|
it 'defaults to ActiveSupport::Notifications' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
expect(res).to eq(ActiveSupport::Notifications)
|
39
|
-
end
|
33
|
+
res = options.instrumenter
|
34
|
+
rescue NameError => e
|
35
|
+
expect(e.to_s).to match('ActiveSupport')
|
36
|
+
else
|
37
|
+
expect(res).to eq(ActiveSupport::Notifications)
|
40
38
|
end
|
41
39
|
|
42
40
|
it 'instruments with default name' do
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Request::Json do
|
4
|
+
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
|
5
|
+
|
6
|
+
def process(body, content_type = nil)
|
7
|
+
env = { body: body, request_headers: Faraday::Utils::Headers.new }
|
8
|
+
env[:request_headers]['content-type'] = content_type if content_type
|
9
|
+
middleware.call(Faraday::Env.from(env)).env
|
10
|
+
end
|
11
|
+
|
12
|
+
def result_body
|
13
|
+
result[:body]
|
14
|
+
end
|
15
|
+
|
16
|
+
def result_type
|
17
|
+
result[:request_headers]['content-type']
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'no body' do
|
21
|
+
let(:result) { process(nil) }
|
22
|
+
|
23
|
+
it "doesn't change body" do
|
24
|
+
expect(result_body).to be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "doesn't add content type" do
|
28
|
+
expect(result_type).to be_nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'empty body' do
|
33
|
+
let(:result) { process('') }
|
34
|
+
|
35
|
+
it "doesn't change body" do
|
36
|
+
expect(result_body).to be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it "doesn't add content type" do
|
40
|
+
expect(result_type).to be_nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'string body' do
|
45
|
+
let(:result) { process('{"a":1}') }
|
46
|
+
|
47
|
+
it "doesn't change body" do
|
48
|
+
expect(result_body).to eq('{"a":1}')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'adds content type' do
|
52
|
+
expect(result_type).to eq('application/json')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'object body' do
|
57
|
+
let(:result) { process(a: 1) }
|
58
|
+
|
59
|
+
it 'encodes body' do
|
60
|
+
expect(result_body).to eq('{"a":1}')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'adds content type' do
|
64
|
+
expect(result_type).to eq('application/json')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'empty object body' do
|
69
|
+
let(:result) { process({}) }
|
70
|
+
|
71
|
+
it 'encodes body' do
|
72
|
+
expect(result_body).to eq('{}')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'object body with json type' do
|
77
|
+
let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
|
78
|
+
|
79
|
+
it 'encodes body' do
|
80
|
+
expect(result_body).to eq('{"a":1}')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "doesn't change content type" do
|
84
|
+
expect(result_type).to eq('application/json; charset=utf-8')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'object body with vendor json type' do
|
89
|
+
let(:result) { process({ a: 1 }, 'application/vnd.myapp.v1+json; charset=utf-8') }
|
90
|
+
|
91
|
+
it 'encodes body' do
|
92
|
+
expect(result_body).to eq('{"a":1}')
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesn't change content type" do
|
96
|
+
expect(result_type).to eq('application/vnd.myapp.v1+json; charset=utf-8')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'object body with incompatible type' do
|
101
|
+
let(:result) { process({ a: 1 }, 'application/xml; charset=utf-8') }
|
102
|
+
|
103
|
+
it "doesn't change body" do
|
104
|
+
expect(result_body).to eq(a: 1)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "doesn't change content type" do
|
108
|
+
expect(result_type).to eq('application/xml; charset=utf-8')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
RSpec.describe Faraday::Request do
|
4
4
|
let(:conn) do
|
5
|
-
Faraday.new(url: 'http://
|
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
|
@@ -22,23 +22,12 @@ RSpec.describe Faraday::Request do
|
|
22
22
|
it { expect(subject.http_method).to eq(:post) }
|
23
23
|
end
|
24
24
|
|
25
|
-
describe 'deprecate method for HTTP method' do
|
26
|
-
let(:http_method) { :post }
|
27
|
-
let(:expected_warning) do
|
28
|
-
%r{WARNING: `Faraday::Request#method` is deprecated; use `#http_method` instead. It will be removed in or after version 2.0.\n`Faraday::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
25
|
context 'when setting the url on setup with a URI' do
|
37
26
|
let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
|
38
27
|
|
39
28
|
it { expect(subject.path).to eq(URI.parse('foo.json')) }
|
40
29
|
it { expect(subject.params).to eq('a' => '1') }
|
41
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
30
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
42
31
|
end
|
43
32
|
|
44
33
|
context 'when setting the url on setup with a string path and params' do
|
@@ -46,7 +35,7 @@ RSpec.describe Faraday::Request do
|
|
46
35
|
|
47
36
|
it { expect(subject.path).to eq('foo.json') }
|
48
37
|
it { expect(subject.params).to eq('a' => 1) }
|
49
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
38
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
50
39
|
end
|
51
40
|
|
52
41
|
context 'when setting the url on setup with a path including params' do
|
@@ -54,7 +43,7 @@ RSpec.describe Faraday::Request do
|
|
54
43
|
|
55
44
|
it { expect(subject.path).to eq('foo.json') }
|
56
45
|
it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
|
57
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
46
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
|
58
47
|
end
|
59
48
|
|
60
49
|
context 'when setting a header on setup with []= syntax' do
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Response::Json, type: :response do
|
4
|
+
let(:options) { {} }
|
5
|
+
let(:headers) { {} }
|
6
|
+
let(:middleware) do
|
7
|
+
described_class.new(lambda { |env|
|
8
|
+
Faraday::Response.new(env)
|
9
|
+
}, **options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(body, content_type = 'application/json', options = {})
|
13
|
+
env = {
|
14
|
+
body: body, request: options,
|
15
|
+
request_headers: Faraday::Utils::Headers.new,
|
16
|
+
response_headers: Faraday::Utils::Headers.new(headers)
|
17
|
+
}
|
18
|
+
env[:response_headers]['content-type'] = content_type if content_type
|
19
|
+
yield(env) if block_given?
|
20
|
+
middleware.call(Faraday::Env.from(env))
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'no type matching' do
|
24
|
+
it "doesn't change nil body" do
|
25
|
+
expect(process(nil).body).to be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'nullifies empty body' do
|
29
|
+
expect(process('').body).to be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'parses json body' do
|
33
|
+
response = process('{"a":1}')
|
34
|
+
expect(response.body).to eq('a' => 1)
|
35
|
+
expect(response.env[:raw_body]).to be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with preserving raw' do
|
40
|
+
let(:options) { { preserve_raw: true } }
|
41
|
+
|
42
|
+
it 'parses json body' do
|
43
|
+
response = process('{"a":1}')
|
44
|
+
expect(response.body).to eq('a' => 1)
|
45
|
+
expect(response.env[:raw_body]).to eq('{"a":1}')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with default regexp type matching' do
|
50
|
+
it 'parses json body of correct type' do
|
51
|
+
response = process('{"a":1}', 'application/x-json')
|
52
|
+
expect(response.body).to eq('a' => 1)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'ignores json body of incorrect type' do
|
56
|
+
response = process('{"a":1}', 'text/json-xml')
|
57
|
+
expect(response.body).to eq('{"a":1}')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with array type matching' do
|
62
|
+
let(:options) { { content_type: %w[a/b c/d] } }
|
63
|
+
|
64
|
+
it 'parses json body of correct type' do
|
65
|
+
expect(process('{"a":1}', 'a/b').body).to be_a(Hash)
|
66
|
+
expect(process('{"a":1}', 'c/d').body).to be_a(Hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'ignores json body of incorrect type' do
|
70
|
+
expect(process('{"a":1}', 'a/d').body).not_to be_a(Hash)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'chokes on invalid json' do
|
75
|
+
expect { process('{!') }.to raise_error(Faraday::ParsingError)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'includes the response on the ParsingError instance' do
|
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)
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'HEAD responses' do
|
86
|
+
it "nullifies the body if it's only one space" do
|
87
|
+
response = process(' ')
|
88
|
+
expect(response.body).to be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it "nullifies the body if it's two spaces" do
|
92
|
+
response = process(' ')
|
93
|
+
expect(response.body).to be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'JSON options' do
|
98
|
+
let(:body) { '{"a": 1}' }
|
99
|
+
let(:result) { { a: 1 } }
|
100
|
+
let(:options) do
|
101
|
+
{
|
102
|
+
parser_options: {
|
103
|
+
symbolize_names: true
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'passes relevant options to JSON parse' do
|
109
|
+
expect(::JSON).to receive(:parse)
|
110
|
+
.with(body, options[:parser_options])
|
111
|
+
.and_return(result)
|
112
|
+
|
113
|
+
response = process(body)
|
114
|
+
expect(response.body).to eq(result)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -139,7 +139,7 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
139
139
|
Faraday.new do |b|
|
140
140
|
b.response :raise_error
|
141
141
|
b.adapter :test do |stub|
|
142
|
-
stub.post(
|
142
|
+
stub.post(url, request_body, request_headers) do
|
143
143
|
[400, { 'X-Reason' => 'because' }, 'keep looking']
|
144
144
|
end
|
145
145
|
end
|
@@ -147,11 +147,13 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
147
147
|
end
|
148
148
|
let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
|
149
149
|
let(:request_headers) { { 'Authorization' => 'Basic 123' } }
|
150
|
+
let(:url_path) { 'request' }
|
151
|
+
let(:query_params) { 'full=true' }
|
152
|
+
let(:url) { "#{url_path}?#{query_params}" }
|
150
153
|
|
151
154
|
subject(:perform_request) do
|
152
|
-
conn.post
|
155
|
+
conn.post url do |req|
|
153
156
|
req.headers['Authorization'] = 'Basic 123'
|
154
|
-
req.params[:full] = true
|
155
157
|
req.body = request_body
|
156
158
|
end
|
157
159
|
end
|
@@ -159,7 +161,8 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
159
161
|
it 'returns the request info in the exception' do
|
160
162
|
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
161
163
|
expect(ex.response[:request][:method]).to eq(:post)
|
162
|
-
expect(ex.response[:request][:
|
164
|
+
expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
|
165
|
+
expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
|
163
166
|
expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
|
164
167
|
expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
|
165
168
|
expect(ex.response[:request][:body]).to eq(request_body)
|
@@ -68,9 +68,9 @@ RSpec.describe Faraday::Utils::Headers do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
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://
|
71
|
+
let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://httpbingo.org/\r\n\r\n" }
|
72
72
|
|
73
|
-
it { expect(subject['location']).to eq('http://
|
73
|
+
it { expect(subject['location']).to eq('http://httpbingo.org/') }
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'when response headers include a blank line' do
|
data/spec/faraday/utils_spec.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -53,4 +53,65 @@ 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
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'does not overwrite an Options Struct value' do
|
110
|
+
deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
|
111
|
+
|
112
|
+
expect(deep_merge.request.to_h).to eq(request)
|
113
|
+
expect(deep_merge.ssl.to_h).to eq(ssl)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
56
117
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'multipart_parser/reader'
|
4
|
-
|
5
3
|
module Faraday
|
6
4
|
module HelperMethods
|
7
5
|
def self.included(base)
|
@@ -86,41 +84,6 @@ module Faraday
|
|
86
84
|
end
|
87
85
|
end
|
88
86
|
|
89
|
-
def multipart_file
|
90
|
-
Faraday::FilePart.new(__FILE__, 'text/x-ruby')
|
91
|
-
end
|
92
|
-
|
93
|
-
# parse boundary out of a Content-Type header like:
|
94
|
-
# Content-Type: multipart/form-data; boundary=gc0p4Jq0M2Yt08jU534c0p
|
95
|
-
def parse_multipart_boundary(ctype)
|
96
|
-
MultipartParser::Reader.extract_boundary_value(ctype)
|
97
|
-
end
|
98
|
-
|
99
|
-
# parse a multipart MIME message, returning a hash of any multipart errors
|
100
|
-
def parse_multipart(boundary, body)
|
101
|
-
reader = MultipartParser::Reader.new(boundary)
|
102
|
-
result = { errors: [], parts: [] }
|
103
|
-
def result.part(name)
|
104
|
-
hash = self[:parts].detect { |h| h[:part].name == name }
|
105
|
-
[hash[:part], hash[:body].join]
|
106
|
-
end
|
107
|
-
|
108
|
-
reader.on_part do |part|
|
109
|
-
result[:parts] << thispart = {
|
110
|
-
part: part,
|
111
|
-
body: []
|
112
|
-
}
|
113
|
-
part.on_data do |chunk|
|
114
|
-
thispart[:body] << chunk
|
115
|
-
end
|
116
|
-
end
|
117
|
-
reader.on_error do |msg|
|
118
|
-
result[:errors] << msg
|
119
|
-
end
|
120
|
-
reader.write(body)
|
121
|
-
result
|
122
|
-
end
|
123
|
-
|
124
87
|
def method_with_body?(method)
|
125
88
|
self.class.method_with_body?(method)
|
126
89
|
end
|
@@ -40,7 +40,6 @@ shared_examples 'adapter examples' do |**options|
|
|
40
40
|
conn_options[:ssl][:ca_file] ||= ENV['SSL_FILE']
|
41
41
|
|
42
42
|
Faraday.new(remote, conn_options) do |conn|
|
43
|
-
conn.request :multipart
|
44
43
|
conn.request :url_encoded
|
45
44
|
conn.response :raise_error
|
46
45
|
conn.adapter described_class, *adapter_options
|
@@ -79,7 +79,7 @@ shared_examples 'a request method' do |http_method|
|
|
79
79
|
|
80
80
|
on_feature :request_body_on_query_methods do
|
81
81
|
it 'sends request body' do
|
82
|
-
request_stub.with(
|
82
|
+
request_stub.with({ body: 'test' })
|
83
83
|
res = if query_or_body == :body
|
84
84
|
conn.public_send(http_method, '/', 'test')
|
85
85
|
else
|
@@ -93,7 +93,7 @@ shared_examples 'a request method' do |http_method|
|
|
93
93
|
|
94
94
|
it 'sends url encoded parameters' do
|
95
95
|
payload = { name: 'zack' }
|
96
|
-
request_stub.with(
|
96
|
+
request_stub.with({ query_or_body => payload })
|
97
97
|
res = conn.public_send(http_method, '/', payload)
|
98
98
|
if query_or_body == :query
|
99
99
|
expect(res.env.request_body).to be_nil
|
@@ -104,7 +104,7 @@ shared_examples 'a request method' do |http_method|
|
|
104
104
|
|
105
105
|
it 'sends url encoded nested parameters' do
|
106
106
|
payload = { name: { first: 'zack' } }
|
107
|
-
request_stub.with(
|
107
|
+
request_stub.with({ query_or_body => payload })
|
108
108
|
conn.public_send(http_method, '/', payload)
|
109
109
|
end
|
110
110
|
|
@@ -126,19 +126,6 @@ shared_examples 'a request method' do |http_method|
|
|
126
126
|
expect { conn.public_send(http_method, '/') }.to raise_error(exc)
|
127
127
|
end
|
128
128
|
|
129
|
-
# Can't send files on get, head and delete methods
|
130
|
-
if method_with_body?(http_method)
|
131
|
-
it 'sends files' do
|
132
|
-
payload = { uploaded_file: multipart_file }
|
133
|
-
request_stub.with(headers: { 'Content-Type' => %r{\Amultipart/form-data} }) do |request|
|
134
|
-
# WebMock does not support matching body for multipart/form-data requests yet :(
|
135
|
-
# https://github.com/bblimke/webmock/issues/623
|
136
|
-
request.body.include?('RubyMultipartPost')
|
137
|
-
end
|
138
|
-
conn.public_send(http_method, '/', payload)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
129
|
on_feature :reason_phrase_parse do
|
143
130
|
it 'parses the reason phrase' do
|
144
131
|
request_stub.to_return(status: [200, 'OK'])
|
@@ -199,11 +186,11 @@ shared_examples 'a request method' do |http_method|
|
|
199
186
|
@payload2 = { b: '2' }
|
200
187
|
|
201
188
|
request_stub
|
202
|
-
.with(
|
189
|
+
.with({ query_or_body => @payload1 })
|
203
190
|
.to_return(body: @payload1.to_json)
|
204
191
|
|
205
192
|
stub_request(http_method, remote)
|
206
|
-
.with(
|
193
|
+
.with({ query_or_body => @payload2 })
|
207
194
|
.to_return(body: @payload2.to_json)
|
208
195
|
|
209
196
|
conn.in_parallel do
|