faraday 2.7.0 → 2.13.4

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/LICENSE.md +1 -1
  4. data/README.md +29 -17
  5. data/Rakefile +6 -1
  6. data/lib/faraday/adapter/test.rb +20 -7
  7. data/lib/faraday/adapter.rb +2 -3
  8. data/lib/faraday/connection.rb +35 -32
  9. data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
  10. data/lib/faraday/error.rb +59 -7
  11. data/lib/faraday/logging/formatter.rb +18 -20
  12. data/lib/faraday/middleware.rb +40 -1
  13. data/lib/faraday/options/connection_options.rb +7 -6
  14. data/lib/faraday/options/env.rb +68 -63
  15. data/lib/faraday/options/proxy_options.rb +11 -5
  16. data/lib/faraday/options/request_options.rb +7 -6
  17. data/lib/faraday/options/ssl_options.rb +57 -50
  18. data/lib/faraday/options.rb +4 -3
  19. data/lib/faraday/rack_builder.rb +21 -25
  20. data/lib/faraday/request/instrumentation.rb +3 -1
  21. data/lib/faraday/request/json.rb +18 -3
  22. data/lib/faraday/request.rb +10 -7
  23. data/lib/faraday/response/json.rb +21 -1
  24. data/lib/faraday/response/logger.rb +7 -5
  25. data/lib/faraday/response/raise_error.rb +36 -17
  26. data/lib/faraday/response.rb +2 -1
  27. data/lib/faraday/utils/headers.rb +8 -2
  28. data/lib/faraday/utils.rb +3 -4
  29. data/lib/faraday/version.rb +1 -1
  30. data/lib/faraday.rb +2 -1
  31. data/spec/faraday/adapter/test_spec.rb +29 -0
  32. data/spec/faraday/connection_spec.rb +17 -2
  33. data/spec/faraday/error_spec.rb +122 -7
  34. data/spec/faraday/middleware_spec.rb +143 -0
  35. data/spec/faraday/options/options_spec.rb +1 -1
  36. data/spec/faraday/options/proxy_options_spec.rb +35 -0
  37. data/spec/faraday/params_encoders/nested_spec.rb +2 -1
  38. data/spec/faraday/request/json_spec.rb +88 -0
  39. data/spec/faraday/response/json_spec.rb +89 -0
  40. data/spec/faraday/response/logger_spec.rb +50 -5
  41. data/spec/faraday/response/raise_error_spec.rb +112 -9
  42. data/spec/faraday/response_spec.rb +3 -1
  43. data/spec/faraday/utils/headers_spec.rb +9 -0
  44. data/spec/faraday/utils_spec.rb +3 -1
  45. data/spec/faraday_spec.rb +10 -4
  46. data/spec/spec_helper.rb +6 -5
  47. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  48. metadata +26 -14
@@ -10,11 +10,13 @@ module Faraday
10
10
  # lifecycle to a given Logger object. By default, this logs to STDOUT. See
11
11
  # Faraday::Logging::Formatter to see specifically what is logged.
12
12
  class Logger < Middleware
13
+ DEFAULT_OPTIONS = { formatter: Logging::Formatter }.merge(Logging::Formatter::DEFAULT_OPTIONS).freeze
14
+
13
15
  def initialize(app, logger = nil, options = {})
14
- super(app)
16
+ super(app, options)
15
17
  logger ||= ::Logger.new($stdout)
16
- formatter_class = options.delete(:formatter) || Logging::Formatter
17
- @formatter = formatter_class.new(logger: logger, options: options)
18
+ formatter_class = @options.delete(:formatter)
19
+ @formatter = formatter_class.new(logger: logger, options: @options)
18
20
  yield @formatter if block_given?
19
21
  end
20
22
 
@@ -27,8 +29,8 @@ module Faraday
27
29
  @formatter.response(env)
28
30
  end
29
31
 
30
- def on_error(error)
31
- @formatter.error(error) if @formatter.respond_to?(:error)
32
+ def on_error(exc)
33
+ @formatter.exception(exc) if @formatter.respond_to?(:exception)
32
34
  end
33
35
  end
34
36
  end
@@ -6,28 +6,32 @@ module Faraday
6
6
  # client or server error responses.
7
7
  class RaiseError < Middleware
8
8
  # rubocop:disable Naming/ConstantName
9
- ClientErrorStatuses = (400...500).freeze
10
- ServerErrorStatuses = (500...600).freeze
9
+ ClientErrorStatuses = (400...500)
10
+ ServerErrorStatuses = (500...600)
11
+ ClientErrorStatusesWithCustomExceptions = {
12
+ 400 => Faraday::BadRequestError,
13
+ 401 => Faraday::UnauthorizedError,
14
+ 403 => Faraday::ForbiddenError,
15
+ 404 => Faraday::ResourceNotFound,
16
+ 408 => Faraday::RequestTimeoutError,
17
+ 409 => Faraday::ConflictError,
18
+ 422 => Faraday::UnprocessableEntityError,
19
+ 429 => Faraday::TooManyRequestsError
20
+ }.freeze
11
21
  # rubocop:enable Naming/ConstantName
12
22
 
23
+ DEFAULT_OPTIONS = { include_request: true, allowed_statuses: [] }.freeze
24
+
13
25
  def on_complete(env)
26
+ return if Array(options[:allowed_statuses]).include?(env[:status])
27
+
14
28
  case env[:status]
15
- when 400
16
- raise Faraday::BadRequestError, response_values(env)
17
- when 401
18
- raise Faraday::UnauthorizedError, response_values(env)
19
- when 403
20
- raise Faraday::ForbiddenError, response_values(env)
21
- when 404
22
- raise Faraday::ResourceNotFound, response_values(env)
29
+ when *ClientErrorStatusesWithCustomExceptions.keys
30
+ raise ClientErrorStatusesWithCustomExceptions[env[:status]], response_values(env)
23
31
  when 407
24
32
  # mimic the behavior that we get with proxy requests with HTTPS
25
33
  msg = %(407 "Proxy Authentication Required")
26
34
  raise Faraday::ProxyAuthError.new(msg, response_values(env))
27
- when 409
28
- raise Faraday::ConflictError, response_values(env)
29
- when 422
30
- raise Faraday::UnprocessableEntityError, response_values(env)
31
35
  when ClientErrorStatuses
32
36
  raise Faraday::ClientError, response_values(env)
33
37
  when ServerErrorStatuses
@@ -37,11 +41,26 @@ module Faraday
37
41
  end
38
42
  end
39
43
 
44
+ # Returns a hash of response data with the following keys:
45
+ # - status
46
+ # - headers
47
+ # - body
48
+ # - request
49
+ #
50
+ # The `request` key is omitted when the middleware is explicitly
51
+ # configured with the option `include_request: false`.
40
52
  def response_values(env)
41
- {
53
+ response = {
42
54
  status: env.status,
43
55
  headers: env.response_headers,
44
- body: env.body,
56
+ body: env.body
57
+ }
58
+
59
+ # Include the request data by default. If the middleware was explicitly
60
+ # configured to _not_ include request data, then omit it.
61
+ return response unless options[:include_request]
62
+
63
+ response.merge(
45
64
  request: {
46
65
  method: env.method,
47
66
  url: env.url,
@@ -50,7 +69,7 @@ module Faraday
50
69
  headers: env.request_headers,
51
70
  body: env.request_body
52
71
  }
53
- }
72
+ )
54
73
  end
55
74
 
56
75
  def query_params(env)
@@ -61,7 +61,8 @@ module Faraday
61
61
  def to_hash
62
62
  {
63
63
  status: env.status, body: env.body,
64
- response_headers: env.response_headers
64
+ response_headers: env.response_headers,
65
+ url: env.url
65
66
  }
66
67
  end
67
68
 
@@ -62,10 +62,10 @@ module Faraday
62
62
  super(key, val)
63
63
  end
64
64
 
65
- def fetch(key, *args, &block)
65
+ def fetch(key, ...)
66
66
  key = KeyMap[key]
67
67
  key = @names.fetch(key.downcase, key)
68
- super(key, *args, &block)
68
+ super(key, ...)
69
69
  end
70
70
 
71
71
  def delete(key)
@@ -77,6 +77,12 @@ module Faraday
77
77
  super(key)
78
78
  end
79
79
 
80
+ def dig(key, *rest)
81
+ key = KeyMap[key]
82
+ key = @names.fetch(key.downcase, key)
83
+ super(key, *rest)
84
+ end
85
+
80
86
  def include?(key)
81
87
  @names.include? key.downcase
82
88
  end
data/lib/faraday/utils.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
3
  require 'uri'
5
4
  require 'faraday/utils/headers'
6
5
  require 'faraday/utils/params_hash'
@@ -26,7 +25,7 @@ module Faraday
26
25
  attr_writer :default_space_encoding
27
26
  end
28
27
 
29
- ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/.freeze
28
+ ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/
30
29
 
31
30
  def escape(str)
32
31
  str.to_s.gsub(ESCAPE_RE) do |match|
@@ -38,7 +37,7 @@ module Faraday
38
37
  CGI.unescape str.to_s
39
38
  end
40
39
 
41
- DEFAULT_SEP = /[&;] */n.freeze
40
+ DEFAULT_SEP = /[&;] */n
42
41
 
43
42
  # Adapted from Rack
44
43
  def parse_query(query)
@@ -54,7 +53,7 @@ module Faraday
54
53
  end
55
54
 
56
55
  def basic_header_from(login, pass)
57
- value = Base64.encode64("#{login}:#{pass}")
56
+ value = ["#{login}:#{pass}"].pack('m') # Base64 encoding
58
57
  value.delete!("\n")
59
58
  "Basic #{value}"
60
59
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Faraday
4
- VERSION = '2.7.0'
4
+ VERSION = '2.13.4'
5
5
  end
data/lib/faraday.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cgi'
3
+ require 'cgi/escape'
4
+ require 'cgi/util' if RUBY_VERSION < '3.5'
4
5
  require 'date'
5
6
  require 'set'
6
7
  require 'forwardable'
@@ -410,4 +410,33 @@ RSpec.describe Faraday::Adapter::Test do
410
410
  end
411
411
  end
412
412
  end
413
+
414
+ describe 'request timeout' do
415
+ subject(:request) do
416
+ connection.get('/sleep') do |req|
417
+ req.options.timeout = timeout
418
+ end
419
+ end
420
+
421
+ before do
422
+ stubs.get('/sleep') do
423
+ sleep(0.01)
424
+ [200, {}, '']
425
+ end
426
+ end
427
+
428
+ context 'when request is within timeout' do
429
+ let(:timeout) { 1 }
430
+
431
+ it { expect(request.status).to eq 200 }
432
+ end
433
+
434
+ context 'when request is too slow' do
435
+ let(:timeout) { 0.001 }
436
+
437
+ it 'raises an exception' do
438
+ expect { request }.to raise_error(Faraday::TimeoutError)
439
+ end
440
+ end
441
+ end
413
442
  end
@@ -300,14 +300,29 @@ RSpec.describe Faraday::Connection do
300
300
  it 'joins url to base when used relative path' do
301
301
  conn = Faraday.new(url: url)
302
302
  uri = conn.build_exclusive_url('service:search?limit=400')
303
- expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
303
+ expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
304
304
  end
305
305
 
306
306
  it 'joins url to base when used with path prefix' do
307
307
  conn = Faraday.new(url: url)
308
308
  conn.path_prefix = '/api'
309
309
  uri = conn.build_exclusive_url('service:search?limit=400')
310
- expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
310
+ expect(uri.to_s).to eq('http://service.com/api/service:search?limit=400')
311
+ end
312
+ end
313
+
314
+ context 'with a custom `default_uri_parser`' do
315
+ let(:url) { 'http://httpbingo.org' }
316
+ let(:parser) { Addressable::URI }
317
+
318
+ around do |example|
319
+ with_default_uri_parser(parser) do
320
+ example.run
321
+ end
322
+ end
323
+
324
+ it 'does not raise error' do
325
+ expect { conn.build_exclusive_url('/nigiri') }.not_to raise_error
311
326
  end
312
327
  end
313
328
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe Faraday::ClientError do
3
+ RSpec.describe Faraday::Error do
4
4
  describe '.initialize' do
5
5
  subject { described_class.new(exception, response) }
6
6
  let(:response) { nil }
@@ -12,8 +12,10 @@ RSpec.describe Faraday::ClientError do
12
12
  it { expect(subject.response).to be_nil }
13
13
  it { expect(subject.message).to eq(exception.message) }
14
14
  it { expect(subject.backtrace).to eq(exception.backtrace) }
15
- it { expect(subject.inspect).to eq('#<Faraday::ClientError wrapped=#<RuntimeError: test>>') }
15
+ it { expect(subject.inspect).to eq('#<Faraday::Error wrapped=#<RuntimeError: test>>') }
16
16
  it { expect(subject.response_status).to be_nil }
17
+ it { expect(subject.response_headers).to be_nil }
18
+ it { expect(subject.response_body).to be_nil }
17
19
  end
18
20
 
19
21
  context 'with response hash' do
@@ -21,9 +23,15 @@ RSpec.describe Faraday::ClientError do
21
23
 
22
24
  it { expect(subject.wrapped_exception).to be_nil }
23
25
  it { expect(subject.response).to eq(exception) }
24
- it { expect(subject.message).to eq('the server responded with status 400') }
25
- it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
26
+ it { expect(subject.message).to eq('the server responded with status 400 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
27
+ if RUBY_VERSION >= '3.4'
28
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
29
+ else
30
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
31
+ end
26
32
  it { expect(subject.response_status).to eq(400) }
33
+ it { expect(subject.response_headers).to be_nil }
34
+ it { expect(subject.response_body).to be_nil }
27
35
  end
28
36
 
29
37
  context 'with string' do
@@ -32,8 +40,10 @@ RSpec.describe Faraday::ClientError do
32
40
  it { expect(subject.wrapped_exception).to be_nil }
33
41
  it { expect(subject.response).to be_nil }
34
42
  it { expect(subject.message).to eq('custom message') }
35
- it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: custom message>>') }
43
+ it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: custom message>>') }
36
44
  it { expect(subject.response_status).to be_nil }
45
+ it { expect(subject.response_headers).to be_nil }
46
+ it { expect(subject.response_body).to be_nil }
37
47
  end
38
48
 
39
49
  context 'with anything else #to_s' do
@@ -42,8 +52,10 @@ RSpec.describe Faraday::ClientError do
42
52
  it { expect(subject.wrapped_exception).to be_nil }
43
53
  it { expect(subject.response).to be_nil }
44
54
  it { expect(subject.message).to eq('["error1", "error2"]') }
45
- it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: ["error1", "error2"]>>') }
55
+ it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: ["error1", "error2"]>>') }
46
56
  it { expect(subject.response_status).to be_nil }
57
+ it { expect(subject.response_headers).to be_nil }
58
+ it { expect(subject.response_body).to be_nil }
47
59
  end
48
60
 
49
61
  context 'with exception string and response hash' do
@@ -53,8 +65,111 @@ RSpec.describe Faraday::ClientError do
53
65
  it { expect(subject.wrapped_exception).to be_nil }
54
66
  it { expect(subject.response).to eq(response) }
55
67
  it { expect(subject.message).to eq('custom message') }
56
- it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
68
+ if RUBY_VERSION >= '3.4'
69
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
70
+ else
71
+ it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
72
+ end
57
73
  it { expect(subject.response_status).to eq(400) }
74
+ it { expect(subject.response_headers).to be_nil }
75
+ it { expect(subject.response_body).to be_nil }
76
+ end
77
+
78
+ context 'with exception and response object' do
79
+ let(:exception) { RuntimeError.new('test') }
80
+ let(:body) { { test: 'test' } }
81
+ let(:headers) { { 'Content-Type' => 'application/json' } }
82
+ let(:response) { Faraday::Response.new(status: 400, response_headers: headers, response_body: body) }
83
+
84
+ it { expect(subject.wrapped_exception).to eq(exception) }
85
+ it { expect(subject.response).to eq(response) }
86
+ it { expect(subject.message).to eq(exception.message) }
87
+ it { expect(subject.backtrace).to eq(exception.backtrace) }
88
+ it { expect(subject.response_status).to eq(400) }
89
+ it { expect(subject.response_headers).to eq(headers) }
90
+ it { expect(subject.response_body).to eq(body) }
91
+ end
92
+
93
+ context 'with hash missing status key' do
94
+ let(:exception) { { body: 'error body' } }
95
+
96
+ it { expect(subject.wrapped_exception).to be_nil }
97
+ it { expect(subject.response).to eq(exception) }
98
+ it { expect(subject.message).to eq('the server responded with status - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
99
+ end
100
+
101
+ context 'with hash with status but missing request data' do
102
+ let(:exception) { { status: 404, body: 'not found' } } # missing request key
103
+
104
+ it { expect(subject.wrapped_exception).to be_nil }
105
+ it { expect(subject.response).to eq(exception) }
106
+ it { expect(subject.message).to eq('the server responded with status 404 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
107
+ end
108
+
109
+ context 'with hash with status and request but missing method in request' do
110
+ let(:exception) { { status: 404, body: 'not found', request: { url: 'http://example.com/test' } } } # missing method
111
+
112
+ it { expect(subject.wrapped_exception).to be_nil }
113
+ it { expect(subject.response).to eq(exception) }
114
+ it { expect(subject.message).to eq('the server responded with status 404 for http://example.com/test') }
115
+ end
116
+
117
+ context 'with hash with status and request but missing url in request' do
118
+ let(:exception) { { status: 404, body: 'not found', request: { method: :get } } } # missing url
119
+
120
+ it { expect(subject.wrapped_exception).to be_nil }
121
+ it { expect(subject.response).to eq(exception) }
122
+ it { expect(subject.message).to eq('the server responded with status 404 for GET ') }
123
+ end
124
+
125
+ context 'with properly formed Faraday::Env' do
126
+ # This represents the normal case - a well-formed Faraday::Env object
127
+ # with all the standard properties populated as they would be during
128
+ # a typical HTTP request/response cycle
129
+ let(:exception) { Faraday::Env.new }
130
+
131
+ before do
132
+ exception.status = 500
133
+ exception.method = :post
134
+ exception.url = URI('https://api.example.com/users')
135
+ exception.request = Faraday::RequestOptions.new
136
+ exception.response_headers = { 'content-type' => 'application/json' }
137
+ exception.response_body = '{"error": "Internal server error"}'
138
+ exception.request_headers = { 'authorization' => 'Bearer token123' }
139
+ exception.request_body = '{"name": "John"}'
140
+ end
141
+
142
+ it { expect(subject.wrapped_exception).to be_nil }
143
+ it { expect(subject.response).to eq(exception) }
144
+ it { expect(subject.message).to eq('the server responded with status 500 for POST https://api.example.com/users') }
145
+ end
146
+
147
+ context 'with Faraday::Env missing status key' do
148
+ let(:exception) { Faraday::Env.new }
149
+
150
+ before do
151
+ exception[:body] = 'error body'
152
+ # Intentionally not setting status
153
+ end
154
+
155
+ it { expect(subject.wrapped_exception).to be_nil }
156
+ it { expect(subject.response).to eq(exception) }
157
+ it { expect(subject.message).to eq('the server responded with status for ') }
158
+ end
159
+
160
+ context 'with Faraday::Env with direct method and url properties' do
161
+ let(:exception) { Faraday::Env.new }
162
+
163
+ before do
164
+ exception.status = 404
165
+ exception.method = :get
166
+ exception.url = URI('http://example.com/test')
167
+ exception[:body] = 'not found'
168
+ end
169
+
170
+ it { expect(subject.wrapped_exception).to be_nil }
171
+ it { expect(subject.response).to eq(exception) }
172
+ it { expect(subject.message).to eq('the server responded with status 404 for GET http://example.com/test') }
58
173
  end
59
174
  end
60
175
  end
@@ -67,4 +67,147 @@ RSpec.describe Faraday::Middleware do
67
67
  end
68
68
  end
69
69
  end
70
+
71
+ describe '::default_options' do
72
+ let(:subclass_no_options) { FaradayMiddlewareSubclasses::SubclassNoOptions }
73
+ let(:subclass_one_option) { FaradayMiddlewareSubclasses::SubclassOneOption }
74
+ let(:subclass_two_options) { FaradayMiddlewareSubclasses::SubclassTwoOptions }
75
+
76
+ def build_conn(resp_middleware)
77
+ Faraday.new do |c|
78
+ c.adapter :test do |stub|
79
+ stub.get('/success') { [200, {}, 'ok'] }
80
+ end
81
+ c.response resp_middleware
82
+ end
83
+ end
84
+
85
+ RSpec.shared_context 'reset @default_options' do
86
+ before(:each) do
87
+ FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
88
+ FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
89
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
90
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
91
+ end
92
+ end
93
+
94
+ after(:all) do
95
+ FaradayMiddlewareSubclasses::SubclassNoOptions.instance_variable_set(:@default_options, nil)
96
+ FaradayMiddlewareSubclasses::SubclassOneOption.instance_variable_set(:@default_options, nil)
97
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.instance_variable_set(:@default_options, nil)
98
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
99
+ end
100
+
101
+ context 'with subclass DEFAULT_OPTIONS defined' do
102
+ include_context 'reset @default_options'
103
+
104
+ context 'and without application options configured' do
105
+ let(:resp1) { build_conn(:one_option).get('/success') }
106
+
107
+ it 'has only subclass defaults' do
108
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
109
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
110
+ expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
111
+ expect(subclass_two_options.default_options).to eq(subclass_two_options::DEFAULT_OPTIONS)
112
+ end
113
+
114
+ it { expect(resp1.body).to eq('ok') }
115
+ end
116
+
117
+ context "and with one application's options changed" do
118
+ let(:resp2) { build_conn(:two_options).get('/success') }
119
+
120
+ before(:each) do
121
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
122
+ end
123
+
124
+ it 'only updates default options of target subclass' do
125
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
126
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
127
+ expect(subclass_one_option.default_options).to eq(subclass_one_option::DEFAULT_OPTIONS)
128
+ expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
129
+ end
130
+
131
+ it { expect(resp2.body).to eq('ok') }
132
+ end
133
+
134
+ context "and with two applications' options changed" do
135
+ let(:resp1) { build_conn(:one_option).get('/success') }
136
+ let(:resp2) { build_conn(:two_options).get('/success') }
137
+
138
+ before(:each) do
139
+ FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
140
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
141
+ end
142
+
143
+ it 'updates subclasses and parent independent of each other' do
144
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
145
+ expect(subclass_no_options.default_options).to eq(subclass_no_options::DEFAULT_OPTIONS)
146
+ expect(subclass_one_option.default_options).to eq({ some_other_option: true })
147
+ expect(subclass_two_options.default_options).to eq({ some_option: false, some_other_option: false })
148
+ end
149
+
150
+ it { expect(resp1.body).to eq('ok') }
151
+ it { expect(resp2.body).to eq('ok') }
152
+ end
153
+ end
154
+
155
+ context 'with FARADAY::MIDDLEWARE DEFAULT_OPTIONS and with Subclass DEFAULT_OPTIONS' do
156
+ before(:each) do
157
+ stub_const('Faraday::Middleware::DEFAULT_OPTIONS', { its_magic: false })
158
+ end
159
+
160
+ # Must stub Faraday::Middleware::DEFAULT_OPTIONS before resetting default options
161
+ include_context 'reset @default_options'
162
+
163
+ context 'and without application options configured' do
164
+ let(:resp1) { build_conn(:one_option).get('/success') }
165
+
166
+ it 'has only subclass defaults' do
167
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
168
+ expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
169
+ expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: false })
170
+ expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: true, some_other_option: false })
171
+ end
172
+
173
+ it { expect(resp1.body).to eq('ok') }
174
+ end
175
+
176
+ context "and with two applications' options changed" do
177
+ let(:resp1) { build_conn(:one_option).get('/success') }
178
+ let(:resp2) { build_conn(:two_options).get('/success') }
179
+
180
+ before(:each) do
181
+ FaradayMiddlewareSubclasses::SubclassOneOption.default_options = { some_other_option: true }
182
+ FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options = { some_option: false }
183
+ end
184
+
185
+ it 'updates subclasses and parent independent of each other' do
186
+ expect(Faraday::Middleware.default_options).to eq(Faraday::Middleware::DEFAULT_OPTIONS)
187
+ expect(FaradayMiddlewareSubclasses::SubclassNoOptions.default_options).to eq({ its_magic: false })
188
+ expect(FaradayMiddlewareSubclasses::SubclassOneOption.default_options).to eq({ its_magic: false, some_other_option: true })
189
+ expect(FaradayMiddlewareSubclasses::SubclassTwoOptions.default_options).to eq({ its_magic: false, some_option: false, some_other_option: false })
190
+ end
191
+
192
+ it { expect(resp1.body).to eq('ok') }
193
+ it { expect(resp2.body).to eq('ok') }
194
+ end
195
+ end
196
+
197
+ describe 'default_options input validation' do
198
+ include_context 'reset @default_options'
199
+
200
+ it 'raises error if Faraday::Middleware option does not exist' do
201
+ expect { Faraday::Middleware.default_options = { something_special: true } }.to raise_error(Faraday::InitializationError) do |e|
202
+ expect(e.message).to eq('Invalid options provided. Keys not found in Faraday::Middleware::DEFAULT_OPTIONS: something_special')
203
+ end
204
+ end
205
+
206
+ it 'raises error if subclass option does not exist' do
207
+ expect { subclass_one_option.default_options = { this_is_a_typo: true } }.to raise_error(Faraday::InitializationError) do |e|
208
+ expect(e.message).to eq('Invalid options provided. Keys not found in FaradayMiddlewareSubclasses::SubclassOneOption::DEFAULT_OPTIONS: this_is_a_typo')
209
+ end
210
+ end
211
+ end
212
+ end
70
213
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe Faraday::Options do
4
4
  SubOptions = Class.new(Faraday::Options.new(:sub_a, :sub_b))
5
- class ParentOptions < Faraday::Options.new(:a, :b, :c)
5
+ ParentOptions = Faraday::Options.new(:a, :b, :c) do
6
6
  options c: SubOptions
7
7
  end
8
8
 
@@ -27,11 +27,46 @@ RSpec.describe Faraday::ProxyOptions do
27
27
  expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
28
28
  end
29
29
 
30
+ it 'works with hash' do
31
+ hash = { user: 'user', password: 'pass', uri: 'http://@example.org' }
32
+ options = Faraday::ProxyOptions.from(hash)
33
+ expect(options.user).to eq('user')
34
+ expect(options.password).to eq('pass')
35
+ expect(options.uri).to be_a_kind_of(URI)
36
+ expect(options.path).to eq('')
37
+ expect(options.port).to eq(80)
38
+ expect(options.host).to eq('example.org')
39
+ expect(options.scheme).to eq('http')
40
+ expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
41
+ end
42
+
43
+ it 'works with option' do
44
+ opt_arg = { user: 'user', password: 'pass', uri: 'http://@example.org' }
45
+ option = Faraday::ConnectionOptions.from(proxy: opt_arg)
46
+ options = Faraday::ProxyOptions.from(option.proxy)
47
+ expect(options.user).to eq('user')
48
+ expect(options.password).to eq('pass')
49
+ expect(options.uri).to be_a_kind_of(URI)
50
+ expect(options.path).to eq('')
51
+ expect(options.port).to eq(80)
52
+ expect(options.host).to eq('example.org')
53
+ expect(options.scheme).to eq('http')
54
+ expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
55
+ end
56
+
30
57
  it 'works with no auth' do
31
58
  proxy = Faraday::ProxyOptions.from 'http://example.org'
32
59
  expect(proxy.user).to be_nil
33
60
  expect(proxy.password).to be_nil
34
61
  end
62
+
63
+ it 'treats empty string as nil' do
64
+ proxy = nil
65
+ proxy_string = proxy.to_s # => empty string
66
+ options = Faraday::ProxyOptions.from proxy_string
67
+ expect(options).to be_a_kind_of(Faraday::ProxyOptions)
68
+ expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
69
+ end
35
70
  end
36
71
 
37
72
  it 'allows hash access' do
@@ -62,7 +62,8 @@ RSpec.describe Faraday::NestedParamsEncoder do
62
62
  it 'encodes rack compat' do
63
63
  params = { a: [{ one: '1', two: '2' }, '3', ''] }
64
64
  result = Faraday::Utils.unescape(Faraday::NestedParamsEncoder.encode(params)).split('&')
65
- expected = Rack::Utils.build_nested_query(params).split('&')
65
+ escaped = Rack::Utils.build_nested_query(params)
66
+ expected = Rack::Utils.unescape(escaped).split('&')
66
67
  expect(result).to match_array(expected)
67
68
  end
68
69