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
         |