faraday 1.0.0 → 2.10.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 +299 -1
- data/LICENSE.md +1 -1
- data/README.md +35 -23
- data/Rakefile +6 -1
- data/examples/client_spec.rb +68 -14
- data/examples/client_test.rb +80 -15
- data/lib/faraday/adapter/test.rb +117 -52
- data/lib/faraday/adapter.rb +7 -21
- data/lib/faraday/adapter_registry.rb +3 -1
- data/lib/faraday/connection.rb +75 -133
- data/lib/faraday/encoders/flat_params_encoder.rb +9 -2
- data/lib/faraday/encoders/nested_params_encoder.rb +20 -8
- data/lib/faraday/error.rb +39 -6
- data/lib/faraday/logging/formatter.rb +28 -15
- data/lib/faraday/methods.rb +6 -0
- data/lib/faraday/middleware.rb +59 -5
- data/lib/faraday/middleware_registry.rb +17 -63
- data/lib/faraday/options/connection_options.rb +7 -6
- data/lib/faraday/options/env.rb +85 -62
- data/lib/faraday/options/proxy_options.rb +11 -3
- data/lib/faraday/options/request_options.rb +7 -6
- data/lib/faraday/options/ssl_options.rb +56 -45
- data/lib/faraday/options.rb +11 -14
- data/lib/faraday/rack_builder.rb +35 -32
- data/lib/faraday/request/authorization.rb +37 -36
- data/lib/faraday/request/instrumentation.rb +5 -1
- data/lib/faraday/request/json.rb +70 -0
- data/lib/faraday/request/url_encoded.rb +8 -2
- data/lib/faraday/request.rb +22 -29
- data/lib/faraday/response/json.rb +74 -0
- data/lib/faraday/response/logger.rb +8 -4
- data/lib/faraday/response/raise_error.rb +43 -3
- data/lib/faraday/response.rb +10 -23
- data/lib/faraday/utils/headers.rb +17 -6
- data/lib/faraday/utils.rb +22 -10
- data/lib/faraday/version.rb +5 -0
- data/lib/faraday.rb +49 -58
- data/spec/faraday/adapter/test_spec.rb +442 -0
- data/spec/faraday/connection_spec.rb +207 -90
- data/spec/faraday/error_spec.rb +45 -5
- data/spec/faraday/middleware_registry_spec.rb +31 -0
- data/spec/faraday/middleware_spec.rb +193 -6
- data/spec/faraday/options/env_spec.rb +8 -2
- data/spec/faraday/options/options_spec.rb +1 -1
- data/spec/faraday/options/proxy_options_spec.rb +15 -0
- data/spec/faraday/params_encoders/flat_spec.rb +8 -0
- data/spec/faraday/params_encoders/nested_spec.rb +18 -1
- data/spec/faraday/rack_builder_spec.rb +171 -50
- data/spec/faraday/request/authorization_spec.rb +54 -24
- data/spec/faraday/request/instrumentation_spec.rb +5 -7
- data/spec/faraday/request/json_spec.rb +199 -0
- data/spec/faraday/request/url_encoded_spec.rb +25 -2
- data/spec/faraday/request_spec.rb +11 -10
- data/spec/faraday/response/json_spec.rb +206 -0
- data/spec/faraday/response/logger_spec.rb +38 -0
- data/spec/faraday/response/raise_error_spec.rb +149 -0
- data/spec/faraday/response_spec.rb +3 -1
- data/spec/faraday/utils/headers_spec.rb +31 -4
- data/spec/faraday/utils_spec.rb +63 -1
- data/spec/faraday_spec.rb +10 -4
- data/spec/spec_helper.rb +6 -5
- data/spec/support/fake_safe_buffer.rb +1 -1
- data/spec/support/faraday_middleware_subclasses.rb +18 -0
- data/spec/support/helper_methods.rb +0 -37
- data/spec/support/shared_examples/adapter.rb +4 -3
- data/spec/support/shared_examples/request_method.rb +60 -31
- metadata +34 -44
- data/UPGRADING.md +0 -55
- data/lib/faraday/adapter/em_http.rb +0 -285
- data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
- data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
- data/lib/faraday/adapter/em_synchrony.rb +0 -150
- data/lib/faraday/adapter/excon.rb +0 -124
- data/lib/faraday/adapter/httpclient.rb +0 -151
- data/lib/faraday/adapter/net_http.rb +0 -209
- data/lib/faraday/adapter/net_http_persistent.rb +0 -91
- data/lib/faraday/adapter/patron.rb +0 -132
- data/lib/faraday/adapter/rack.rb +0 -75
- data/lib/faraday/adapter/typhoeus.rb +0 -15
- data/lib/faraday/autoload.rb +0 -95
- 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 -99
- 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 -47
- data/spec/faraday/adapter/em_synchrony_spec.rb +0 -16
- data/spec/faraday/adapter/excon_spec.rb +0 -49
- data/spec/faraday/adapter/httpclient_spec.rb +0 -73
- data/spec/faraday/adapter/net_http_persistent_spec.rb +0 -57
- 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 -274
- data/spec/faraday/request/retry_spec.rb +0 -242
- data/spec/faraday/response/middleware_spec.rb +0 -52
- data/spec/support/webmock_rack_app.rb +0 -68
|
@@ -2,24 +2,25 @@
|
|
|
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
|
|
9
|
-
let(:
|
|
9
|
+
let(:http_method) { :get }
|
|
10
10
|
let(:block) { nil }
|
|
11
11
|
|
|
12
|
-
subject { conn.build_request(
|
|
12
|
+
subject { conn.build_request(http_method, &block) }
|
|
13
13
|
|
|
14
14
|
context 'when nothing particular is configured' do
|
|
15
|
-
it { expect(subject.
|
|
15
|
+
it { expect(subject.http_method).to eq(:get) }
|
|
16
16
|
it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
|
|
17
|
+
it { expect(subject.to_env(conn).ssl.verify_hostname).to be_falsey }
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
context 'when method is post' do
|
|
20
|
-
let(:
|
|
20
|
+
context 'when HTTP method is post' do
|
|
21
|
+
let(:http_method) { :post }
|
|
21
22
|
|
|
22
|
-
it { expect(subject.
|
|
23
|
+
it { expect(subject.http_method).to eq(:post) }
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
context 'when setting the url on setup with a URI' do
|
|
@@ -27,7 +28,7 @@ RSpec.describe Faraday::Request do
|
|
|
27
28
|
|
|
28
29
|
it { expect(subject.path).to eq(URI.parse('foo.json')) }
|
|
29
30
|
it { expect(subject.params).to eq('a' => '1') }
|
|
30
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
|
31
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
context 'when setting the url on setup with a string path and params' do
|
|
@@ -35,7 +36,7 @@ RSpec.describe Faraday::Request do
|
|
|
35
36
|
|
|
36
37
|
it { expect(subject.path).to eq('foo.json') }
|
|
37
38
|
it { expect(subject.params).to eq('a' => 1) }
|
|
38
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
|
39
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
context 'when setting the url on setup with a path including params' do
|
|
@@ -43,7 +44,7 @@ RSpec.describe Faraday::Request do
|
|
|
43
44
|
|
|
44
45
|
it { expect(subject.path).to eq('foo.json') }
|
|
45
46
|
it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
|
|
46
|
-
it { expect(subject.to_env(conn).url.to_s).to eq('http://
|
|
47
|
+
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
context 'when setting a header on setup with []= syntax' do
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
|
|
118
|
+
context 'with decoder' do
|
|
119
|
+
let(:decoder) do
|
|
120
|
+
double('Decoder').tap do |e|
|
|
121
|
+
allow(e).to receive(:load) { |s, opts| JSON.parse(s, opts) }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
let(:body) { '{"a": 1}' }
|
|
126
|
+
let(:result) { { a: 1 } }
|
|
127
|
+
|
|
128
|
+
context 'when decoder is passed as object' do
|
|
129
|
+
let(:options) do
|
|
130
|
+
{
|
|
131
|
+
parser_options: {
|
|
132
|
+
decoder: decoder,
|
|
133
|
+
option: :option_value,
|
|
134
|
+
symbolize_names: true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'passes relevant options to specified decoder\'s load method' do
|
|
140
|
+
expect(decoder).to receive(:load)
|
|
141
|
+
.with(body, { option: :option_value, symbolize_names: true })
|
|
142
|
+
.and_return(result)
|
|
143
|
+
|
|
144
|
+
response = process(body)
|
|
145
|
+
expect(response.body).to eq(result)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'when decoder is passed as an object-method pair' do
|
|
150
|
+
let(:options) do
|
|
151
|
+
{
|
|
152
|
+
parser_options: {
|
|
153
|
+
decoder: [decoder, :load],
|
|
154
|
+
option: :option_value,
|
|
155
|
+
symbolize_names: true
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'passes relevant options to specified decoder\'s method' do
|
|
161
|
+
expect(decoder).to receive(:load)
|
|
162
|
+
.with(body, { option: :option_value, symbolize_names: true })
|
|
163
|
+
.and_return(result)
|
|
164
|
+
|
|
165
|
+
response = process(body)
|
|
166
|
+
expect(response.body).to eq(result)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context 'when decoder is not passed' do
|
|
171
|
+
let(:options) do
|
|
172
|
+
{
|
|
173
|
+
parser_options: {
|
|
174
|
+
symbolize_names: true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'passes relevant options to JSON parse' do
|
|
180
|
+
expect(JSON).to receive(:parse)
|
|
181
|
+
.with(body, { symbolize_names: true })
|
|
182
|
+
.and_return(result)
|
|
183
|
+
|
|
184
|
+
response = process(body)
|
|
185
|
+
expect(response.body).to eq(result)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'passes relevant options to JSON parse even when nil responds to :load' do
|
|
189
|
+
original_allow_message_expectations_on_nil = RSpec::Mocks.configuration.allow_message_expectations_on_nil
|
|
190
|
+
RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
|
|
191
|
+
allow(nil).to receive(:respond_to?)
|
|
192
|
+
.with(:load)
|
|
193
|
+
.and_return(true)
|
|
194
|
+
|
|
195
|
+
expect(JSON).to receive(:parse)
|
|
196
|
+
.with(body, { symbolize_names: true })
|
|
197
|
+
.and_return(result)
|
|
198
|
+
|
|
199
|
+
response = process(body)
|
|
200
|
+
expect(response.body).to eq(result)
|
|
201
|
+
ensure
|
|
202
|
+
RSpec::Mocks.configuration.allow_message_expectations_on_nil = original_allow_message_expectations_on_nil
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -25,6 +25,7 @@ RSpec.describe Faraday::Response::Logger do
|
|
|
25
25
|
stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
|
|
26
26
|
stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
|
|
27
27
|
stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
|
|
28
|
+
stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
end
|
|
@@ -64,6 +65,15 @@ RSpec.describe Faraday::Response::Logger do
|
|
|
64
65
|
expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
|
|
65
66
|
conn.get '/hello'
|
|
66
67
|
end
|
|
68
|
+
|
|
69
|
+
context 'when no route' do
|
|
70
|
+
it 'delegates logging to the formatter' do
|
|
71
|
+
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
|
|
72
|
+
expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
|
|
73
|
+
|
|
74
|
+
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
context 'with custom formatter' do
|
|
@@ -94,6 +104,16 @@ RSpec.describe Faraday::Response::Logger do
|
|
|
94
104
|
expect(string_io.string).to match('GET http:/hello')
|
|
95
105
|
end
|
|
96
106
|
|
|
107
|
+
it 'logs status' do
|
|
108
|
+
conn.get '/hello', nil, accept: 'text/html'
|
|
109
|
+
expect(string_io.string).to match('Status 200')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'does not log error message by default' do
|
|
113
|
+
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
|
114
|
+
expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
|
|
115
|
+
end
|
|
116
|
+
|
|
97
117
|
it 'logs request headers by default' do
|
|
98
118
|
conn.get '/hello', nil, accept: 'text/html'
|
|
99
119
|
expect(string_io.string).to match(%(Accept: "text/html))
|
|
@@ -188,6 +208,24 @@ RSpec.describe Faraday::Response::Logger do
|
|
|
188
208
|
end
|
|
189
209
|
end
|
|
190
210
|
|
|
211
|
+
context 'when logging errors' do
|
|
212
|
+
let(:logger_options) { { errors: true } }
|
|
213
|
+
|
|
214
|
+
it 'logs error message' do
|
|
215
|
+
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
|
216
|
+
expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context 'when logging headers and errors' do
|
|
221
|
+
let(:logger_options) { { headers: true, errors: true } }
|
|
222
|
+
|
|
223
|
+
it 'logs error message' do
|
|
224
|
+
expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
|
|
225
|
+
expect(string_io.string).to match(%(Failed to open TCP connection))
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
191
229
|
context 'when using log_level' do
|
|
192
230
|
let(:logger_options) { { bodies: true, log_level: :debug } }
|
|
193
231
|
|
|
@@ -11,8 +11,10 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
11
11
|
stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
12
12
|
stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
13
13
|
stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
14
|
+
stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
14
15
|
stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
15
16
|
stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
17
|
+
stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
16
18
|
stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
|
|
17
19
|
stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
|
|
18
20
|
stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
|
|
@@ -29,6 +31,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
29
31
|
expect(ex.message).to eq('the server responded with status 400')
|
|
30
32
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
31
33
|
expect(ex.response[:status]).to eq(400)
|
|
34
|
+
expect(ex.response_status).to eq(400)
|
|
35
|
+
expect(ex.response_body).to eq('keep looking')
|
|
36
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
|
|
@@ -37,6 +42,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
37
42
|
expect(ex.message).to eq('the server responded with status 401')
|
|
38
43
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
39
44
|
expect(ex.response[:status]).to eq(401)
|
|
45
|
+
expect(ex.response_status).to eq(401)
|
|
46
|
+
expect(ex.response_body).to eq('keep looking')
|
|
47
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
40
48
|
end
|
|
41
49
|
end
|
|
42
50
|
|
|
@@ -45,6 +53,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
45
53
|
expect(ex.message).to eq('the server responded with status 403')
|
|
46
54
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
47
55
|
expect(ex.response[:status]).to eq(403)
|
|
56
|
+
expect(ex.response_status).to eq(403)
|
|
57
|
+
expect(ex.response_body).to eq('keep looking')
|
|
58
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
48
59
|
end
|
|
49
60
|
end
|
|
50
61
|
|
|
@@ -53,6 +64,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
53
64
|
expect(ex.message).to eq('the server responded with status 404')
|
|
54
65
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
55
66
|
expect(ex.response[:status]).to eq(404)
|
|
67
|
+
expect(ex.response_status).to eq(404)
|
|
68
|
+
expect(ex.response_body).to eq('keep looking')
|
|
69
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
56
70
|
end
|
|
57
71
|
end
|
|
58
72
|
|
|
@@ -61,6 +75,20 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
61
75
|
expect(ex.message).to eq('407 "Proxy Authentication Required"')
|
|
62
76
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
63
77
|
expect(ex.response[:status]).to eq(407)
|
|
78
|
+
expect(ex.response_status).to eq(407)
|
|
79
|
+
expect(ex.response_body).to eq('keep looking')
|
|
80
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'raises Faraday::RequestTimeoutError for 408 responses' do
|
|
85
|
+
expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
|
|
86
|
+
expect(ex.message).to eq('the server responded with status 408')
|
|
87
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
88
|
+
expect(ex.response[:status]).to eq(408)
|
|
89
|
+
expect(ex.response_status).to eq(408)
|
|
90
|
+
expect(ex.response_body).to eq('keep looking')
|
|
91
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
64
92
|
end
|
|
65
93
|
end
|
|
66
94
|
|
|
@@ -69,6 +97,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
69
97
|
expect(ex.message).to eq('the server responded with status 409')
|
|
70
98
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
71
99
|
expect(ex.response[:status]).to eq(409)
|
|
100
|
+
expect(ex.response_status).to eq(409)
|
|
101
|
+
expect(ex.response_body).to eq('keep looking')
|
|
102
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
72
103
|
end
|
|
73
104
|
end
|
|
74
105
|
|
|
@@ -77,6 +108,20 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
77
108
|
expect(ex.message).to eq('the server responded with status 422')
|
|
78
109
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
79
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::TooManyRequestsError for 429 responses' do
|
|
118
|
+
expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
|
|
119
|
+
expect(ex.message).to eq('the server responded with status 429')
|
|
120
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
121
|
+
expect(ex.response[:status]).to eq(429)
|
|
122
|
+
expect(ex.response_status).to eq(429)
|
|
123
|
+
expect(ex.response_body).to eq('keep looking')
|
|
124
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
80
125
|
end
|
|
81
126
|
end
|
|
82
127
|
|
|
@@ -85,6 +130,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
85
130
|
expect(ex.message).to eq('http status could not be derived from the server response')
|
|
86
131
|
expect(ex.response[:headers]['X-Reason']).to eq('nil')
|
|
87
132
|
expect(ex.response[:status]).to be_nil
|
|
133
|
+
expect(ex.response_status).to be_nil
|
|
134
|
+
expect(ex.response_body).to eq('fail')
|
|
135
|
+
expect(ex.response_headers['X-Reason']).to eq('nil')
|
|
88
136
|
end
|
|
89
137
|
end
|
|
90
138
|
|
|
@@ -93,6 +141,9 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
93
141
|
expect(ex.message).to eq('the server responded with status 499')
|
|
94
142
|
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
|
95
143
|
expect(ex.response[:status]).to eq(499)
|
|
144
|
+
expect(ex.response_status).to eq(499)
|
|
145
|
+
expect(ex.response_body).to eq('keep looking')
|
|
146
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
|
96
147
|
end
|
|
97
148
|
end
|
|
98
149
|
|
|
@@ -101,6 +152,104 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
|
101
152
|
expect(ex.message).to eq('the server responded with status 500')
|
|
102
153
|
expect(ex.response[:headers]['X-Error']).to eq('bailout')
|
|
103
154
|
expect(ex.response[:status]).to eq(500)
|
|
155
|
+
expect(ex.response_status).to eq(500)
|
|
156
|
+
expect(ex.response_body).to eq('fail')
|
|
157
|
+
expect(ex.response_headers['X-Error']).to eq('bailout')
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe 'request info' do
|
|
162
|
+
let(:conn) do
|
|
163
|
+
Faraday.new do |b|
|
|
164
|
+
b.response :raise_error, **middleware_options
|
|
165
|
+
b.adapter :test do |stub|
|
|
166
|
+
stub.post(url, request_body, request_headers) do
|
|
167
|
+
[400, { 'X-Reason' => 'because' }, 'keep looking']
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
let(:middleware_options) { {} }
|
|
173
|
+
let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
|
|
174
|
+
let(:request_headers) { { 'Authorization' => 'Basic 123' } }
|
|
175
|
+
let(:url_path) { 'request' }
|
|
176
|
+
let(:query_params) { 'full=true' }
|
|
177
|
+
let(:url) { "#{url_path}?#{query_params}" }
|
|
178
|
+
|
|
179
|
+
subject(:perform_request) do
|
|
180
|
+
conn.post url do |req|
|
|
181
|
+
req.headers['Authorization'] = 'Basic 123'
|
|
182
|
+
req.body = request_body
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'returns the request info in the exception' do
|
|
187
|
+
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
|
188
|
+
expect(ex.response[:request][:method]).to eq(:post)
|
|
189
|
+
expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
|
|
190
|
+
expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
|
|
191
|
+
expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
|
|
192
|
+
expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
|
|
193
|
+
expect(ex.response[:request][:body]).to eq(request_body)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
describe 'DEFAULT_OPTION: include_request' do
|
|
198
|
+
before(:each) do
|
|
199
|
+
Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
|
|
200
|
+
Faraday::Middleware.instance_variable_set(:@default_options, nil)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
after(:all) do
|
|
204
|
+
Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
|
|
205
|
+
Faraday::Middleware.instance_variable_set(:@default_options, nil)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
|
|
209
|
+
it 'includes request info in the exception' do
|
|
210
|
+
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
|
211
|
+
expect(ex.response.keys).to contain_exactly(
|
|
212
|
+
:status,
|
|
213
|
+
:headers,
|
|
214
|
+
:body,
|
|
215
|
+
:request
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context 'when application sets default_options `include_request: false`' do
|
|
222
|
+
before(:each) do
|
|
223
|
+
Faraday::Response::RaiseError.default_options = { include_request: false }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
context 'and when include_request option is omitted' do
|
|
227
|
+
it 'does not include request info in the exception' do
|
|
228
|
+
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
|
229
|
+
expect(ex.response.keys).to contain_exactly(
|
|
230
|
+
:status,
|
|
231
|
+
:headers,
|
|
232
|
+
:body
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
context 'and when include_request option is explicitly set for instance' do
|
|
239
|
+
let(:middleware_options) { { include_request: true } }
|
|
240
|
+
|
|
241
|
+
it 'includes request info in the exception' do
|
|
242
|
+
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
|
243
|
+
expect(ex.response.keys).to contain_exactly(
|
|
244
|
+
:status,
|
|
245
|
+
:headers,
|
|
246
|
+
:body,
|
|
247
|
+
:request
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
104
253
|
end
|
|
105
254
|
end
|
|
106
255
|
end
|
|
@@ -4,7 +4,7 @@ RSpec.describe Faraday::Response do
|
|
|
4
4
|
subject { Faraday::Response.new(env) }
|
|
5
5
|
|
|
6
6
|
let(:env) do
|
|
7
|
-
Faraday::Env.from(status: 404, body: 'yikes',
|
|
7
|
+
Faraday::Env.from(status: 404, body: 'yikes', url: Faraday::Utils.URI('https://lostisland.github.io/faraday'),
|
|
8
8
|
response_headers: { 'Content-Type' => 'text/plain' })
|
|
9
9
|
end
|
|
10
10
|
|
|
@@ -30,6 +30,7 @@ RSpec.describe Faraday::Response do
|
|
|
30
30
|
it { expect(hash[:status]).to eq(subject.status) }
|
|
31
31
|
it { expect(hash[:response_headers]).to eq(subject.headers) }
|
|
32
32
|
it { expect(hash[:body]).to eq(subject.body) }
|
|
33
|
+
it { expect(hash[:url]).to eq(subject.env.url) }
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
describe 'marshal serialization support' do
|
|
@@ -45,6 +46,7 @@ RSpec.describe Faraday::Response do
|
|
|
45
46
|
it { expect(loaded.env[:body]).to eq(env[:body]) }
|
|
46
47
|
it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
|
|
47
48
|
it { expect(loaded.env[:status]).to eq(env[:status]) }
|
|
49
|
+
it { expect(loaded.env[:url]).to eq(env[:url]) }
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
describe '#on_complete' do
|
|
@@ -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 '#
|
|
60
|
-
before { subject
|
|
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://
|
|
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
|
-
|
|
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
|