Add new error classes (#841)

* Removes sub-class constants definition from `Faraday::Error`. A sub-class (e.g. `ClientError`) was previously accessible
either through the `Faraday` module (e.g. `Faraday::ClientError`) or through the `Faraday::Error` class (e.g. `Faraday::Error::ClientError`).
The latter is no longer available and the former should be used instead, so check your `rescue`s.
* Introduces a new `Faraday::ServerError` (5xx status codes) alongside the existing `Faraday::ClientError` (4xx status codes).
Please note `Faraday::ClientError` was previously used for both.
* Introduces new Errors that describe the most common REST status codes:
  * Faraday::BadRequestError (400)
  * Faraday::UnauthorizedError (401)
  * Faraday::ForbiddenError (403)
  * Faraday::ProxyAuthError (407). Please note this raised a `Faraday::ConnectionFailed` before.
  * Faraday::UnprocessableEntityError (422)
This commit is contained in:
Mattia 2019-01-04 17:24:38 +00:00 committed by GitHub
parent 0e07aaee76
commit 793e6ba9c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 203 additions and 112 deletions

View File

@ -1,5 +1,19 @@
## Faraday 1.0
### Errors
* Removes sub-class constants definition from `Faraday::Error`. A sub-class (e.g. `ClientError`) was previously accessible
either through the `Faraday` module (e.g. `Faraday::ClientError`) or through the `Faraday::Error` class (e.g. `Faraday::Error::ClientError`).
The latter is no longer available and the former should be used instead, so check your `rescue`s.
* Introduces a new `Faraday::ServerError` (5xx status codes) alongside the existing `Faraday::ClientError` (4xx status codes).
Please note `Faraday::ClientError` was previously used for both.
* Introduces new Errors that describe the most common REST status codes:
* Faraday::BadRequestError (400)
* Faraday::UnauthorizedError (401)
* Faraday::ForbiddenError (403)
* Faraday::ProxyAuthError (407). Please note this raised a `Faraday::ConnectionFailed` before.
* Faraday::UnprocessableEntityError (422)
### Others
* Dropped support for jruby and Rubinius.
* Officially supports Ruby 2.3+
* In order to specify the adapter you now MUST use the `#adapter` method on the connection builder. If you don't do so and your adapter inherits from `Faraday::Adapter` then Faraday will raise an exception. Otherwise, Faraday will automatically push the default adapter at the end of the stack causing your request to be executed twice.
@ -27,3 +41,4 @@ conn = Faraday.new(...) do |f|
f.adapter AnyAdapter
end
```

View File

@ -131,9 +131,9 @@ module Faraday
end
rescue EventMachine::Connectify::CONNECTError => err
if err.message.include?("Proxy Authentication Required")
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "}
else
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
end
rescue => err
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
@ -170,15 +170,15 @@ module Faraday
end
def raise_error(msg)
errklass = Faraday::Error::ClientError
errklass = Faraday::ClientError
if msg == Errno::ETIMEDOUT
errklass = Faraday::Error::TimeoutError
errklass = Faraday::TimeoutError
msg = "request timed out"
elsif msg == Errno::ECONNREFUSED
errklass = Faraday::Error::ConnectionFailed
errklass = Faraday::ConnectionFailed
msg = "connection refused"
elsif msg == "connection closed by server"
errklass = Faraday::Error::ConnectionFailed
errklass = Faraday::ConnectionFailed
end
raise errklass, msg
end
@ -226,7 +226,7 @@ module Faraday
end
end
if @errors.size > 0
raise Faraday::Error::ClientError, @errors.first || "connection failed"
raise Faraday::ClientError, @errors.first || "connection failed"
end
end
ensure

View File

@ -76,18 +76,18 @@ module Faraday
@app.call env
rescue Errno::ECONNREFUSED
raise Error::ConnectionFailed, $!
raise Faraday::ConnectionFailed, $!
rescue EventMachine::Connectify::CONNECTError => err
if err.message.include?("Proxy Authentication Required")
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "}
else
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
end
rescue Errno::ETIMEDOUT => err
raise Error::TimeoutError, err
raise Faraday::TimeoutError, err
rescue RuntimeError => err
if err.message == "connection closed by server"
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
else
raise
end

View File

@ -64,14 +64,14 @@ module Faraday
@app.call env
rescue ::Excon::Errors::SocketError => err
if err.message =~ /\btimeout\b/
raise Error::TimeoutError, err
raise Faraday::TimeoutError, err
elsif err.message =~ /\bcertificate\b/
raise Faraday::SSLError, err
else
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
end
rescue ::Excon::Errors::Timeout => err
raise Error::TimeoutError, err
raise Faraday::TimeoutError, err
end
# @return [Excon]

View File

@ -49,15 +49,15 @@ module Faraday
@app.call env
rescue ::HTTPClient::TimeoutError, Errno::ETIMEDOUT
raise Faraday::Error::TimeoutError, $!
raise Faraday::TimeoutError, $!
rescue ::HTTPClient::BadResponseError => err
if err.message.include?('status 407')
raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "}
else
raise Faraday::Error::ClientError, $!
raise Faraday::ClientError, $!
end
rescue Errno::ECONNREFUSED, IOError, SocketError
raise Faraday::Error::ConnectionFailed, $!
raise Faraday::ConnectionFailed, $!
rescue => err
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
raise Faraday::SSLError, err

View File

@ -46,7 +46,7 @@ module Faraday
if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
raise Faraday::SSLError, err
else
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
end
end
@ -59,7 +59,7 @@ module Faraday
@app.call env
rescue Timeout::Error, Errno::ETIMEDOUT => err
raise Faraday::Error::TimeoutError, err
raise Faraday::TimeoutError, err
end
private

View File

@ -38,12 +38,12 @@ module Faraday
def perform_request(http, env)
http.request env[:url], create_request(env)
rescue Errno::ETIMEDOUT => error
raise Faraday::Error::TimeoutError, error
raise Faraday::TimeoutError, error
rescue Net::HTTP::Persistent::Error => error
if error.message.include? 'Timeout'
raise Faraday::Error::TimeoutError, error
raise Faraday::TimeoutError, error
elsif error.message.include? 'connection refused'
raise Faraday::Error::ConnectionFailed, error
raise Faraday::ConnectionFailed, error
else
raise
end

View File

@ -29,7 +29,7 @@ module Faraday
data = env[:body] ? env[:body].to_s : nil
session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
raise Error::ConnectionFailed, $!
raise Faraday::ConnectionFailed, $!
end
if (req = env[:request]).stream_response?
@ -44,15 +44,15 @@ module Faraday
@app.call env
rescue ::Patron::TimeoutError => err
if connection_timed_out_message?(err.message)
raise Faraday::Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
else
raise Faraday::Error::TimeoutError, err
raise Faraday::TimeoutError, err
end
rescue ::Patron::Error => err
if err.message.include?("code 407")
raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "}
else
raise Error::ConnectionFailed, err
raise Faraday::ConnectionFailed, err
end
end

View File

@ -41,7 +41,7 @@ module Faraday
timeout = env[:request][:timeout] || env[:request][:open_timeout]
response = if timeout
Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
Timer.timeout(timeout, Faraday::TimeoutError) { execute_request(env, rack_env) }
else
execute_request(env, rack_env)
end

View File

@ -1,10 +1,6 @@
module Faraday
# Faraday error base class.
class Error < StandardError;
end
# Faraday client error class.
class ClientError < Error
class Error < StandardError
attr_reader :response, :wrapped_exception
def initialize(ex, response = nil)
@ -45,39 +41,60 @@ module Faraday
end
end
# A unified client error for failed connections.
class ConnectionFailed < ClientError;
# Faraday client error class. Represents 4xx status responses.
class ClientError < Error
end
# A 404 error used in the RaiseError middleware
#
# @see Faraday::Response::RaiseError
class ResourceNotFound < ClientError;
# Raised by Faraday::Response::RaiseError in case of a 400 response.
class BadRequestError < ClientError
end
# Raised by FaradayMiddleware::ResponseMiddleware
class ParsingError < ClientError;
# Raised by Faraday::Response::RaiseError in case of a 401 response.
class UnauthorizedError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 403 response.
class ForbiddenError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 404 response.
class ResourceNotFound < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 407 response.
class ProxyAuthError < ClientError
end
# Raised by Faraday::Response::RaiseError in case of a 422 response.
class UnprocessableEntityError < ClientError
end
# Faraday server error class. Represents 5xx status responses.
class ServerError < Error
end
# A unified client error for timeouts.
class TimeoutError < ClientError
def initialize(ex = nil)
super(ex || "timeout")
class TimeoutError < ServerError
def initialize(ex = 'timeout', response = nil)
super(ex, response)
end
end
# A unified error for failed connections.
class ConnectionFailed < Error
end
# A unified client error for SSL errors.
class SSLError < ClientError
class SSLError < Error
end
# Raised by FaradayMiddleware::ResponseMiddleware
class ParsingError < Error
end
# Exception used to control the Retry middleware.
#
# @see Faraday::Request::Retry
class RetriableResponse < ClientError;
end
[:ClientError, :ConnectionFailed, :ResourceNotFound,
:ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
Error.const_set(const, Faraday.const_get(const))
class RetriableResponse < Error
end
end

View File

@ -21,7 +21,7 @@ module Faraday
# interval that is random between 0.1 and 0.15.
class Request::Retry < Faraday::Middleware
DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error', Error::TimeoutError, Faraday::Error::RetriableResponse].freeze
DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error', Faraday::TimeoutError, Faraday::RetriableResponse].freeze
IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
@ -90,7 +90,7 @@ module Faraday
# @option options [Integer] :backoff_factor (1) The amount to multiple each successive retry's
# interval amount by in order to provide backoff
# @option options [Array] :exceptions ([Errno::ETIMEDOUT, 'Timeout::Error',
# Error::TimeoutError, Faraday::Error::RetriableResponse]) The list of
# 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
@ -124,7 +124,7 @@ module Faraday
begin
env[:body] = request_body # after failure env[:body] is set to the response body
@app.call(env).tap do |resp|
raise Faraday::Error::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
raise Faraday::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
end
rescue @errmatch => exception
if retries > 0 && retry_request?(env, exception)
@ -137,7 +137,7 @@ module Faraday
end
end
if exception.is_a?(Faraday::Error::RetriableResponse)
if exception.is_a?(Faraday::RetriableResponse)
exception.response
else
raise

View File

@ -1,16 +1,27 @@
module Faraday
class Response::RaiseError < Response::Middleware
ClientErrorStatuses = 400...600
ClientErrorStatuses = 400...500
ServerErrorStatuses = 500...600
def on_complete(env)
case env[:status]
when 400
raise Faraday::BadRequestError, response_values(env)
when 401
raise Faraday::UnauthorizedError, response_values(env)
when 403
raise Faraday::ForbiddenError, response_values(env)
when 404
raise Faraday::Error::ResourceNotFound, response_values(env)
raise Faraday::ResourceNotFound, response_values(env)
when 407
# mimic the behavior that we get with proxy requests with HTTPS
raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
raise Faraday::ProxyAuthError.new(%{407 "Proxy Authentication Required"}, response_values(env))
when 422
raise Faraday::UnprocessableEntityError, response_values(env)
when ClientErrorStatuses
raise Faraday::Error::ClientError, response_values(env)
raise Faraday::ClientError, response_values(env)
when ServerErrorStatuses
raise Faraday::ServerError, response_values(env)
end
end

View File

@ -1,5 +1,5 @@
RSpec.describe Faraday::Adapter::Typhoeus do
features :body_on_get, :parallel
it_behaves_like 'an adapter'
it_behaves_like 'an adapter', skip: true
end

View File

@ -1,45 +1,43 @@
RSpec.describe Faraday::Error do
describe Faraday::Error::ClientError do
describe '.initialize' do
subject { described_class.new(exception, response) }
let(:response) { nil }
RSpec.describe Faraday::ClientError do
describe '.initialize' do
subject { described_class.new(exception, response) }
let(:response) { nil }
context 'with exception only' do
let(:exception) { RuntimeError.new('test') }
context 'with exception only' do
let(:exception) { RuntimeError.new('test') }
it { expect(subject.wrapped_exception).to eq(exception) }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq(exception.message) }
it { expect(subject.backtrace).to eq(exception.backtrace) }
it { expect(subject.inspect).to eq('#<Faraday::ClientError wrapped=#<RuntimeError: test>>') }
end
it { expect(subject.wrapped_exception).to eq(exception) }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq(exception.message) }
it { expect(subject.backtrace).to eq(exception.backtrace) }
it { expect(subject.inspect).to eq('#<Faraday::ClientError wrapped=#<RuntimeError: test>>') }
end
context 'with response hash' do
let(:exception) { { status: 400 } }
context 'with response hash' do
let(:exception) { { status: 400 } }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 400') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to eq(exception) }
it { expect(subject.message).to eq('the server responded with status 400') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError response={:status=>400}>') }
end
context 'with string' do
let(:exception) { 'custom message' }
context 'with string' do
let(:exception) { 'custom message' }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('custom message') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: custom message>>') }
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('custom message') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: custom message>>') }
end
context 'with anything else #to_s' do
let(:exception) { %w(error1 error2) }
context 'with anything else #to_s' do
let(:exception) { %w(error1 error2) }
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('["error1", "error2"]') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: ["error1", "error2"]>>') }
end
it { expect(subject.wrapped_exception).to be_nil }
it { expect(subject.response).to be_nil }
it { expect(subject.message).to eq('["error1", "error2"]') }
it { expect(subject.inspect).to eq('#<Faraday::ClientError #<Faraday::ClientError: ["error1", "error2"]>>') }
end
end
end

View File

@ -3,26 +3,74 @@ RSpec.describe Faraday::Response::RaiseError do
Faraday.new do |b|
b.response :raise_error
b.adapter :test do |stub|
stub.get('ok') { [200, {'Content-Type' => 'text/html'}, '<body></body>'] }
stub.get('not-found') { [404, {'X-Reason' => 'because'}, 'keep looking'] }
stub.get('error') { [500, {'X-Error' => 'bailout'}, 'fail'] }
stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('unauthorized') { [401, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
end
end
end
it 'raises no exceptio for 200 responses' do
it 'raises no exception for 200 responses' do
expect { conn.get('ok') }.not_to raise_error
end
it 'raise Faraday::Error::ResourceNotFound for 404 responses' do
expect { conn.get('not-found') }.to raise_error(Faraday::Error::ResourceNotFound) do |ex|
it 'raises Faraday::ResourceNotFound for 400 responses' do
expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
expect(ex.message).to eq('the server responded with status 400')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::UnauthorizedError for 401 responses' do
expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
expect(ex.message).to eq('the server responded with status 401')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::ForbiddenError for 403 responses' do
expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
expect(ex.message).to eq('the server responded with status 403')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::ResourceNotFound for 404 responses' do
expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
expect(ex.message).to eq('the server responded with status 404')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raise Faraday::Error::ClientError for 500 responses' do
expect { conn.get('error') }.to raise_error(Faraday::Error::ClientError) do |ex|
it 'raises Faraday::ProxyAuthError for 407 responses' do
expect { conn.get('proxy-error') }.to raise_error(Faraday::ProxyAuthError) do |ex|
expect(ex.message).to eq('407 "Proxy Authentication Required"')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::UnprocessableEntityError for 422 responses' do
expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
expect(ex.message).to eq('the server responded with status 422')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::ClientError for other 4xx responses' do
expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
expect(ex.message).to eq('the server responded with status 499')
expect(ex.response[:headers]['X-Reason']).to eq('because')
end
end
it 'raises Faraday::ClientError for 500 responses' do
expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
expect(ex.message).to eq('the server responded with status 500')
expect(ex.response[:headers]['X-Error']).to eq('bailout')
end

View File

@ -1,4 +1,6 @@
shared_examples 'an adapter' do |**options|
before { skip } if options[:skip]
context 'with SSL enabled' do
before { ENV['SSL'] = 'yes' }
include_examples 'adapter examples', options

View File

@ -30,7 +30,7 @@ shared_examples 'a request method' do |http_method|
it 'handles connection error' do
request_stub.disable
expect { conn.public_send(http_method, 'http://localhost:4') }.to raise_error(Faraday::Error::ConnectionFailed)
expect { conn.public_send(http_method, 'http://localhost:4') }.to raise_error(Faraday::ConnectionFailed)
end
# context 'when wrong ssl certificate is provided' do
@ -61,16 +61,16 @@ shared_examples 'a request method' do |http_method|
it 'supports timeout option' do
conn_options[:request] = { timeout: 1 }
request_stub.to_timeout
exc = adapter == 'NetHttp' ? Faraday::Error::ConnectionFailed : Faraday::TimeoutError
exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
expect { conn.public_send(http_method, '/') }.to raise_error(exc)
end
# TODO: This needs reimplementation: see https://github.com/lostisland/faraday/issues/718
# Should raise Faraday::Error::ConnectionFailed
# Should raise Faraday::ConnectionFailed
it 'supports open_timeout option' do
conn_options[:request] = { open_timeout: 1 }
request_stub.to_timeout
exc = adapter == 'NetHttp' ? Faraday::Error::ConnectionFailed : Faraday::TimeoutError
exc = adapter == 'NetHttp' ? Faraday::ConnectionFailed : Faraday::TimeoutError
expect { conn.public_send(http_method, '/') }.to raise_error(exc)
end
@ -159,8 +159,8 @@ shared_examples 'a request method' do |http_method|
end
expect(conn.in_parallel?).to be_falsey
expect(resp1.body).to eq(payload1.to_json)
expect(resp2.body).to eq(payload2.to_json)
expect(resp1&.body).to eq(payload1.to_json)
expect(resp2&.body).to eq(payload2.to_json)
end
end
@ -176,6 +176,6 @@ shared_examples 'a request method' do |http_method|
conn_options[:proxy] = 'http://google.co.uk'
request_stub.to_return(status: 407)
expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::Error::ConnectionFailed)
expect { conn.public_send(http_method, '/') }.to raise_error(Faraday::ProxyAuthError)
end
end

View File

@ -27,7 +27,7 @@ module Adapters
conn = create_connection(:request => {:timeout => 1, :open_timeout => 1})
begin
conn.get '/slow'
rescue Faraday::Error::ClientError
rescue Faraday::ClientError
end
end