Compare commits

...

35 Commits

Author SHA1 Message Date
Matt
41c990ee1f
Version bump to 1.10.4 2024-09-20 15:39:11 +01:00
Matt
435888d46c
Make sure that Faraday::Request::Json and Faraday::Response::Json are correctly autoloaded (#1595) 2024-09-20 15:38:27 +01:00
Matt
82086930f9
Use ruby/setup-ruby in publish.yml 2023-01-18 15:42:56 +00:00
Matt
eeec3677d7
Fix ruby version in publish.yml 2023-01-18 15:40:12 +00:00
Matt
92c038bcbf
Update publish.yml to use Ruby 2.7 2023-01-18 15:08:48 +00:00
Matt
7a382b5abb
Version bump to 1.10.3 2023-01-18 15:04:12 +00:00
Tim Rogers
16506ee918
Add support for Ruby 3.2.0 in Faraday v1.x (#1483)
* Run tests against Ruby 3.2

* Declare `DependencyLoader#new` with `ruby2_keywords` to fix Ruby 3.2.0

When adding a middleware that receives keyword arguments in the
constructor, the call from `DependencyLoader#new` fails because
the method is not defined using `ruby2_keywords`.

This adds the required `ruby2_keywords` declaration to
`DependencyLoader#new`, fixing the tests and getting Faraday v1.x
working with Ruby 3.2.0.

Fixes #1479.

Co-authored-by: Matt <iMacTia@users.noreply.github.com>
2023-01-18 14:58:13 +00:00
Mattia Giuffrida
d72db285e9 Fix rack version to '< 3' to avoid backwards incompatible change breaking specs 2023-01-18 13:46:45 +00:00
Matt
e7bbb4e750
Version bump to 1.10.2 2022-08-23 13:46:22 +01:00
hyuraku
2b1f33122d
Deprecate three methods for version 2.0 (#1446) 2022-08-22 14:42:14 +01:00
sampatbadhe
71dcb9cbc5 fix typo 2022-08-08 11:30:17 +02:00
Mattia Giuffrida
6e5403613a Version bump to 1.10.1 2022-08-08 08:20:09 +01:00
Mattia Giuffrida
eba6cd906a Exclude typhoeus adapter from Inclusivity/Race cop 2022-08-08 08:15:43 +01:00
hyuraku
84eb2538e0
Add Faraday#Deprecate to 1.x (#1438) 2022-08-08 08:10:39 +01:00
Matt
824423ccbb
Version bump to 1.10.0 2022-02-17 09:44:47 +00:00
Matt
4a838a58ce
Add JSON middleware (#1400) 2022-02-17 09:44:10 +00:00
Matt
47b3979850
Version bump to 1.9.3 2022-01-06 21:48:24 +00:00
Matt
ef0040fe4c
Re-add support for Ruby 2.4+ (#1371) 2022-01-06 21:45:11 +00:00
Matt
0ee48490b0
Version bump to 1.9.2 2022-01-06 17:50:53 +00:00
Matt
3f127bb10f
Add alias with legacy name to gemified middleware (#1372) 2022-01-06 17:50:29 +00:00
Matt
554930c6c5
Version bump to 1.9.1 2022-01-06 17:05:58 +00:00
Matt
c65d3be5fb
Update adapter dependencies in Gemspec (#1370)
Adapters v2 have hard dependencies, hence can't be used with Faraday v1
2022-01-06 17:05:29 +00:00
Mattia Giuffrida
aa17e5b3df Update CI branches 2022-01-06 16:07:21 +01:00
Mattia Giuffrida
537f5d7f2d Update CI with correct ruby versions 2022-01-06 16:07:21 +01:00
Mattia Giuffrida
4a8a504fe2 Require Ruby 2.6+ 2022-01-06 16:07:21 +01:00
Mattia Giuffrida
1d7c8ad2fc Use external multipart and retry middleware 2022-01-06 16:07:21 +01:00
Matt
80407137f1
Version bump to 1.8.1 2022-01-05 21:35:46 +00:00
Matt
3f0acb63b1
Remove version lock from faraday-net_http 2022-01-05 21:21:33 +00:00
Matt
d0efa09282
Version bump to 1.8.0 2021-09-18 09:20:47 +02:00
Matt
8cd1bf1190
Proc auth value backport cleanup (#1326) 2021-09-18 09:19:37 +02:00
Jarl Friis
2a5702a616
Backport authorization procs to 1.x branch (#1322) 2021-09-17 09:39:56 +02:00
Matt
d881f163c0
Version bump to 1.7.2 2021-09-13 17:41:35 +02:00
Matt
18ba248d14
Fix deprecation warning (#1323) 2021-09-13 15:45:18 +02:00
Matt
a84e6b4dba
Version bump to 1.7.1 2021-08-30 09:30:03 +01:00
Yutaka Kamei
8f32d7d59d
Respect the params_encoder in Faraday::Adapter::Test (#1316) 2021-08-30 09:25:24 +01:00
43 changed files with 897 additions and 1194 deletions

View File

@ -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

View File

@ -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
View File

@ -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/

View File

@ -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

View File

@ -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.

View File

@ -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'

View File

@ -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

View File

@ -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.

View File

@ -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
---

View 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}
```

View File

@ -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
---

View 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"}}
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
#

View File

@ -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

View File

@ -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

View File

@ -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.
#

View File

@ -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
View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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)

View File

@ -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

View File

@ -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

View File

@ -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

View 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)

View File

@ -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?

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true
module Faraday
VERSION = '1.7.0'
VERSION = '1.10.4'
end

View File

@ -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|

View 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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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) }

View 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

View File

@ -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