mirror of
https://github.com/lostisland/faraday.git
synced 2025-08-12 00:03:20 -04:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
41c990ee1f | ||
|
435888d46c | ||
|
82086930f9 | ||
|
eeec3677d7 | ||
|
92c038bcbf | ||
|
7a382b5abb | ||
|
16506ee918 | ||
|
d72db285e9 | ||
|
e7bbb4e750 | ||
|
2b1f33122d | ||
|
71dcb9cbc5 | ||
|
6e5403613a | ||
|
eba6cd906a | ||
|
84eb2538e0 | ||
|
824423ccbb | ||
|
4a838a58ce | ||
|
47b3979850 | ||
|
ef0040fe4c | ||
|
0ee48490b0 | ||
|
3f127bb10f | ||
|
554930c6c5 | ||
|
c65d3be5fb | ||
|
aa17e5b3df | ||
|
537f5d7f2d | ||
|
4a8a504fe2 | ||
|
1d7c8ad2fc | ||
|
80407137f1 | ||
|
3f0acb63b1 | ||
|
d0efa09282 | ||
|
8cd1bf1190 | ||
|
2a5702a616 | ||
|
d881f163c0 | ||
|
18ba248d14 | ||
|
a84e6b4dba | ||
|
8f32d7d59d |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -2,9 +2,8 @@ name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
push:
|
||||
branches: [main, 0.1x]
|
||||
branches: [main, 0.1x, 1.x]
|
||||
|
||||
env:
|
||||
GIT_COMMIT_SHA: ${{ github.sha }}
|
||||
@ -16,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby 2.7
|
||||
uses: ruby/setup-ruby@v1
|
||||
@ -25,7 +24,7 @@ jobs:
|
||||
|
||||
- name: Rubocop
|
||||
run: |
|
||||
gem install bundler
|
||||
gem install bundler -v 2.4.22
|
||||
bundle config set without 'development test'
|
||||
bundle config set with 'lint'
|
||||
bundle install
|
||||
@ -43,21 +42,22 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby: ['2.4', '2.5', '2.6', '2.7', '3.0']
|
||||
ruby: ['3.0', '3.1', '3.2']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libcurl4-openssl-dev
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
gem install bundler -v '<2'
|
||||
gem install bundler -v 2.4.22
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
- name: Test
|
||||
|
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby 2.6
|
||||
uses: actions/setup-ruby@v1
|
||||
- name: Set up Ruby 2.7
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.6.x
|
||||
ruby-version: 2.7
|
||||
|
||||
- name: Publish to RubyGems
|
||||
run: |
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,6 +8,7 @@ tmp
|
||||
.rvmrc
|
||||
.ruby-version
|
||||
.yardoc
|
||||
.DS_Store
|
||||
|
||||
## BUNDLER
|
||||
*.gem
|
||||
@ -17,6 +18,7 @@ vendor/bundle
|
||||
|
||||
## PROJECT::SPECIFIC
|
||||
.rbx
|
||||
node_modules
|
||||
|
||||
## IDEs
|
||||
.idea/
|
||||
|
@ -12,6 +12,8 @@ AllCops:
|
||||
|
||||
Inclusivity/Race:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'lib/faraday/adapter/typhoeus.rb'
|
||||
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
@ -59,4 +61,3 @@ Style/IfUnlessModifier:
|
||||
|
||||
Style/SlicingWithRange: # (0.83)
|
||||
Enabled: true
|
||||
|
||||
|
@ -14,7 +14,7 @@ Metrics/AbcSize:
|
||||
# Offense count: 5
|
||||
# Configuration parameters: CountComments, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Max: 256
|
||||
Max: 260
|
||||
|
||||
# Offense count: 15
|
||||
# Configuration parameters: IgnoredMethods.
|
||||
|
2
Gemfile
2
Gemfile
@ -7,6 +7,7 @@ ruby RUBY_VERSION
|
||||
gem 'jruby-openssl', '~> 0.10.7', platforms: :jruby
|
||||
|
||||
group :development, :test do
|
||||
gem 'irb'
|
||||
gem 'pry'
|
||||
gem 'rake'
|
||||
end
|
||||
@ -29,6 +30,7 @@ group :test, :development do
|
||||
options = (RUBY_VERSION.start_with?('3') ? { github: 'grosser/net-http-persistent', branch: 'grosser/spec' } : {})
|
||||
gem 'net-http-persistent', '>= 3.0', **options
|
||||
gem 'patron', '>= 0.4.2', platforms: :ruby
|
||||
gem 'rack', '< 3'
|
||||
gem 'rack-test', '>= 0.6', require: 'rack/test'
|
||||
gem 'rspec', '~> 3.7'
|
||||
gem 'rspec_junit_formatter', '~> 0.4'
|
||||
|
@ -27,6 +27,8 @@ base64 representation.
|
||||
* [`Multipart`][multipart] converts a `Faraday::Request#body` hash of key/value pairs into a
|
||||
multipart form request.
|
||||
* [`UrlEncoded`][url_encoded] converts a `Faraday::Request#body` hash of key/value pairs into a url-encoded request body.
|
||||
* [`Json Request`][json-request] converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
|
||||
* [`Json Response`][json-response] parses response body into a hash of key/value pairs.
|
||||
* [`Retry`][retry] automatically retries requests that fail due to intermittent client
|
||||
or server errors (such as network hiccups).
|
||||
* [`Instrumentation`][instrumentation] allows to instrument requests using different tools.
|
||||
@ -44,7 +46,9 @@ before returning it.
|
||||
[authentication]: ./authentication
|
||||
[multipart]: ./multipart
|
||||
[url_encoded]: ./url-encoded
|
||||
[json-request]: ./json-request
|
||||
[retry]: ./retry
|
||||
[instrumentation]: ./instrumentation
|
||||
[json-response]: ./json-response
|
||||
[logger]: ./logger
|
||||
[raise_error]: ./raise-error
|
||||
|
@ -23,6 +23,16 @@ Faraday.new(...) do |conn|
|
||||
end
|
||||
```
|
||||
|
||||
### With a proc
|
||||
|
||||
You can also provide a proc, which will be evaluated on each request:
|
||||
|
||||
```ruby
|
||||
Faraday.new(...) do |conn|
|
||||
conn.request :authorization, 'Bearer', -> { MyAuthStorage.get_auth_token }
|
||||
end
|
||||
```
|
||||
|
||||
### Basic Authentication
|
||||
|
||||
`BasicAuthentication` adds a 'Basic' type Authorization header to a Faraday request.
|
||||
|
@ -5,8 +5,8 @@ permalink: /middleware/instrumentation
|
||||
hide: true
|
||||
prev_name: Retry Middleware
|
||||
prev_link: ./retry
|
||||
next_name: Logger Middleware
|
||||
next_link: ./logger
|
||||
next_name: JSON Response Middleware
|
||||
next_link: ./json-response
|
||||
top_name: Back to Middleware
|
||||
top_link: ./list
|
||||
---
|
||||
|
31
docs/middleware/request/json.md
Normal file
31
docs/middleware/request/json.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
layout: documentation
|
||||
title: "JSON Request Middleware"
|
||||
permalink: /middleware/json-request
|
||||
hide: true
|
||||
prev_name: UrlEncoded Middleware
|
||||
prev_link: ./url-encoded
|
||||
next_name: Retry Middleware
|
||||
next_link: ./retry
|
||||
top_name: Back to Middleware
|
||||
top_link: ./list
|
||||
---
|
||||
|
||||
The `JSON` request middleware converts a `Faraday::Request#body` hash of key/value pairs into a JSON request body.
|
||||
The middleware also automatically sets the `Content-Type` header to `application/json`,
|
||||
processes only requests with matching Content-Type or those without a type and
|
||||
doesn't try to encode bodies that already are in string form.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new(...) do |f|
|
||||
f.request :json
|
||||
...
|
||||
end
|
||||
|
||||
conn.post('/', { a: 1, b: 2 })
|
||||
# POST with
|
||||
# Content-Type: application/json
|
||||
# Body: {"a":1,"b":2}
|
||||
```
|
@ -5,8 +5,8 @@ permalink: /middleware/url-encoded
|
||||
hide: true
|
||||
prev_name: Multipart Middleware
|
||||
prev_link: ./multipart
|
||||
next_name: Retry Middleware
|
||||
next_link: ./retry
|
||||
next_name: JSON Request Middleware
|
||||
next_link: ./json-request
|
||||
top_name: Back to Middleware
|
||||
top_link: ./list
|
||||
---
|
||||
|
29
docs/middleware/response/json.md
Normal file
29
docs/middleware/response/json.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
layout: documentation
|
||||
title: "JSON Response Middleware"
|
||||
permalink: /middleware/json-response
|
||||
hide: true
|
||||
prev_name: Instrumentation Middleware
|
||||
prev_link: ./instrumentation
|
||||
next_name: Logger Middleware
|
||||
next_link: ./logger
|
||||
top_name: Back to Middleware
|
||||
top_link: ./list
|
||||
---
|
||||
|
||||
The `JSON` response middleware parses response body into a hash of key/value pairs.
|
||||
The behaviour can be customized with the following options:
|
||||
* **parser_options:** options that will be sent to the JSON.parse method. Defaults to {}.
|
||||
* **content_type:** Single value or Array of response content-types that should be processed. Can be either strings or Regex. Defaults to `/\bjson$/`.
|
||||
* **preserve_raw:** If set to true, the original un-parsed response will be stored in the `response.env[:raw_body]` property. Defaults to `false`.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```ruby
|
||||
conn = Faraday.new('http://httpbingo.org') do |f|
|
||||
f.response :json, **options
|
||||
end
|
||||
|
||||
conn.get('json').body
|
||||
# => {"slideshow"=>{"author"=>"Yours Truly", "date"=>"date of publication", "slides"=>[{"title"=>"Wake up to WonderWidgets!", "type"=>"all"}, {"items"=>["Why <em>WonderWidgets</em> are great", "Who <em>buys</em> WonderWidgets"], "title"=>"Overview", "type"=>"all"}], "title"=>"Sample Slide Show"}}
|
||||
```
|
@ -3,8 +3,8 @@ layout: documentation
|
||||
title: "Logger Middleware"
|
||||
permalink: /middleware/logger
|
||||
hide: true
|
||||
prev_name: Instrumentation Middleware
|
||||
prev_link: ./instrumentation
|
||||
prev_name: JSON Response Middleware
|
||||
prev_link: ./json-response
|
||||
next_name: RaiseError Middleware
|
||||
next_link: ./raise-error
|
||||
top_name: Back to Middleware
|
||||
|
@ -81,4 +81,17 @@ RSpec.describe Client do
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
|
||||
context 'When the Faraday connection is configured with FlatParamsEncoder' do
|
||||
let(:conn) { Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) { |b| b.adapter(:test, stubs) } }
|
||||
|
||||
it 'handles the same multiple URL parameters' do
|
||||
stubs.get('/ebi?a=x&a=y&a=z') { [200, { 'Content-Type' => 'application/json' }, '{"name": "shrimp"}'] }
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# expect(client.sushi('ebi', params: { a: %w[x y] })).to eq('shrimp')
|
||||
expect(client.sushi('ebi', params: { a: %w[x y z] })).to eq('shrimp')
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -13,8 +13,8 @@ class Client
|
||||
@conn = conn
|
||||
end
|
||||
|
||||
def sushi(jname)
|
||||
res = @conn.get("/#{jname}")
|
||||
def sushi(jname, params: {})
|
||||
res = @conn.get("/#{jname}", params)
|
||||
data = JSON.parse(res.body)
|
||||
data['name']
|
||||
end
|
||||
@ -70,6 +70,45 @@ class ClientTest < Test::Unit::TestCase
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_strict_mode
|
||||
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||
stubs.get('/ebi?abc=123') do
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"name": "shrimp"}'
|
||||
]
|
||||
end
|
||||
|
||||
cli = client(stubs)
|
||||
assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123 })
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# assert_equal 'shrimp', cli.sushi('ebi', params: { abc: 123, foo: 'Kappa' })
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def test_non_default_params_encoder
|
||||
stubs = Faraday::Adapter::Test::Stubs.new(strict_mode: true)
|
||||
stubs.get('/ebi?a=x&a=y&a=z') do
|
||||
[
|
||||
200,
|
||||
{ 'Content-Type': 'application/javascript' },
|
||||
'{"name": "shrimp"}'
|
||||
]
|
||||
end
|
||||
conn = Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
|
||||
cli = Client.new(conn)
|
||||
assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y z] })
|
||||
|
||||
# uncomment to raise Stubs::NotFound
|
||||
# assert_equal 'shrimp', cli.sushi('ebi', params: { a: %w[x y] })
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
def client(stubs)
|
||||
conn = Faraday.new do |builder|
|
||||
builder.adapter :test, stubs
|
||||
|
@ -18,12 +18,13 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
|
||||
spec.add_dependency 'faraday-em_http', '~> 1.0'
|
||||
spec.add_dependency 'faraday-em_synchrony', '~> 1.0'
|
||||
spec.add_dependency 'faraday-excon', '~> 1.1'
|
||||
spec.add_dependency 'faraday-httpclient', '~> 1.0.1'
|
||||
spec.add_dependency 'faraday-httpclient', '~> 1.0'
|
||||
spec.add_dependency 'faraday-multipart', '~> 1.0'
|
||||
spec.add_dependency 'faraday-net_http', '~> 1.0'
|
||||
spec.add_dependency 'faraday-net_http_persistent', '~> 1.1'
|
||||
spec.add_dependency 'faraday-net_http_persistent', '~> 1.0'
|
||||
spec.add_dependency 'faraday-patron', '~> 1.0'
|
||||
spec.add_dependency 'faraday-rack', '~> 1.0'
|
||||
spec.add_dependency 'multipart-post', '>= 1.2', '< 3'
|
||||
spec.add_dependency 'faraday-retry', '~> 1.0'
|
||||
spec.add_dependency 'ruby2_keywords', '>= 0.0.4'
|
||||
|
||||
# Includes `examples` and `spec` to allow external adapter gems to run Faraday unit and integration tests
|
||||
|
@ -24,9 +24,15 @@ require 'faraday/adapter'
|
||||
require 'faraday/request'
|
||||
require 'faraday/response'
|
||||
require 'faraday/error'
|
||||
require 'faraday/file_part'
|
||||
require 'faraday/param_part'
|
||||
require 'faraday/request/url_encoded' # needed by multipart
|
||||
|
||||
# External Middleware gems and their aliases
|
||||
require 'faraday/multipart'
|
||||
require 'faraday/retry'
|
||||
Faraday::Request::Multipart = Faraday::Multipart::Middleware
|
||||
Faraday::Request::Retry = Faraday::Retry::Middleware
|
||||
|
||||
# External Adapters gems
|
||||
unless defined?(JRUBY_VERSION)
|
||||
require 'faraday/em_http'
|
||||
require 'faraday/em_synchrony'
|
||||
@ -51,6 +57,8 @@ require 'faraday/rack'
|
||||
# conn.get '/'
|
||||
#
|
||||
module Faraday
|
||||
CONTENT_TYPE = 'Content-Type'
|
||||
|
||||
class << self
|
||||
# The root path that Faraday is being loaded from.
|
||||
#
|
||||
|
@ -62,18 +62,20 @@ module Faraday
|
||||
@stack.empty?
|
||||
end
|
||||
|
||||
def match(request_method, host, path, headers, body)
|
||||
# @param env [Faraday::Env]
|
||||
def match(env)
|
||||
request_method = env[:method]
|
||||
return false unless @stack.key?(request_method)
|
||||
|
||||
stack = @stack[request_method]
|
||||
consumed = (@consumed[request_method] ||= [])
|
||||
|
||||
stub, meta = matches?(stack, host, path, headers, body)
|
||||
stub, meta = matches?(stack, env)
|
||||
if stub
|
||||
consumed << stack.delete(stub)
|
||||
return stub, meta
|
||||
end
|
||||
matches?(consumed, host, path, headers, body)
|
||||
matches?(consumed, env)
|
||||
end
|
||||
|
||||
def get(path, headers = {}, &block)
|
||||
@ -142,15 +144,18 @@ module Faraday
|
||||
Faraday::Utils.URI(path).host
|
||||
]
|
||||
end
|
||||
|
||||
path, query = normalized_path.respond_to?(:split) ? normalized_path.split('?') : normalized_path
|
||||
headers = Utils::Headers.new(headers)
|
||||
stub = Stub.new(host, normalized_path, headers, body, @strict_mode, block)
|
||||
|
||||
stub = Stub.new(host, path, query, headers, body, @strict_mode, block)
|
||||
(@stack[request_method] ||= []) << stub
|
||||
end
|
||||
|
||||
def matches?(stack, host, path, headers, body)
|
||||
# @param stack [Hash]
|
||||
# @param env [Faraday::Env]
|
||||
def matches?(stack, env)
|
||||
stack.each do |stub|
|
||||
match_result, meta = stub.matches?(host, path, headers, body)
|
||||
match_result, meta = stub.matches?(env)
|
||||
return stub, meta if match_result
|
||||
end
|
||||
nil
|
||||
@ -158,35 +163,20 @@ module Faraday
|
||||
end
|
||||
|
||||
# Stub request
|
||||
# rubocop:disable Style/StructInheritance
|
||||
class Stub < Struct.new(:host, :path, :params, :headers, :body, :strict_mode, :block)
|
||||
# rubocop:enable Style/StructInheritance
|
||||
def initialize(host, full, headers, body, strict_mode, block) # rubocop:disable Metrics/ParameterLists
|
||||
path, query = full.respond_to?(:split) ? full.split('?') : full
|
||||
params =
|
||||
if query
|
||||
Faraday::Utils.parse_nested_query(query)
|
||||
else
|
||||
{}
|
||||
end
|
||||
class Stub < Struct.new(:host, :path, :query, :headers, :body, :strict_mode, :block) # rubocop:disable Style/StructInheritance
|
||||
# @param env [Faraday::Env]
|
||||
def matches?(env)
|
||||
request_host = env[:url].host
|
||||
request_path = Faraday::Utils.normalize_path(env[:url].path)
|
||||
request_headers = env.request_headers
|
||||
request_body = env[:body]
|
||||
|
||||
super(host, path, params, headers, body, strict_mode, block)
|
||||
end
|
||||
|
||||
def matches?(request_host, request_uri, request_headers, request_body)
|
||||
request_path, request_query = request_uri.split('?')
|
||||
request_params =
|
||||
if request_query
|
||||
Faraday::Utils.parse_nested_query(request_query)
|
||||
else
|
||||
{}
|
||||
end
|
||||
# meta is a hash used as carrier
|
||||
# that will be yielded to consumer block
|
||||
meta = {}
|
||||
[(host.nil? || host == request_host) &&
|
||||
path_match?(request_path, meta) &&
|
||||
params_match?(request_params) &&
|
||||
params_match?(env) &&
|
||||
(body.to_s.size.zero? || request_body == body) &&
|
||||
headers_match?(request_headers), meta]
|
||||
end
|
||||
@ -199,7 +189,11 @@ module Faraday
|
||||
end
|
||||
end
|
||||
|
||||
def params_match?(request_params)
|
||||
# @param env [Faraday::Env]
|
||||
def params_match?(env)
|
||||
request_params = env[:params]
|
||||
params = env.params_encoder.decode(query) || {}
|
||||
|
||||
if strict_mode
|
||||
return Set.new(params) == Set.new(request_params)
|
||||
end
|
||||
@ -239,26 +233,19 @@ module Faraday
|
||||
yield(stubs)
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
def call(env)
|
||||
super
|
||||
host = env[:url].host
|
||||
normalized_path = Faraday::Utils.normalize_path(env[:url])
|
||||
params_encoder = env.request.params_encoder ||
|
||||
Faraday::Utils.default_params_encoder
|
||||
|
||||
stub, meta = stubs.match(env[:method], host, normalized_path,
|
||||
env.request_headers, env[:body])
|
||||
env.request.params_encoder ||= Faraday::Utils.default_params_encoder
|
||||
env[:params] = env.params_encoder.decode(env[:url].query) || {}
|
||||
stub, meta = stubs.match(env)
|
||||
|
||||
unless stub
|
||||
raise Stubs::NotFound, "no stubbed request for #{env[:method]} "\
|
||||
"#{normalized_path} #{env[:body]}"
|
||||
"#{env[:url]} #{env[:body]}"
|
||||
end
|
||||
|
||||
env[:params] = if (query = env[:url].query)
|
||||
params_encoder.decode(query)
|
||||
else
|
||||
{}
|
||||
end
|
||||
block_arity = stub.block.arity
|
||||
status, headers, body =
|
||||
if block_arity >= 0
|
||||
|
@ -67,13 +67,14 @@ module Faraday
|
||||
class Request
|
||||
extend AutoloadHelper
|
||||
autoload_all 'faraday/request',
|
||||
UrlEncoded: 'url_encoded',
|
||||
Multipart: 'multipart',
|
||||
Retry: 'retry',
|
||||
Authorization: 'authorization',
|
||||
BasicAuthentication: 'basic_authentication',
|
||||
Instrumentation: 'instrumentation',
|
||||
Json: 'json',
|
||||
Multipart: 'multipart',
|
||||
Retry: 'retry',
|
||||
TokenAuthentication: 'token_authentication',
|
||||
Instrumentation: 'instrumentation'
|
||||
UrlEncoded: 'url_encoded'
|
||||
end
|
||||
|
||||
# Response represents the returned value of a sent Faraday request.
|
||||
@ -81,7 +82,8 @@ module Faraday
|
||||
class Response
|
||||
extend AutoloadHelper
|
||||
autoload_all 'faraday/response',
|
||||
RaiseError: 'raise_error',
|
||||
Logger: 'logger'
|
||||
Json: 'json',
|
||||
Logger: 'logger',
|
||||
RaiseError: 'raise_error'
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'faraday/deprecate'
|
||||
|
||||
module Faraday
|
||||
# Connection objects manage the default properties and the middleware
|
||||
# stack for fulfilling an HTTP request.
|
||||
@ -297,14 +299,12 @@ module Faraday
|
||||
#
|
||||
# @return [void]
|
||||
def basic_auth(login, pass)
|
||||
warn <<~TEXT
|
||||
WARNING: `Faraday::Connection#basic_auth` is deprecated; it will be removed in version 2.0.
|
||||
While initializing your connection, use `#request(:basic_auth, ...)` instead.
|
||||
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
|
||||
TEXT
|
||||
set_authorization_header(:basic_auth, login, pass)
|
||||
end
|
||||
|
||||
extend Faraday::Deprecate
|
||||
deprecate :basic_auth, '#request(:basic_auth, ...)', '2.0'
|
||||
|
||||
# Sets up the Authorization header with the given token.
|
||||
#
|
||||
# @param token [String]
|
||||
@ -319,14 +319,14 @@ module Faraday
|
||||
#
|
||||
# @return [void]
|
||||
def token_auth(token, options = nil)
|
||||
warn <<~TEXT
|
||||
WARNING: `Faraday::Connection#token_auth` is deprecated; it will be removed in version 2.0.
|
||||
While initializing your connection, use `#request(:token_auth, ...)` instead.
|
||||
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
|
||||
TEXT
|
||||
set_authorization_header(:token_auth, token, options)
|
||||
end
|
||||
|
||||
deprecate :token_auth,
|
||||
'#request(:token_auth, ...)',
|
||||
'2.0',
|
||||
'See https://lostisland.github.io/faraday/middleware/authentication for more usage info.'
|
||||
|
||||
# Sets up a custom Authorization header.
|
||||
#
|
||||
# @param type [String] authorization type
|
||||
@ -346,14 +346,14 @@ module Faraday
|
||||
#
|
||||
# @return [void]
|
||||
def authorization(type, token)
|
||||
warn <<~TEXT
|
||||
WARNING: `Faraday::Connection#authorization` is deprecated; it will be removed in version 2.0.
|
||||
While initializing your connection, use `#request(:authorization, ...)` instead.
|
||||
See https://lostisland.github.io/faraday/middleware/authentication for more usage info.
|
||||
TEXT
|
||||
set_authorization_header(:authorization, type, token)
|
||||
end
|
||||
|
||||
deprecate :authorization,
|
||||
'#request(:authorization, ...)',
|
||||
'2.0',
|
||||
'See https://lostisland.github.io/faraday/middleware/authentication for more usage info.'
|
||||
|
||||
# Check if the adapter is parallel-capable.
|
||||
#
|
||||
# @yield if the adapter isn't parallel-capable, or if no adapter is set yet.
|
||||
@ -433,13 +433,18 @@ module Faraday
|
||||
uri.query = nil
|
||||
|
||||
with_uri_credentials(uri) do |user, password|
|
||||
basic_auth user, password
|
||||
set_basic_auth(user, password)
|
||||
uri.user = uri.password = nil
|
||||
end
|
||||
|
||||
@proxy = proxy_from_env(url) unless @manual_proxy
|
||||
end
|
||||
|
||||
def set_basic_auth(user, password)
|
||||
header = Faraday::Request::BasicAuthentication.header(user, password)
|
||||
headers[Faraday::Request::Authorization::KEY] = header
|
||||
end
|
||||
|
||||
# Sets the path prefix and ensures that it always has a leading
|
||||
# slash.
|
||||
#
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'ruby2_keywords'
|
||||
|
||||
module Faraday
|
||||
# DependencyLoader helps Faraday adapters and middleware load dependencies.
|
||||
module DependencyLoader
|
||||
@ -13,7 +15,7 @@ module Faraday
|
||||
self.load_error = e
|
||||
end
|
||||
|
||||
def new(*)
|
||||
ruby2_keywords def new(*)
|
||||
unless loaded?
|
||||
raise "missing dependency for #{self}: #{load_error.message}"
|
||||
end
|
||||
|
110
lib/faraday/deprecate.rb
Normal file
110
lib/faraday/deprecate.rb
Normal file
@ -0,0 +1,110 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# @param new_klass [Class] new Klass to use
|
||||
#
|
||||
# @return [Class] A modified version of new_klass that warns on
|
||||
# usage about deprecation.
|
||||
# @see Faraday::Deprecate
|
||||
module DeprecatedClass
|
||||
def self.proxy_class(origclass, ver = '1.0')
|
||||
proxy = Class.new(origclass) do
|
||||
const_set('ORIG_CLASS', origclass)
|
||||
|
||||
class << self
|
||||
extend Faraday::Deprecate
|
||||
|
||||
def ===(other)
|
||||
(superclass == const_get('ORIG_CLASS') && other.is_a?(superclass)) || super
|
||||
end
|
||||
end
|
||||
end
|
||||
proxy.singleton_class.send(:deprecate, :new, "#{origclass}.new", ver)
|
||||
proxy.singleton_class.send(:deprecate, :inherited, origclass.name, ver)
|
||||
proxy
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecation using semver instead of date, based on Gem::Deprecate
|
||||
# Provides a single method +deprecate+ to be used to declare when
|
||||
# something is going away.
|
||||
#
|
||||
# class Legacy
|
||||
# def self.klass_method
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# def instance_method
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# extend Faraday::Deprecate
|
||||
# deprecate :instance_method, "X.z", '1.0'
|
||||
#
|
||||
# class << self
|
||||
# extend Faraday::Deprecate
|
||||
# deprecate :klass_method, :none, '1.0'
|
||||
# end
|
||||
# end
|
||||
module Deprecate
|
||||
def self.skip # :nodoc:
|
||||
@skip ||= begin
|
||||
case ENV['FARADAY_DEPRECATE'].to_s.downcase
|
||||
when '1', 'warn' then :warn
|
||||
else :skip
|
||||
end
|
||||
end
|
||||
@skip == :skip
|
||||
end
|
||||
|
||||
def self.skip=(value) # :nodoc:
|
||||
@skip = value ? :skip : :warn
|
||||
end
|
||||
|
||||
# Temporarily turn off warnings. Intended for tests only.
|
||||
def skip_during
|
||||
original = Faraday::Deprecate.skip
|
||||
Faraday::Deprecate.skip = true
|
||||
yield
|
||||
ensure
|
||||
Faraday::Deprecate.skip = original
|
||||
end
|
||||
|
||||
# Simple deprecation method that deprecates +name+ by wrapping it up
|
||||
# in a dummy method. It warns on each call to the dummy method
|
||||
# telling the user of +repl+ (unless +repl+ is :none) and the
|
||||
# semver that it is planned to go away.
|
||||
# @param name [Symbol] the method symbol to deprecate
|
||||
# @param repl [#to_s, :none] the replacement to use, when `:none` it will
|
||||
# alert the user that no replacement is present.
|
||||
# @param ver [String] the semver the method will be removed.
|
||||
def deprecate(name, repl, ver, custom_message = nil)
|
||||
class_eval do
|
||||
gem_ver = Gem::Version.new(ver)
|
||||
old = "_deprecated_#{name}"
|
||||
alias_method old, name
|
||||
define_method name do |*args, &block|
|
||||
mod = is_a? Module
|
||||
target = mod ? "#{self}." : "#{self.class}#"
|
||||
target_message = if name == :inherited
|
||||
"Inheriting #{self}"
|
||||
else
|
||||
"#{target}#{name}"
|
||||
end
|
||||
|
||||
msg = [
|
||||
"NOTE: #{target_message} is deprecated",
|
||||
repl == :none ? ' with no replacement' : "; use #{repl} instead. ",
|
||||
"It will be removed in or after version #{gem_ver} ",
|
||||
custom_message,
|
||||
"\n#{target}#{name} called from #{Gem.location_of_caller.join(':')}"
|
||||
]
|
||||
warn "#{msg.join}." unless Faraday::Deprecate.skip
|
||||
send old, *args, &block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module_function :deprecate, :skip_during
|
||||
end
|
||||
end
|
@ -143,10 +143,4 @@ module Faraday
|
||||
# Raised by FaradayMiddleware::ResponseMiddleware
|
||||
class ParsingError < Error
|
||||
end
|
||||
|
||||
# Exception used to control the Retry middleware.
|
||||
#
|
||||
# @see Faraday::Request::Retry
|
||||
class RetriableResponse < Error
|
||||
end
|
||||
end
|
||||
|
@ -1,128 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
# multipart-post gem
|
||||
require 'composite_io'
|
||||
require 'parts'
|
||||
|
||||
module Faraday
|
||||
# Multipart value used to POST a binary data from a file or
|
||||
#
|
||||
# @example
|
||||
# payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
|
||||
# http.post("/upload", payload)
|
||||
#
|
||||
|
||||
# @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
|
||||
#
|
||||
# @param filename_or_io [String, IO] Either a String filename to a local
|
||||
# file or an open IO object.
|
||||
# @param content_type [String] String content type of the file data.
|
||||
# @param filename [String] Optional String filename, usually to add context
|
||||
# to a given IO object.
|
||||
# @param opts [Hash] Optional Hash of String key/value pairs to describethis
|
||||
# this uploaded file. Expected Header keys include:
|
||||
# * Content-Transfer-Encoding - Defaults to "binary"
|
||||
# * Content-Disposition - Defaults to "form-data"
|
||||
# * Content-Type - Defaults to the content_type argument.
|
||||
# * Content-ID - Optional.
|
||||
#
|
||||
# @return [Faraday::FilePart]
|
||||
#
|
||||
# @!attribute [r] content_type
|
||||
# The uploaded binary data's content type.
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @!attribute [r] original_filename
|
||||
# The base filename, taken either from the filename_or_io or filename
|
||||
# arguments in #initialize.
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
# @!attribute [r] opts
|
||||
# Extra String key/value pairs to make up the header for this uploaded file.
|
||||
#
|
||||
# @return [Hash]
|
||||
#
|
||||
# @!attribute [r] io
|
||||
# The open IO object for the uploaded file.
|
||||
#
|
||||
# @return [IO]
|
||||
FilePart = ::UploadIO
|
||||
|
||||
# Multipart value used to POST a file.
|
||||
#
|
||||
# @deprecated Use FilePart instead of this class. It behaves identically, with
|
||||
# a matching name to ParamPart.
|
||||
UploadIO = ::UploadIO
|
||||
|
||||
Parts = ::Parts
|
||||
|
||||
# Similar to, but not compatible with CompositeReadIO provided by the
|
||||
# multipart-post gem.
|
||||
# https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
|
||||
class CompositeReadIO
|
||||
def initialize(*parts)
|
||||
@parts = parts.flatten
|
||||
@ios = @parts.map(&:to_io)
|
||||
@index = 0
|
||||
end
|
||||
|
||||
# @return [Integer] sum of the lengths of all the parts
|
||||
def length
|
||||
@parts.inject(0) { |sum, part| sum + part.length }
|
||||
end
|
||||
|
||||
# Rewind each of the IOs and reset the index to 0.
|
||||
#
|
||||
# @return [void]
|
||||
def rewind
|
||||
@ios.each(&:rewind)
|
||||
@index = 0
|
||||
end
|
||||
|
||||
# Read from IOs in order until `length` bytes have been received.
|
||||
#
|
||||
# @param length [Integer, nil]
|
||||
# @param outbuf [String, nil]
|
||||
def read(length = nil, outbuf = nil)
|
||||
got_result = false
|
||||
outbuf = outbuf ? (+outbuf).replace('') : +''
|
||||
|
||||
while (io = current_io)
|
||||
if (result = io.read(length))
|
||||
got_result ||= !result.nil?
|
||||
result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
|
||||
outbuf << result
|
||||
length -= result.length if length
|
||||
break if length&.zero?
|
||||
end
|
||||
advance_io
|
||||
end
|
||||
!got_result && length ? nil : outbuf
|
||||
end
|
||||
|
||||
# Close each of the IOs.
|
||||
#
|
||||
# @return [void]
|
||||
def close
|
||||
@ios.each(&:close)
|
||||
end
|
||||
|
||||
def ensure_open_and_readable
|
||||
# Rubinius compatibility
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_io
|
||||
@ios[@index]
|
||||
end
|
||||
|
||||
def advance_io
|
||||
@index += 1
|
||||
end
|
||||
end
|
||||
end
|
@ -1,53 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
# Multipart value used to POST data with a content type.
|
||||
class ParamPart
|
||||
# @param value [String] Uploaded content as a String.
|
||||
# @param content_type [String] String content type of the value.
|
||||
# @param content_id [String] Optional String of this value's Content-ID.
|
||||
#
|
||||
# @return [Faraday::ParamPart]
|
||||
def initialize(value, content_type, content_id = nil)
|
||||
@value = value
|
||||
@content_type = content_type
|
||||
@content_id = content_id
|
||||
end
|
||||
|
||||
# Converts this value to a form part.
|
||||
#
|
||||
# @param boundary [String] String multipart boundary that must not exist in
|
||||
# the content exactly.
|
||||
# @param key [String] String key name for this value.
|
||||
#
|
||||
# @return [Faraday::Parts::Part]
|
||||
def to_part(boundary, key)
|
||||
Faraday::Parts::Part.new(boundary, key, value, headers)
|
||||
end
|
||||
|
||||
# Returns a Hash of String key/value pairs.
|
||||
#
|
||||
# @return [Hash]
|
||||
def headers
|
||||
{
|
||||
'Content-Type' => content_type,
|
||||
'Content-ID' => content_id
|
||||
}
|
||||
end
|
||||
|
||||
# The content to upload.
|
||||
#
|
||||
# @return [String]
|
||||
attr_reader :value
|
||||
|
||||
# The value's content type.
|
||||
#
|
||||
# @return [String]
|
||||
attr_reader :content_type
|
||||
|
||||
# The value's content ID, if given.
|
||||
#
|
||||
# @return [String, nil]
|
||||
attr_reader :content_id
|
||||
end
|
||||
end
|
@ -35,8 +35,6 @@ module Faraday
|
||||
|
||||
register_middleware File.expand_path('request', __dir__),
|
||||
url_encoded: [:UrlEncoded, 'url_encoded'],
|
||||
multipart: [:Multipart, 'multipart'],
|
||||
retry: [:Retry, 'retry'],
|
||||
authorization: [:Authorization, 'authorization'],
|
||||
basic_auth: [
|
||||
:BasicAuthentication,
|
||||
@ -46,7 +44,8 @@ module Faraday
|
||||
:TokenAuthentication,
|
||||
'token_authentication'
|
||||
],
|
||||
instrumentation: [:Instrumentation, 'instrumentation']
|
||||
instrumentation: [:Instrumentation, 'instrumentation'],
|
||||
json: [:Json, 'json']
|
||||
|
||||
# @param request_method [String]
|
||||
# @yield [request] for block customization, if block given
|
||||
@ -59,13 +58,12 @@ module Faraday
|
||||
end
|
||||
|
||||
def method
|
||||
warn <<~TEXT
|
||||
WARNING: `Faraday::Request##{__method__}` is deprecated; use `#http_method` instead. It will be removed in or after version 2.0.
|
||||
`Faraday::Request##{__method__}` called from #{caller_locations(1..1).first}
|
||||
TEXT
|
||||
http_method
|
||||
end
|
||||
|
||||
extend Faraday::Deprecate
|
||||
deprecate :method, :http_method, '2.0'
|
||||
|
||||
# Replace params, preserving the existing hash type.
|
||||
#
|
||||
# @param hash [Hash] new params
|
||||
@ -140,11 +138,11 @@ module Faraday
|
||||
# @param serialised [Hash] the serialised object.
|
||||
def marshal_load(serialised)
|
||||
self.http_method = serialised[:http_method]
|
||||
self.body = serialised[:body]
|
||||
self.headers = serialised[:headers]
|
||||
self.path = serialised[:path]
|
||||
self.params = serialised[:params]
|
||||
self.options = serialised[:options]
|
||||
self.body = serialised[:body]
|
||||
self.headers = serialised[:headers]
|
||||
self.path = serialised[:path]
|
||||
self.params = serialised[:params]
|
||||
self.options = serialised[:options]
|
||||
end
|
||||
|
||||
# @return [Env] the Env for this Request
|
||||
|
@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'base64'
|
||||
|
||||
module Faraday
|
||||
class Request
|
||||
# Request middleware for the Authorization HTTP header
|
||||
@ -13,7 +15,8 @@ module Faraday
|
||||
# @return [String] a header value
|
||||
def self.header(type, token)
|
||||
case token
|
||||
when String, Symbol
|
||||
when String, Symbol, Proc
|
||||
token = token.call if token.is_a?(Proc)
|
||||
"#{type} #{token}"
|
||||
when Hash
|
||||
build_hash(type.to_s, token)
|
||||
@ -32,6 +35,7 @@ module Faraday
|
||||
comma = ', '
|
||||
values = []
|
||||
hash.each do |key, value|
|
||||
value = value.call if value.is_a?(Proc)
|
||||
values << "#{key}=#{value.to_s.inspect}"
|
||||
end
|
||||
"#{type} #{values * comma}"
|
||||
@ -39,16 +43,19 @@ module Faraday
|
||||
|
||||
# @param app [#call]
|
||||
# @param type [String, Symbol] Type of Authorization
|
||||
# @param token [String, Symbol, Hash] Token value for the Authorization
|
||||
def initialize(app, type, token)
|
||||
@header_value = self.class.header(type, token)
|
||||
# @param param [String, Symbol, Hash, Proc] parameter to build the Authorization header.
|
||||
# This value can be a proc, in which case it will be invoked on each request.
|
||||
def initialize(app, type, param)
|
||||
@type = type
|
||||
@param = param
|
||||
super(app)
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
def call(env)
|
||||
env.request_headers[KEY] = @header_value unless env.request_headers[KEY]
|
||||
@app.call(env)
|
||||
def on_request(env)
|
||||
return if env.request_headers[KEY]
|
||||
|
||||
env.request_headers[KEY] = self.class.header(@type, @param)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
55
lib/faraday/request/json.rb
Normal file
55
lib/faraday/request/json.rb
Normal file
@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
|
||||
module Faraday
|
||||
class Request
|
||||
# Request middleware that encodes the body as JSON.
|
||||
#
|
||||
# Processes only requests with matching Content-type or those without a type.
|
||||
# If a request doesn't have a type but has a body, it sets the Content-type
|
||||
# to JSON MIME-type.
|
||||
#
|
||||
# Doesn't try to encode bodies that already are in string form.
|
||||
class Json < Middleware
|
||||
MIME_TYPE = 'application/json'
|
||||
MIME_TYPE_REGEX = %r{^application/(vnd\..+\+)?json$}.freeze
|
||||
|
||||
def on_request(env)
|
||||
match_content_type(env) do |data|
|
||||
env[:body] = encode(data)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def encode(data)
|
||||
::JSON.generate(data)
|
||||
end
|
||||
|
||||
def match_content_type(env)
|
||||
return unless process_request?(env)
|
||||
|
||||
env[:request_headers][CONTENT_TYPE] ||= MIME_TYPE
|
||||
yield env[:body] unless env[:body].respond_to?(:to_str)
|
||||
end
|
||||
|
||||
def process_request?(env)
|
||||
type = request_type(env)
|
||||
body?(env) && (type.empty? || type.match?(MIME_TYPE_REGEX))
|
||||
end
|
||||
|
||||
def body?(env)
|
||||
(body = env[:body]) && !(body.respond_to?(:to_str) && body.empty?)
|
||||
end
|
||||
|
||||
def request_type(env)
|
||||
type = env[:request_headers][CONTENT_TYPE].to_s
|
||||
type = type.split(';', 2).first if type.index(';')
|
||||
type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Faraday::Request.register_middleware(json: Faraday::Request::Json)
|
@ -1,106 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require File.expand_path('url_encoded', __dir__)
|
||||
require 'securerandom'
|
||||
|
||||
module Faraday
|
||||
class Request
|
||||
# Middleware for supporting multi-part requests.
|
||||
class Multipart < UrlEncoded
|
||||
self.mime_type = 'multipart/form-data'
|
||||
unless defined?(::Faraday::Request::Multipart::DEFAULT_BOUNDARY_PREFIX)
|
||||
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
|
||||
end
|
||||
|
||||
def initialize(app = nil, options = {})
|
||||
super(app)
|
||||
@options = options
|
||||
end
|
||||
|
||||
# Checks for files in the payload, otherwise leaves everything untouched.
|
||||
#
|
||||
# @param env [Faraday::Env]
|
||||
def call(env)
|
||||
match_content_type(env) do |params|
|
||||
env.request.boundary ||= unique_boundary
|
||||
env.request_headers[CONTENT_TYPE] +=
|
||||
"; boundary=#{env.request.boundary}"
|
||||
env.body = create_multipart(env, params)
|
||||
end
|
||||
@app.call env
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
def process_request?(env)
|
||||
type = request_type(env)
|
||||
env.body.respond_to?(:each_key) && !env.body.empty? && (
|
||||
(type.empty? && has_multipart?(env.body)) ||
|
||||
(type == self.class.mime_type)
|
||||
)
|
||||
end
|
||||
|
||||
# Returns true if obj is an enumerable with values that are multipart.
|
||||
#
|
||||
# @param obj [Object]
|
||||
# @return [Boolean]
|
||||
def has_multipart?(obj) # rubocop:disable Naming/PredicateName
|
||||
if obj.respond_to?(:each)
|
||||
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
||||
return true if val.respond_to?(:content_type) || has_multipart?(val)
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
# @param params [Hash]
|
||||
def create_multipart(env, params)
|
||||
boundary = env.request.boundary
|
||||
parts = process_params(params) do |key, value|
|
||||
part(boundary, key, value)
|
||||
end
|
||||
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
||||
|
||||
body = Faraday::CompositeReadIO.new(parts)
|
||||
env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
|
||||
body
|
||||
end
|
||||
|
||||
def part(boundary, key, value)
|
||||
if value.respond_to?(:to_part)
|
||||
value.to_part(boundary, key)
|
||||
else
|
||||
Faraday::Parts::Part.new(boundary, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [String]
|
||||
def unique_boundary
|
||||
"#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
|
||||
end
|
||||
|
||||
# @param params [Hash]
|
||||
# @param prefix [String]
|
||||
# @param pieces [Array]
|
||||
def process_params(params, prefix = nil, pieces = nil, &block)
|
||||
params.inject(pieces || []) do |all, (key, value)|
|
||||
if prefix
|
||||
key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
|
||||
end
|
||||
|
||||
case value
|
||||
when Array
|
||||
values = value.inject([]) { |a, v| a << [nil, v] }
|
||||
process_params(values, key, all, &block)
|
||||
when Hash
|
||||
process_params(value, key, all, &block)
|
||||
else
|
||||
# rubocop:disable Performance/RedundantBlockCall
|
||||
all << block.call(key, value)
|
||||
# rubocop:enable Performance/RedundantBlockCall
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,239 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
class Request
|
||||
# Catches exceptions and retries each request a limited number of times.
|
||||
#
|
||||
# By default, it retries 2 times and handles only timeout exceptions. It can
|
||||
# be configured with an arbitrary number of retries, a list of exceptions to
|
||||
# handle, a retry interval, a percentage of randomness to add to the retry
|
||||
# interval, and a backoff factor.
|
||||
#
|
||||
# @example Configure Retry middleware using intervals
|
||||
# Faraday.new do |conn|
|
||||
# conn.request(:retry, max: 2,
|
||||
# interval: 0.05,
|
||||
# interval_randomness: 0.5,
|
||||
# backoff_factor: 2,
|
||||
# exceptions: [CustomException, 'Timeout::Error'])
|
||||
#
|
||||
# conn.adapter(:net_http) # NB: Last middleware must be the adapter
|
||||
# end
|
||||
#
|
||||
# This example will result in a first interval that is random between 0.05
|
||||
# and 0.075 and a second interval that is random between 0.1 and 0.125.
|
||||
class Retry < Faraday::Middleware
|
||||
DEFAULT_EXCEPTIONS = [
|
||||
Errno::ETIMEDOUT, 'Timeout::Error',
|
||||
Faraday::TimeoutError, Faraday::RetriableResponse
|
||||
].freeze
|
||||
IDEMPOTENT_METHODS = %i[delete get head options put].freeze
|
||||
|
||||
# Options contains the configurable parameters for the Retry middleware.
|
||||
class Options < Faraday::Options.new(:max, :interval, :max_interval,
|
||||
:interval_randomness,
|
||||
:backoff_factor, :exceptions,
|
||||
:methods, :retry_if, :retry_block,
|
||||
:retry_statuses)
|
||||
|
||||
DEFAULT_CHECK = ->(_env, _exception) { false }
|
||||
|
||||
def self.from(value)
|
||||
if value.is_a?(Integer)
|
||||
new(value)
|
||||
else
|
||||
super(value)
|
||||
end
|
||||
end
|
||||
|
||||
def max
|
||||
(self[:max] ||= 2).to_i
|
||||
end
|
||||
|
||||
def interval
|
||||
(self[:interval] ||= 0).to_f
|
||||
end
|
||||
|
||||
def max_interval
|
||||
(self[:max_interval] ||= Float::MAX).to_f
|
||||
end
|
||||
|
||||
def interval_randomness
|
||||
(self[:interval_randomness] ||= 0).to_f
|
||||
end
|
||||
|
||||
def backoff_factor
|
||||
(self[:backoff_factor] ||= 1).to_f
|
||||
end
|
||||
|
||||
def exceptions
|
||||
Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
|
||||
end
|
||||
|
||||
def methods
|
||||
Array(self[:methods] ||= IDEMPOTENT_METHODS)
|
||||
end
|
||||
|
||||
def retry_if
|
||||
self[:retry_if] ||= DEFAULT_CHECK
|
||||
end
|
||||
|
||||
def retry_block
|
||||
self[:retry_block] ||= proc {}
|
||||
end
|
||||
|
||||
def retry_statuses
|
||||
Array(self[:retry_statuses] ||= [])
|
||||
end
|
||||
end
|
||||
|
||||
# @param app [#call]
|
||||
# @param options [Hash]
|
||||
# @option options [Integer] :max (2) Maximum number of retries
|
||||
# @option options [Integer] :interval (0) Pause in seconds between retries
|
||||
# @option options [Integer] :interval_randomness (0) The maximum random
|
||||
# interval amount expressed as a float between
|
||||
# 0 and 1 to use in addition to the interval.
|
||||
# @option options [Integer] :max_interval (Float::MAX) An upper limit
|
||||
# for the interval
|
||||
# @option options [Integer] :backoff_factor (1) The amount to multiply
|
||||
# each successive retry's interval amount by in order to provide backoff
|
||||
# @option options [Array] :exceptions ([ Errno::ETIMEDOUT,
|
||||
# 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse])
|
||||
# The list of exceptions to handle. Exceptions can be given as
|
||||
# Class, Module, or String.
|
||||
# @option options [Array] :methods (the idempotent HTTP methods
|
||||
# in IDEMPOTENT_METHODS) A list of HTTP methods to retry without
|
||||
# calling retry_if. Pass an empty Array to call retry_if
|
||||
# for all exceptions.
|
||||
# @option options [Block] :retry_if (false) block that will receive
|
||||
# the env object and the exception raised
|
||||
# and should decide if the code should retry still the action or
|
||||
# not independent of the retry count. This would be useful
|
||||
# if the exception produced is non-recoverable or if the
|
||||
# the HTTP method called is not idempotent.
|
||||
# @option options [Block] :retry_block block that is executed before
|
||||
# every retry. Request environment, middleware options, current number
|
||||
# of retries and the exception is passed to the block as parameters.
|
||||
# @option options [Array] :retry_statuses Array of Integer HTTP status
|
||||
# codes or a single Integer value that determines whether to raise
|
||||
# a Faraday::RetriableResponse exception based on the HTTP status code
|
||||
# of an HTTP response.
|
||||
def initialize(app, options = nil)
|
||||
super(app)
|
||||
@options = Options.from(options)
|
||||
@errmatch = build_exception_matcher(@options.exceptions)
|
||||
end
|
||||
|
||||
def calculate_sleep_amount(retries, env)
|
||||
retry_after = calculate_retry_after(env)
|
||||
retry_interval = calculate_retry_interval(retries)
|
||||
|
||||
return if retry_after && retry_after > @options.max_interval
|
||||
|
||||
if retry_after && retry_after >= retry_interval
|
||||
retry_after
|
||||
else
|
||||
retry_interval
|
||||
end
|
||||
end
|
||||
|
||||
# @param env [Faraday::Env]
|
||||
def call(env)
|
||||
retries = @options.max
|
||||
request_body = env[:body]
|
||||
begin
|
||||
# after failure env[:body] is set to the response body
|
||||
env[:body] = request_body
|
||||
@app.call(env).tap do |resp|
|
||||
if @options.retry_statuses.include?(resp.status)
|
||||
raise Faraday::RetriableResponse.new(nil, resp)
|
||||
end
|
||||
end
|
||||
rescue @errmatch => e
|
||||
if retries.positive? && retry_request?(env, e)
|
||||
retries -= 1
|
||||
rewind_files(request_body)
|
||||
@options.retry_block.call(env, @options, retries, e)
|
||||
if (sleep_amount = calculate_sleep_amount(retries + 1, env))
|
||||
sleep sleep_amount
|
||||
retry
|
||||
end
|
||||
end
|
||||
|
||||
raise unless e.is_a?(Faraday::RetriableResponse)
|
||||
|
||||
e.response
|
||||
end
|
||||
end
|
||||
|
||||
# An exception matcher for the rescue clause can usually be any object
|
||||
# that responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
|
||||
#
|
||||
# @param exceptions [Array]
|
||||
# @api private
|
||||
# @return [Module] an exception matcher
|
||||
def build_exception_matcher(exceptions)
|
||||
matcher = Module.new
|
||||
(
|
||||
class << matcher
|
||||
self
|
||||
end).class_eval do
|
||||
define_method(:===) do |error|
|
||||
exceptions.any? do |ex|
|
||||
if ex.is_a? Module
|
||||
error.is_a? ex
|
||||
else
|
||||
error.class.to_s == ex.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
matcher
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def retry_request?(env, exception)
|
||||
@options.methods.include?(env[:method]) ||
|
||||
@options.retry_if.call(env, exception)
|
||||
end
|
||||
|
||||
def rewind_files(body)
|
||||
return unless body.is_a?(Hash)
|
||||
|
||||
body.each do |_, value|
|
||||
value.rewind if value.is_a?(UploadIO)
|
||||
end
|
||||
end
|
||||
|
||||
# MDN spec for Retry-After header:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
def calculate_retry_after(env)
|
||||
response_headers = env[:response_headers]
|
||||
return unless response_headers
|
||||
|
||||
retry_after_value = env[:response_headers]['Retry-After']
|
||||
|
||||
# Try to parse date from the header value
|
||||
begin
|
||||
datetime = DateTime.rfc2822(retry_after_value)
|
||||
datetime.to_time - Time.now.utc
|
||||
rescue ArgumentError
|
||||
retry_after_value.to_f
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_retry_interval(retries)
|
||||
retry_index = @options.max - retries
|
||||
current_interval = @options.interval *
|
||||
(@options.backoff_factor**retry_index)
|
||||
current_interval = [current_interval, @options.max_interval].min
|
||||
random_interval = rand * @options.interval_randomness.to_f *
|
||||
@options.interval
|
||||
|
||||
current_interval + random_interval
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -22,7 +22,8 @@ module Faraday
|
||||
|
||||
register_middleware File.expand_path('response', __dir__),
|
||||
raise_error: [:RaiseError, 'raise_error'],
|
||||
logger: [:Logger, 'logger']
|
||||
logger: [:Logger, 'logger'],
|
||||
json: [:Json, 'json']
|
||||
|
||||
def initialize(env = nil)
|
||||
@env = Env.from(env) if env
|
||||
@ -42,6 +43,7 @@ module Faraday
|
||||
def headers
|
||||
finished? ? env.response_headers : {}
|
||||
end
|
||||
|
||||
def_delegator :headers, :[]
|
||||
|
||||
def body
|
||||
|
54
lib/faraday/response/json.rb
Normal file
54
lib/faraday/response/json.rb
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
|
||||
module Faraday
|
||||
class Response
|
||||
# Parse response bodies as JSON.
|
||||
class Json < Middleware
|
||||
def initialize(app = nil, options = {})
|
||||
super(app)
|
||||
@parser_options = options[:parser_options]
|
||||
@content_types = Array(options[:content_type] || /\bjson$/)
|
||||
@preserve_raw = options[:preserve_raw]
|
||||
end
|
||||
|
||||
def on_complete(env)
|
||||
process_response(env) if parse_response?(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_response(env)
|
||||
env[:raw_body] = env[:body] if @preserve_raw
|
||||
env[:body] = parse(env[:body])
|
||||
rescue StandardError, SyntaxError => e
|
||||
raise Faraday::ParsingError.new(e, env[:response])
|
||||
end
|
||||
|
||||
def parse(body)
|
||||
::JSON.parse(body, @parser_options || {}) unless body.strip.empty?
|
||||
end
|
||||
|
||||
def parse_response?(env)
|
||||
process_response_type?(env) &&
|
||||
env[:body].respond_to?(:to_str)
|
||||
end
|
||||
|
||||
def process_response_type?(env)
|
||||
type = response_type(env)
|
||||
@content_types.empty? || @content_types.any? do |pattern|
|
||||
pattern.is_a?(Regexp) ? type.match?(pattern) : type == pattern
|
||||
end
|
||||
end
|
||||
|
||||
def response_type(env)
|
||||
type = env[:response_headers][CONTENT_TYPE].to_s
|
||||
type = type.split(';', 2).first if type.index(';')
|
||||
type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Faraday::Response.register_middleware(json: Faraday::Response::Json)
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
require 'logger'
|
||||
require 'faraday/logging/formatter'
|
||||
|
||||
module Faraday
|
||||
@ -11,10 +12,7 @@ module Faraday
|
||||
class Logger < Middleware
|
||||
def initialize(app, logger = nil, options = {})
|
||||
super(app)
|
||||
logger ||= begin
|
||||
require 'logger'
|
||||
::Logger.new($stdout)
|
||||
end
|
||||
logger ||= ::Logger.new($stdout)
|
||||
formatter_class = options.delete(:formatter) || Logging::Formatter
|
||||
@formatter = formatter_class.new(logger: logger, options: options)
|
||||
yield @formatter if block_given?
|
||||
|
@ -1,5 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Faraday
|
||||
VERSION = '1.7.0'
|
||||
VERSION = '1.10.4'
|
||||
end
|
||||
|
@ -258,6 +258,38 @@ RSpec.describe Faraday::Adapter::Test do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'for request with non default params encoder' do
|
||||
let(:connection) do
|
||||
Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
end
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new do |stubs|
|
||||
stubs.get('/path?a=x&a=y&a=z') { [200, {}, 'a'] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all flat param values are correctly set' do
|
||||
subject(:request) { connection.get('/path?a=x&a=y&a=z') }
|
||||
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
|
||||
shared_examples 'raise NotFound when params do not satisfy the flat param values' do |params|
|
||||
subject(:request) { connection.get('/path', params) }
|
||||
|
||||
context "with #{params.inspect}" do
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x] }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x y] }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x z y] } # NOTE: The order of the value is also compared.
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { b: %w[x y z] }
|
||||
end
|
||||
|
||||
describe 'strict_mode' do
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new(strict_mode: true) do |stubs|
|
||||
|
147
spec/faraday/deprecate_spec.rb
Normal file
147
spec/faraday/deprecate_spec.rb
Normal file
@ -0,0 +1,147 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::DeprecatedClass do
|
||||
class SampleClass < StandardError
|
||||
attr_accessor :foo
|
||||
|
||||
def initialize(foo = nil)
|
||||
@foo = foo || :foo
|
||||
end
|
||||
end
|
||||
|
||||
SampleDeprecatedClass = Faraday::DeprecatedClass.proxy_class(SampleClass)
|
||||
|
||||
it 'does not raise error for deprecated classes but prints an error message' do
|
||||
error_message, foobar = with_warn_squelching { SampleDeprecatedClass.new(:foo_bar) }
|
||||
expect(foobar).to be_a(SampleClass)
|
||||
expect(foobar.foo).to eq(:foo_bar)
|
||||
expect(error_message).to match(
|
||||
Regexp.new(
|
||||
'NOTE: SampleDeprecatedClass.new is deprecated; '\
|
||||
'use SampleClass.new instead. It will be removed in or after version 1.0'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not raise an error for inherited error-namespaced classes but prints an error message' do
|
||||
error_message, = with_warn_squelching { Class.new(SampleDeprecatedClass) }
|
||||
|
||||
expect(error_message).to match(
|
||||
Regexp.new(
|
||||
'NOTE: Inheriting SampleDeprecatedClass is deprecated; '\
|
||||
'use SampleClass instead. It will be removed in or after version 1.0'
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'allows backward-compatible class to be subclassed' do
|
||||
expect do
|
||||
with_warn_squelching { Class.new(SampleDeprecatedClass) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it 'allows rescuing of a current error with a deprecated error' do
|
||||
expect { raise SampleClass, nil }.to raise_error(SampleDeprecatedClass)
|
||||
end
|
||||
|
||||
it 'allows rescuing of a current error with a current error' do
|
||||
expect { raise SampleClass, nil }.to raise_error(SampleClass)
|
||||
end
|
||||
|
||||
it 'allows rescuing of a deprecated error with a deprecated error' do
|
||||
expect { raise SampleDeprecatedClass, nil }.to raise_error(SampleDeprecatedClass)
|
||||
end
|
||||
|
||||
it 'allows rescuing of a deprecated error with a current error' do
|
||||
expect { raise SampleDeprecatedClass, nil }.to raise_error(SampleClass)
|
||||
end
|
||||
|
||||
describe 'match behavior' do
|
||||
class SampleDeprecatedClassA < SampleDeprecatedClass; end
|
||||
class SampleDeprecatedClassB < SampleDeprecatedClass; end
|
||||
|
||||
class SampleDeprecatedClassAX < SampleDeprecatedClassA; end
|
||||
|
||||
class SampleClassA < SampleClass; end
|
||||
|
||||
describe 'undeprecated class' do
|
||||
it 'is === to instance of deprecated class' do
|
||||
expect(SampleDeprecatedClass.new.is_a?(SampleClass)).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassA.new.is_a?(SampleClass)).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of subclass of subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassAX.new.is_a?(SampleClass)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subclass of undeprecated class' do
|
||||
it 'is not === to instance of undeprecated class' do
|
||||
expect(SampleClass.new.is_a?(SampleClassA)).to be false
|
||||
end
|
||||
|
||||
it 'is not === to instance of deprecated class' do
|
||||
expect(SampleDeprecatedClass.new.is_a?(SampleClassA)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'deprecated class' do
|
||||
it 'is === to instance of undeprecated class' do
|
||||
expect(SampleDeprecatedClass.new.is_a?(SampleClass)).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of subclass of undeprecated class' do
|
||||
expect(SampleClassA.superclass == SampleDeprecatedClass.superclass).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassA.new.is_a?(SampleDeprecatedClass)).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of subclass of subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassAX.new.is_a?(SampleDeprecatedClass)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subclass of deprecated class' do
|
||||
it 'is not === to instance of subclass of undeprecated class' do
|
||||
expect(SampleClass.new.is_a?(SampleDeprecatedClassA)).to be false
|
||||
end
|
||||
|
||||
it 'is not === to instance of another subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassB.new.is_a?(SampleDeprecatedClassA)).to be false
|
||||
end
|
||||
|
||||
it 'is === to instance of its subclass' do
|
||||
expect(SampleDeprecatedClassAX.new.is_a?(SampleDeprecatedClassA)).to be true
|
||||
end
|
||||
|
||||
it 'is === to instance of deprecated class' do
|
||||
expect(SampleDeprecatedClassB.new.is_a?(SampleDeprecatedClass)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'subclass of subclass of deprecated class' do
|
||||
it 'is not === to instance of subclass of another subclass of deprecated class' do
|
||||
expect(SampleDeprecatedClassB.new.is_a?(SampleDeprecatedClassAX)).to be false
|
||||
end
|
||||
|
||||
it 'is not === to instance of its superclass' do
|
||||
expect(SampleDeprecatedClass.new.is_a?(SampleDeprecatedClassA)).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_warn_squelching
|
||||
stderr_catcher = StringIO.new
|
||||
original_stderr = $stderr
|
||||
$stderr = stderr_catcher
|
||||
result = yield if block_given?
|
||||
[stderr_catcher.tap(&:rewind).string, result]
|
||||
ensure
|
||||
$stderr = original_stderr
|
||||
end
|
||||
end
|
@ -84,5 +84,13 @@ RSpec.describe Faraday::Request::Authorization do
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed a string and a proc' do
|
||||
let(:auth_config) { ['Bearer', -> { 'custom_from_proc' }] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer custom_from_proc') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
111
spec/faraday/request/json_spec.rb
Normal file
111
spec/faraday/request/json_spec.rb
Normal file
@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Json do
|
||||
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
|
||||
|
||||
def process(body, content_type = nil)
|
||||
env = { body: body, request_headers: Faraday::Utils::Headers.new }
|
||||
env[:request_headers]['content-type'] = content_type if content_type
|
||||
middleware.call(Faraday::Env.from(env)).env
|
||||
end
|
||||
|
||||
def result_body
|
||||
result[:body]
|
||||
end
|
||||
|
||||
def result_type
|
||||
result[:request_headers]['content-type']
|
||||
end
|
||||
|
||||
context 'no body' do
|
||||
let(:result) { process(nil) }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to be_nil
|
||||
end
|
||||
|
||||
it "doesn't add content type" do
|
||||
expect(result_type).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty body' do
|
||||
let(:result) { process('') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to be_empty
|
||||
end
|
||||
|
||||
it "doesn't add content type" do
|
||||
expect(result_type).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'string body' do
|
||||
let(:result) { process('{"a":1}') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body' do
|
||||
let(:result) { process(a: 1) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty object body' do
|
||||
let(:result) { process({}) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with json type' do
|
||||
let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/json; charset=utf-8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with vendor json type' do
|
||||
let(:result) { process({ a: 1 }, 'application/vnd.myapp.v1+json; charset=utf-8') }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/vnd.myapp.v1+json; charset=utf-8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with incompatible type' do
|
||||
let(:result) { process({ a: 1 }, 'application/xml; charset=utf-8') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to eq(a: 1)
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/xml; charset=utf-8')
|
||||
end
|
||||
end
|
||||
end
|
@ -1,302 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Multipart do
|
||||
let(:options) { {} }
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.request :multipart, options
|
||||
b.request :url_encoded
|
||||
b.adapter :test do |stub|
|
||||
stub.post('/echo') do |env|
|
||||
posted_as = env[:request_headers]['Content-Type']
|
||||
expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
|
||||
[200, { 'Content-Type' => posted_as }, env[:body].read]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a multipart request' do
|
||||
it 'generates a unique boundary for each request' do
|
||||
response1 = conn.post('/echo', payload)
|
||||
response2 = conn.post('/echo', payload)
|
||||
|
||||
b1 = parse_multipart_boundary(response1.headers['Content-Type'])
|
||||
b2 = parse_multipart_boundary(response2.headers['Content-Type'])
|
||||
expect(b1).to_not eq(b2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'FilePart: when multipart objects in param' do
|
||||
let(:payload) do
|
||||
{
|
||||
a: 1,
|
||||
b: {
|
||||
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
|
||||
'Content-Disposition' => 'form-data; foo=1'),
|
||||
d: 2
|
||||
}
|
||||
}
|
||||
end
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_a, body_a = result.part('a')
|
||||
expect(part_a).to_not be_nil
|
||||
expect(part_a.filename).to be_nil
|
||||
expect(body_a).to eq('1')
|
||||
|
||||
part_bc, body_bc = result.part('b[c]')
|
||||
expect(part_bc).to_not be_nil
|
||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
||||
expect(part_bc.headers['content-disposition'])
|
||||
.to eq(
|
||||
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
||||
)
|
||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
||||
expect(body_bc).to eq(File.read(__FILE__))
|
||||
|
||||
part_bd, body_bd = result.part('b[d]')
|
||||
expect(part_bd).to_not be_nil
|
||||
expect(part_bd.filename).to be_nil
|
||||
expect(body_bd).to eq('2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'FilePart: when providing json and IO content in the same payload' do
|
||||
let(:io) { StringIO.new('io-content') }
|
||||
let(:json) do
|
||||
{
|
||||
b: 1,
|
||||
c: 2
|
||||
}.to_json
|
||||
end
|
||||
|
||||
let(:payload) do
|
||||
{
|
||||
json: Faraday::ParamPart.new(json, 'application/json'),
|
||||
io: Faraday::FilePart.new(io, 'application/pdf')
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_json, body_json = result.part('json')
|
||||
expect(part_json).to_not be_nil
|
||||
expect(part_json.mime).to eq('application/json')
|
||||
expect(part_json.filename).to be_nil
|
||||
expect(body_json).to eq(json)
|
||||
|
||||
part_io, body_io = result.part('io')
|
||||
expect(part_io).to_not be_nil
|
||||
expect(part_io.mime).to eq('application/pdf')
|
||||
expect(part_io.filename).to eq('local.path')
|
||||
expect(body_io).to eq(io.string)
|
||||
end
|
||||
end
|
||||
|
||||
context 'FilePart: when multipart objects in array param' do
|
||||
let(:payload) do
|
||||
{
|
||||
a: 1,
|
||||
b: [{
|
||||
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
|
||||
d: 2
|
||||
}]
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_a, body_a = result.part('a')
|
||||
expect(part_a).to_not be_nil
|
||||
expect(part_a.filename).to be_nil
|
||||
expect(body_a).to eq('1')
|
||||
|
||||
part_bc, body_bc = result.part('b[][c]')
|
||||
expect(part_bc).to_not be_nil
|
||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
||||
expect(part_bc.headers['content-disposition'])
|
||||
.to eq(
|
||||
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
||||
)
|
||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
||||
expect(body_bc).to eq(File.read(__FILE__))
|
||||
|
||||
part_bd, body_bd = result.part('b[][d]')
|
||||
expect(part_bd).to_not be_nil
|
||||
expect(part_bd.filename).to be_nil
|
||||
expect(body_bd).to eq('2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'UploadIO: when multipart objects in param' do
|
||||
let(:payload) do
|
||||
{
|
||||
a: 1,
|
||||
b: {
|
||||
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil,
|
||||
'Content-Disposition' => 'form-data; foo=1'),
|
||||
d: 2
|
||||
}
|
||||
}
|
||||
end
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_a, body_a = result.part('a')
|
||||
expect(part_a).to_not be_nil
|
||||
expect(part_a.filename).to be_nil
|
||||
expect(body_a).to eq('1')
|
||||
|
||||
part_bc, body_bc = result.part('b[c]')
|
||||
expect(part_bc).to_not be_nil
|
||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
||||
expect(part_bc.headers['content-disposition'])
|
||||
.to eq(
|
||||
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
||||
)
|
||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
||||
expect(body_bc).to eq(File.read(__FILE__))
|
||||
|
||||
part_bd, body_bd = result.part('b[d]')
|
||||
expect(part_bd).to_not be_nil
|
||||
expect(part_bd.filename).to be_nil
|
||||
expect(body_bd).to eq('2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'UploadIO: when providing json and IO content in the same payload' do
|
||||
let(:io) { StringIO.new('io-content') }
|
||||
let(:json) do
|
||||
{
|
||||
b: 1,
|
||||
c: 2
|
||||
}.to_json
|
||||
end
|
||||
|
||||
let(:payload) do
|
||||
{
|
||||
json: Faraday::ParamPart.new(json, 'application/json'),
|
||||
io: Faraday::UploadIO.new(io, 'application/pdf')
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_json, body_json = result.part('json')
|
||||
expect(part_json).to_not be_nil
|
||||
expect(part_json.mime).to eq('application/json')
|
||||
expect(part_json.filename).to be_nil
|
||||
expect(body_json).to eq(json)
|
||||
|
||||
part_io, body_io = result.part('io')
|
||||
expect(part_io).to_not be_nil
|
||||
expect(part_io.mime).to eq('application/pdf')
|
||||
expect(part_io.filename).to eq('local.path')
|
||||
expect(body_io).to eq(io.string)
|
||||
end
|
||||
end
|
||||
|
||||
context 'UploadIO: when multipart objects in array param' do
|
||||
let(:payload) do
|
||||
{
|
||||
a: 1,
|
||||
b: [{
|
||||
c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
|
||||
d: 2
|
||||
}]
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'forms a multipart request' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
||||
result = parse_multipart(boundary, response.body)
|
||||
expect(result[:errors]).to be_empty
|
||||
|
||||
part_a, body_a = result.part('a')
|
||||
expect(part_a).to_not be_nil
|
||||
expect(part_a.filename).to be_nil
|
||||
expect(body_a).to eq('1')
|
||||
|
||||
part_bc, body_bc = result.part('b[][c]')
|
||||
expect(part_bc).to_not be_nil
|
||||
expect(part_bc.filename).to eq('multipart_spec.rb')
|
||||
expect(part_bc.headers['content-disposition'])
|
||||
.to eq(
|
||||
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
||||
)
|
||||
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
||||
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
||||
expect(body_bc).to eq(File.read(__FILE__))
|
||||
|
||||
part_bd, body_bd = result.part('b[][d]')
|
||||
expect(part_bd).to_not be_nil
|
||||
expect(part_bd.filename).to be_nil
|
||||
expect(body_bd).to eq('2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passing flat_encode=true option' do
|
||||
let(:options) { { flat_encode: true } }
|
||||
let(:io) { StringIO.new('io-content') }
|
||||
let(:payload) do
|
||||
{
|
||||
a: 1,
|
||||
b: [
|
||||
Faraday::UploadIO.new(io, 'application/pdf'),
|
||||
Faraday::UploadIO.new(io, 'application/pdf')
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a multipart request'
|
||||
|
||||
it 'encode params using flat encoder' do
|
||||
response = conn.post('/echo', payload)
|
||||
|
||||
expect(response.body).to include('name="b"')
|
||||
expect(response.body).not_to include('name="b[]"')
|
||||
end
|
||||
end
|
||||
end
|
@ -1,242 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Retry do
|
||||
let(:calls) { [] }
|
||||
let(:times_called) { calls.size }
|
||||
let(:options) { [] }
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.request :retry, *options
|
||||
|
||||
b.adapter :test do |stub|
|
||||
%w[get post].each do |method|
|
||||
stub.send(method, '/unstable') do |env|
|
||||
calls << env.dup
|
||||
env[:body] = nil # simulate blanking out response body
|
||||
callback.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an unexpected error happens' do
|
||||
let(:callback) { -> { raise 'boom!' } }
|
||||
|
||||
before { expect { conn.get('/unstable') }.to raise_error(RuntimeError) }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
|
||||
context 'and this is passed as a custom exception' do
|
||||
let(:options) { [{ exceptions: StandardError }] }
|
||||
|
||||
it { expect(times_called).to eq(3) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an expected error happens' do
|
||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
||||
|
||||
before do
|
||||
@started = Time.now
|
||||
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
||||
end
|
||||
|
||||
it { expect(times_called).to eq(3) }
|
||||
|
||||
context 'and legacy max_retry set to 1' do
|
||||
let(:options) { [1] }
|
||||
|
||||
it { expect(times_called).to eq(2) }
|
||||
end
|
||||
|
||||
context 'and legacy max_retry set to -9' do
|
||||
let(:options) { [-9] }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
end
|
||||
|
||||
context 'and new max_retry set to 3' do
|
||||
let(:options) { [{ max: 3 }] }
|
||||
|
||||
it { expect(times_called).to eq(4) }
|
||||
end
|
||||
|
||||
context 'and new max_retry set to -9' do
|
||||
let(:options) { [{ max: -9 }] }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
end
|
||||
|
||||
context 'and both max_retry and interval are set' do
|
||||
let(:options) { [{ max: 2, interval: 0.1 }] }
|
||||
|
||||
it { expect(Time.now - @started).to be_within(0.04).of(0.2) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no exception raised' do
|
||||
let(:options) { [{ max: 1, retry_statuses: 429 }] }
|
||||
|
||||
before { conn.get('/unstable') }
|
||||
|
||||
context 'and response code is in retry_statuses' do
|
||||
let(:callback) { -> { [429, {}, ''] } }
|
||||
|
||||
it { expect(times_called).to eq(2) }
|
||||
end
|
||||
|
||||
context 'and response code is not in retry_statuses' do
|
||||
let(:callback) { -> { [503, {}, ''] } }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#calculate_retry_interval' do
|
||||
context 'with exponential backoff' do
|
||||
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2 } }
|
||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
||||
|
||||
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
||||
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
||||
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.4) }
|
||||
end
|
||||
|
||||
context 'with exponential backoff and max_interval' do
|
||||
let(:options) { { max: 5, interval: 0.1, backoff_factor: 2, max_interval: 0.3 } }
|
||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
||||
|
||||
it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
|
||||
it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
|
||||
it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.3) }
|
||||
it { expect(middleware.send(:calculate_retry_interval, 2)).to eq(0.3) }
|
||||
end
|
||||
|
||||
context 'with exponential backoff and interval_randomness' do
|
||||
let(:options) { { max: 2, interval: 0.1, interval_randomness: 0.05 } }
|
||||
let(:middleware) { Faraday::Request::Retry.new(nil, options) }
|
||||
|
||||
it { expect(middleware.send(:calculate_retry_interval, 2)).to be_between(0.1, 0.105) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when method is not idempotent' do
|
||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
||||
|
||||
before { expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT) }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
end
|
||||
|
||||
describe 'retry_if option' do
|
||||
let(:callback) { -> { raise Errno::ETIMEDOUT } }
|
||||
let(:options) { [{ retry_if: @check }] }
|
||||
|
||||
it 'retries if retry_if block always returns true' do
|
||||
body = { foo: :bar }
|
||||
@check = ->(_, _) { true }
|
||||
expect { conn.post('/unstable', body) }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(3)
|
||||
expect(calls.all? { |env| env[:body] == body }).to be_truthy
|
||||
end
|
||||
|
||||
it 'does not retry if retry_if block returns false checking env' do
|
||||
@check = ->(env, _) { env[:method] != :post }
|
||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not retry if retry_if block returns false checking exception' do
|
||||
@check = ->(_, exception) { !exception.is_a?(Errno::ETIMEDOUT) }
|
||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(1)
|
||||
end
|
||||
|
||||
it 'FilePart: should rewind files on retry' do
|
||||
io = StringIO.new('Test data')
|
||||
filepart = Faraday::FilePart.new(io, 'application/octet/stream')
|
||||
|
||||
rewound = 0
|
||||
rewind = -> { rewound += 1 }
|
||||
|
||||
@check = ->(_, _) { true }
|
||||
allow(filepart).to receive(:rewind, &rewind)
|
||||
expect { conn.post('/unstable', file: filepart) }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(3)
|
||||
expect(rewound).to eq(2)
|
||||
end
|
||||
|
||||
it 'UploadIO: should rewind files on retry' do
|
||||
io = StringIO.new('Test data')
|
||||
upload_io = Faraday::UploadIO.new(io, 'application/octet/stream')
|
||||
|
||||
rewound = 0
|
||||
rewind = -> { rewound += 1 }
|
||||
|
||||
@check = ->(_, _) { true }
|
||||
allow(upload_io).to receive(:rewind, &rewind)
|
||||
expect { conn.post('/unstable', file: upload_io) }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(3)
|
||||
expect(rewound).to eq(2)
|
||||
end
|
||||
|
||||
context 'when explicitly specifying methods to retry' do
|
||||
let(:options) { [{ retry_if: @check, methods: [:post] }] }
|
||||
|
||||
it 'does not call retry_if for specified methods' do
|
||||
@check = ->(_, _) { raise 'this should have never been called' }
|
||||
expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with empty list of methods to retry' do
|
||||
let(:options) { [{ retry_if: @check, methods: [] }] }
|
||||
|
||||
it 'calls retry_if for all methods' do
|
||||
@check = ->(_, _) { calls.size < 2 }
|
||||
expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
|
||||
expect(times_called).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'retry_after header support' do
|
||||
let(:callback) { -> { [504, headers, ''] } }
|
||||
let(:elapsed) { Time.now - @started }
|
||||
|
||||
before do
|
||||
@started = Time.now
|
||||
conn.get('/unstable')
|
||||
end
|
||||
|
||||
context 'when retry_after bigger than interval' do
|
||||
let(:headers) { { 'Retry-After' => '0.5' } }
|
||||
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
||||
|
||||
it { expect(elapsed).to be > 0.5 }
|
||||
end
|
||||
|
||||
context 'when retry_after smaller than interval' do
|
||||
let(:headers) { { 'Retry-After' => '0.1' } }
|
||||
let(:options) { [{ max: 1, interval: 0.2, retry_statuses: 504 }] }
|
||||
|
||||
it { expect(elapsed).to be > 0.2 }
|
||||
end
|
||||
|
||||
context 'when retry_after is a timestamp' do
|
||||
let(:headers) { { 'Retry-After' => (Time.now.utc + 2).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
||||
let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
|
||||
|
||||
it { expect(elapsed).to be > 1 }
|
||||
end
|
||||
|
||||
context 'when retry_after is bigger than max_interval' do
|
||||
let(:headers) { { 'Retry-After' => (Time.now.utc + 20).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
|
||||
let(:options) { [{ max: 2, interval: 0.1, max_interval: 5, retry_statuses: 504 }] }
|
||||
|
||||
it { expect(times_called).to eq(1) }
|
||||
end
|
||||
end
|
||||
end
|
@ -25,7 +25,7 @@ RSpec.describe Faraday::Request do
|
||||
describe 'deprecate method for HTTP method' do
|
||||
let(:http_method) { :post }
|
||||
let(:expected_warning) do
|
||||
%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+.}
|
||||
%r{NOTE: Faraday::Request#method is deprecated; use http_method instead\. It will be removed in or after version 2.0 \nFaraday::Request#method called from .+/spec/faraday/request_spec.rb:\d+.}
|
||||
end
|
||||
|
||||
it { expect(subject.method).to eq(:post) }
|
||||
|
119
spec/faraday/response/json_spec.rb
Normal file
119
spec/faraday/response/json_spec.rb
Normal file
@ -0,0 +1,119 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Response::Json, type: :response do
|
||||
let(:options) { {} }
|
||||
let(:headers) { {} }
|
||||
let(:middleware) do
|
||||
described_class.new(lambda { |env|
|
||||
Faraday::Response.new(env)
|
||||
}, **options)
|
||||
end
|
||||
|
||||
def process(body, content_type = 'application/json', options = {})
|
||||
env = {
|
||||
body: body, request: options,
|
||||
request_headers: Faraday::Utils::Headers.new,
|
||||
response_headers: Faraday::Utils::Headers.new(headers)
|
||||
}
|
||||
env[:response_headers]['content-type'] = content_type if content_type
|
||||
yield(env) if block_given?
|
||||
middleware.call(Faraday::Env.from(env))
|
||||
end
|
||||
|
||||
context 'no type matching' do
|
||||
it "doesn't change nil body" do
|
||||
expect(process(nil).body).to be_nil
|
||||
end
|
||||
|
||||
it 'nullifies empty body' do
|
||||
expect(process('').body).to be_nil
|
||||
end
|
||||
|
||||
it 'parses json body' do
|
||||
response = process('{"a":1}')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
expect(response.env[:raw_body]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with preserving raw' do
|
||||
let(:options) { { preserve_raw: true } }
|
||||
|
||||
it 'parses json body' do
|
||||
response = process('{"a":1}')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
expect(response.env[:raw_body]).to eq('{"a":1}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default regexp type matching' do
|
||||
it 'parses json body of correct type' do
|
||||
response = process('{"a":1}', 'application/x-json')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
end
|
||||
|
||||
it 'ignores json body of incorrect type' do
|
||||
response = process('{"a":1}', 'text/json-xml')
|
||||
expect(response.body).to eq('{"a":1}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array type matching' do
|
||||
let(:options) { { content_type: %w[a/b c/d] } }
|
||||
|
||||
it 'parses json body of correct type' do
|
||||
expect(process('{"a":1}', 'a/b').body).to be_a(Hash)
|
||||
expect(process('{"a":1}', 'c/d').body).to be_a(Hash)
|
||||
end
|
||||
|
||||
it 'ignores json body of incorrect type' do
|
||||
expect(process('{"a":1}', 'a/d').body).not_to be_a(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
it 'chokes on invalid json' do
|
||||
expect { process('{!') }.to raise_error(Faraday::ParsingError)
|
||||
end
|
||||
|
||||
it 'includes the response on the ParsingError instance' do
|
||||
begin
|
||||
process('{') { |env| env[:response] = Faraday::Response.new }
|
||||
raise 'Parsing should have failed.'
|
||||
rescue Faraday::ParsingError => e
|
||||
expect(e.response).to be_a(Faraday::Response)
|
||||
end
|
||||
end
|
||||
|
||||
context 'HEAD responses' do
|
||||
it "nullifies the body if it's only one space" do
|
||||
response = process(' ')
|
||||
expect(response.body).to be_nil
|
||||
end
|
||||
|
||||
it "nullifies the body if it's two spaces" do
|
||||
response = process(' ')
|
||||
expect(response.body).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'JSON options' do
|
||||
let(:body) { '{"a": 1}' }
|
||||
let(:result) { { a: 1 } }
|
||||
let(:options) do
|
||||
{
|
||||
parser_options: {
|
||||
symbolize_names: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'passes relevant options to JSON parse' do
|
||||
expect(::JSON).to receive(:parse)
|
||||
.with(body, options[:parser_options])
|
||||
.and_return(result)
|
||||
|
||||
response = process(body)
|
||||
expect(response.body).to eq(result)
|
||||
end
|
||||
end
|
||||
end
|
@ -38,6 +38,8 @@ require 'pry'
|
||||
|
||||
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
||||
|
||||
Faraday::Deprecate.skip = false
|
||||
|
||||
RSpec.configure do |config|
|
||||
# rspec-expectations config goes here. You can use an alternate
|
||||
# assertion/expectation library such as wrong or the stdlib/minitest
|
||||
|
Loading…
x
Reference in New Issue
Block a user