faraday 1.4.1 → 2.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +198 -4
  3. data/LICENSE.md +1 -1
  4. data/README.md +34 -20
  5. data/Rakefile +6 -1
  6. data/examples/client_spec.rb +67 -13
  7. data/examples/client_test.rb +81 -16
  8. data/lib/faraday/adapter/test.rb +117 -52
  9. data/lib/faraday/adapter.rb +12 -20
  10. data/lib/faraday/connection.rb +86 -134
  11. data/lib/faraday/encoders/flat_params_encoder.rb +3 -2
  12. data/lib/faraday/encoders/nested_params_encoder.rb +15 -7
  13. data/lib/faraday/error.rb +65 -15
  14. data/lib/faraday/logging/formatter.rb +30 -17
  15. data/lib/faraday/middleware.rb +44 -3
  16. data/lib/faraday/middleware_registry.rb +17 -63
  17. data/lib/faraday/options/connection_options.rb +7 -6
  18. data/lib/faraday/options/env.rb +88 -65
  19. data/lib/faraday/options/proxy_options.rb +17 -6
  20. data/lib/faraday/options/request_options.rb +8 -7
  21. data/lib/faraday/options/ssl_options.rb +62 -45
  22. data/lib/faraday/options.rb +8 -7
  23. data/lib/faraday/rack_builder.rb +46 -47
  24. data/lib/faraday/request/authorization.rb +37 -38
  25. data/lib/faraday/request/instrumentation.rb +5 -1
  26. data/lib/faraday/request/json.rb +70 -0
  27. data/lib/faraday/request/url_encoded.rb +5 -1
  28. data/lib/faraday/request.rb +20 -37
  29. data/lib/faraday/response/json.rb +74 -0
  30. data/lib/faraday/response/logger.rb +13 -7
  31. data/lib/faraday/response/raise_error.rb +45 -18
  32. data/lib/faraday/response.rb +15 -21
  33. data/lib/faraday/utils/headers.rb +18 -7
  34. data/lib/faraday/utils.rb +11 -7
  35. data/lib/faraday/version.rb +1 -1
  36. data/lib/faraday.rb +12 -32
  37. data/spec/faraday/adapter/test_spec.rb +182 -0
  38. data/spec/faraday/connection_spec.rb +219 -92
  39. data/spec/faraday/error_spec.rb +122 -7
  40. data/spec/faraday/middleware_registry_spec.rb +31 -0
  41. data/spec/faraday/middleware_spec.rb +163 -2
  42. data/spec/faraday/options/env_spec.rb +8 -2
  43. data/spec/faraday/options/options_spec.rb +1 -1
  44. data/spec/faraday/options/proxy_options_spec.rb +42 -0
  45. data/spec/faraday/params_encoders/nested_spec.rb +10 -1
  46. data/spec/faraday/rack_builder_spec.rb +26 -54
  47. data/spec/faraday/request/authorization_spec.rb +54 -24
  48. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  49. data/spec/faraday/request/json_spec.rb +199 -0
  50. data/spec/faraday/request/url_encoded_spec.rb +12 -2
  51. data/spec/faraday/request_spec.rb +14 -15
  52. data/spec/faraday/response/json_spec.rb +206 -0
  53. data/spec/faraday/response/logger_spec.rb +84 -5
  54. data/spec/faraday/response/raise_error_spec.rb +133 -16
  55. data/spec/faraday/response_spec.rb +10 -1
  56. data/spec/faraday/utils/headers_spec.rb +31 -4
  57. data/spec/faraday/utils_spec.rb +66 -2
  58. data/spec/faraday_spec.rb +10 -4
  59. data/spec/spec_helper.rb +6 -5
  60. data/spec/support/fake_safe_buffer.rb +1 -1
  61. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  62. data/spec/support/helper_methods.rb +0 -37
  63. data/spec/support/shared_examples/adapter.rb +2 -2
  64. data/spec/support/shared_examples/request_method.rb +22 -21
  65. metadata +27 -81
  66. data/lib/faraday/adapter/em_http.rb +0 -289
  67. data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -62
  68. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -69
  69. data/lib/faraday/adapter/em_synchrony.rb +0 -153
  70. data/lib/faraday/adapter/httpclient.rb +0 -152
  71. data/lib/faraday/adapter/patron.rb +0 -132
  72. data/lib/faraday/adapter/rack.rb +0 -75
  73. data/lib/faraday/adapter/typhoeus.rb +0 -15
  74. data/lib/faraday/autoload.rb +0 -92
  75. data/lib/faraday/dependency_loader.rb +0 -37
  76. data/lib/faraday/file_part.rb +0 -128
  77. data/lib/faraday/param_part.rb +0 -53
  78. data/lib/faraday/request/basic_authentication.rb +0 -20
  79. data/lib/faraday/request/multipart.rb +0 -106
  80. data/lib/faraday/request/retry.rb +0 -239
  81. data/lib/faraday/request/token_authentication.rb +0 -20
  82. data/spec/faraday/adapter/em_http_spec.rb +0 -47
  83. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -16
  84. data/spec/faraday/adapter/excon_spec.rb +0 -49
  85. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  86. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  87. data/spec/faraday/adapter/patron_spec.rb +0 -18
  88. data/spec/faraday/adapter/rack_spec.rb +0 -8
  89. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  90. data/spec/faraday/composite_read_io_spec.rb +0 -80
  91. data/spec/faraday/request/multipart_spec.rb +0 -302
  92. data/spec/faraday/request/retry_spec.rb +0 -242
  93. data/spec/faraday/response/middleware_spec.rb +0 -68
  94. data/spec/support/webmock_rack_app.rb +0 -68
@@ -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: 'Token token="bar"') }
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('Token token="bar"')
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) { :basic_auth }
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,73 @@ RSpec.describe Faraday::Request::Authorization do
44
44
  end
45
45
  end
46
46
 
47
- describe 'token_auth' do
48
- let(:auth_type) { :token_auth }
47
+ describe 'authorization' do
48
+ let(:auth_type) { :Bearer }
49
49
 
50
- context 'when passed correct params' do
51
- let(:auth_config) { 'quux' }
50
+ context 'when passed a string' do
51
+ let(:auth_config) { ['custom'] }
52
52
 
53
- it { expect(response.body).to eq('Token token="quux"') }
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 other values are provided' do
59
- let(:auth_config) { ['baz', { foo: 42 }] }
58
+ context 'when passed a proc' do
59
+ let(:auth_config) { [-> { 'custom_from_proc' }] }
60
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"/) }
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 two strings' do
73
- let(:auth_config) { ['custom', 'abc def'] }
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('custom abc def') }
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 a string and a hash' do
81
- let(:auth_config) { ['baz', { foo: 42 }] }
75
+ context 'with an argument' do
76
+ let(:response) { conn.get('/auth-echo', nil, 'middle' => 'crunchy surprise') }
77
+
78
+ context 'when passed a proc' do
79
+ let(:auth_config) { [proc { |env| "proc #{env.request_headers['middle']}" }] }
80
+
81
+ it { expect(response.body).to eq('Bearer proc crunchy surprise') }
82
+
83
+ include_examples 'does not interfere with existing authentication'
84
+ end
85
+
86
+ context 'when passed a lambda' do
87
+ let(:auth_config) { [->(env) { "lambda #{env.request_headers['middle']}" }] }
88
+
89
+ it { expect(response.body).to eq('Bearer lambda crunchy surprise') }
90
+
91
+ include_examples 'does not interfere with existing authentication'
92
+ end
93
+
94
+ context 'when passed a callable with an argument' do
95
+ let(:callable) do
96
+ Class.new do
97
+ def call(env)
98
+ "callable #{env.request_headers['middle']}"
99
+ end
100
+ end.new
101
+ end
102
+ let(:auth_config) { [callable] }
103
+
104
+ it { expect(response.body).to eq('Bearer callable crunchy surprise') }
105
+
106
+ include_examples 'does not interfere with existing authentication'
107
+ end
108
+ end
109
+
110
+ context 'when passed too many arguments' do
111
+ let(:auth_config) { %w[baz foo] }
82
112
 
83
- it { expect(response.body).to eq('baz foo="42"') }
113
+ it { expect { response }.to raise_error(ArgumentError) }
84
114
 
85
115
  include_examples 'does not interfere with existing authentication'
86
116
  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
- 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
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,199 @@
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 'true body' do
77
+ let(:result) { process(true) }
78
+
79
+ it 'encodes body' do
80
+ expect(result_body).to eq('true')
81
+ end
82
+
83
+ it 'adds content type' do
84
+ expect(result_type).to eq('application/json')
85
+ end
86
+ end
87
+
88
+ context 'false body' do
89
+ let(:result) { process(false) }
90
+
91
+ it 'encodes body' do
92
+ expect(result_body).to eq('false')
93
+ end
94
+
95
+ it 'adds content type' do
96
+ expect(result_type).to eq('application/json')
97
+ end
98
+ end
99
+
100
+ context 'object body with json type' do
101
+ let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
102
+
103
+ it 'encodes 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/json; charset=utf-8')
109
+ end
110
+ end
111
+
112
+ context 'object body with vendor json type' do
113
+ let(:result) { process({ a: 1 }, 'application/vnd.myapp.v1+json; charset=utf-8') }
114
+
115
+ it 'encodes body' do
116
+ expect(result_body).to eq('{"a":1}')
117
+ end
118
+
119
+ it "doesn't change content type" do
120
+ expect(result_type).to eq('application/vnd.myapp.v1+json; charset=utf-8')
121
+ end
122
+ end
123
+
124
+ context 'object body with incompatible type' do
125
+ let(:result) { process({ a: 1 }, 'application/xml; charset=utf-8') }
126
+
127
+ it "doesn't change body" do
128
+ expect(result_body).to eq(a: 1)
129
+ end
130
+
131
+ it "doesn't change content type" do
132
+ expect(result_type).to eq('application/xml; charset=utf-8')
133
+ end
134
+ end
135
+
136
+ context 'with encoder' do
137
+ let(:encoder) do
138
+ double('Encoder').tap do |e|
139
+ allow(e).to receive(:dump) { |s, opts| JSON.generate(s, opts) }
140
+ end
141
+ end
142
+
143
+ let(:result) { process(a: 1) }
144
+
145
+ context 'when encoder is passed as object' do
146
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: encoder }) }
147
+
148
+ it 'calls specified JSON encoder\'s dump method' do
149
+ expect(encoder).to receive(:dump).with({ a: 1 })
150
+
151
+ result
152
+ end
153
+
154
+ it 'encodes body' do
155
+ expect(result_body).to eq('{"a":1}')
156
+ end
157
+
158
+ it 'adds content type' do
159
+ expect(result_type).to eq('application/json')
160
+ end
161
+ end
162
+
163
+ context 'when encoder is passed as an object-method pair' do
164
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: [encoder, :dump] }) }
165
+
166
+ it 'calls specified JSON encoder' do
167
+ expect(encoder).to receive(:dump).with({ a: 1 })
168
+
169
+ result
170
+ end
171
+
172
+ it 'encodes body' do
173
+ expect(result_body).to eq('{"a":1}')
174
+ end
175
+
176
+ it 'adds content type' do
177
+ expect(result_type).to eq('application/json')
178
+ end
179
+ end
180
+
181
+ context 'when encoder is not passed' do
182
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
183
+
184
+ it 'calls JSON.generate' do
185
+ expect(JSON).to receive(:generate).with({ a: 1 })
186
+
187
+ result
188
+ end
189
+
190
+ it 'encodes body' do
191
+ expect(result_body).to eq('{"a":1}')
192
+ end
193
+
194
+ it 'adds content type' do
195
+ expect(result_type).to eq('application/json')
196
+ end
197
+ end
198
+ end
199
+ end
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  RSpec.describe Faraday::Request::UrlEncoded do
4
6
  let(:conn) do
5
7
  Faraday.new do |b|
6
- b.request :multipart
7
8
  b.request :url_encoded
8
9
  b.adapter :test do |stub|
9
10
  stub.post('/echo') do |env|
10
11
  posted_as = env[:request_headers]['Content-Type']
11
- [200, { 'Content-Type' => posted_as }, env[:body]]
12
+ body = env[:body]
13
+ if body.respond_to?(:read)
14
+ body = body.read
15
+ end
16
+ [200, { 'Content-Type' => posted_as }, body]
12
17
  end
13
18
  end
14
19
  end
@@ -68,6 +73,11 @@ RSpec.describe Faraday::Request::UrlEncoded do
68
73
  expect(response.body).to eq('a%5Bb%5D%5Bc%5D%5B%5D=d')
69
74
  end
70
75
 
76
+ it 'works with files' do
77
+ response = conn.post('/echo', StringIO.new('str=apple'))
78
+ expect(response.body).to eq('str=apple')
79
+ end
80
+
71
81
  context 'customising default_space_encoding' do
72
82
  around do |example|
73
83
  Faraday::Utils.default_space_encoding = '%20'
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe Faraday::Request do
4
4
  let(:conn) do
5
- Faraday.new(url: 'http://sushi.com/api',
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
@@ -14,6 +14,7 @@ RSpec.describe Faraday::Request do
14
14
  context 'when nothing particular is configured' do
15
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
20
  context 'when HTTP method is post' do
@@ -22,23 +23,21 @@ RSpec.describe Faraday::Request do
22
23
  it { expect(subject.http_method).to eq(:post) }
23
24
  end
24
25
 
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
26
  context 'when setting the url on setup with a URI' do
37
27
  let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
38
28
 
39
29
  it { expect(subject.path).to eq(URI.parse('foo.json')) }
40
30
  it { expect(subject.params).to eq('a' => '1') }
41
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
31
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
32
+ end
33
+
34
+ context 'when setting the url on setup with a protocol-relative URI' do
35
+ let(:block) { proc { |req| req.url URI.parse('//evil.com/path?token=1') } }
36
+ let(:url) { subject.to_env(conn).url }
37
+
38
+ it { expect(url.host).to eq('httpbingo.org') }
39
+ it { expect(url.path).to eq('/api///evil.com/path') }
40
+ it { expect(url.query).to eq('token=1') }
42
41
  end
43
42
 
44
43
  context 'when setting the url on setup with a string path and params' do
@@ -46,7 +45,7 @@ RSpec.describe Faraday::Request do
46
45
 
47
46
  it { expect(subject.path).to eq('foo.json') }
48
47
  it { expect(subject.params).to eq('a' => 1) }
49
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
48
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
50
49
  end
51
50
 
52
51
  context 'when setting the url on setup with a path including params' do
@@ -54,7 +53,7 @@ RSpec.describe Faraday::Request do
54
53
 
55
54
  it { expect(subject.path).to eq('foo.json') }
56
55
  it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
57
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1&b=2') }
56
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
58
57
  end
59
58
 
60
59
  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