diff --git a/lib/faraday/error.rb b/lib/faraday/error.rb index 551c15af4..39b0d582b 100644 --- a/lib/faraday/error.rb +++ b/lib/faraday/error.rb @@ -83,6 +83,14 @@ def initialize(exc = 'timeout', response = nil) end end + # Raised by Faraday::Response::RaiseError in case of a nil status in response. + class NilStatusError < ServerError + def initialize(_exc, response: nil) + message = 'http status could not be derived from the server response' + super(message, response) + end + end + # A unified error for failed connections. class ConnectionFailed < Error end diff --git a/lib/faraday/response/raise_error.rb b/lib/faraday/response/raise_error.rb index 2fbcde4e0..ef0f4f8d6 100644 --- a/lib/faraday/response/raise_error.rb +++ b/lib/faraday/response/raise_error.rb @@ -1,6 +1,6 @@ module Faraday class Response::RaiseError < Response::Middleware - ClientErrorStatuses = (400...600).freeze + ClientErrorStatuses = (400...500).freeze ServerErrorStatuses = (500...600).freeze def on_complete(env) @@ -25,6 +25,8 @@ def on_complete(env) raise Faraday::ClientError, response_values(env) when ServerErrorStatuses raise Faraday::ServerError, response_values(env) + when nil + raise Faraday::NilStatusError, response: response_values(env) end end diff --git a/spec/faraday/response/raise_error_spec.rb b/spec/faraday/response/raise_error_spec.rb new file mode 100644 index 000000000..fa635bd4e --- /dev/null +++ b/spec/faraday/response/raise_error_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +RSpec.describe Faraday::Response::RaiseError do + let(:conn) do + Faraday.new do |b| + b.response :raise_error + b.adapter :test do |stub| + stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, ''] } + 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('conflict') { [409, { '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('nil-status') { [nil, { 'X-Reason' => 'bailout' }, 'fail'] } + stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] } + end + end + end + + it 'raises no exception for 200 responses' do + expect { conn.get('ok') }.not_to raise_error + end + + it 'raises Faraday::BadRequestError 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 '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::ConflictError for 409 responses' do + expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex| + expect(ex.message).to eq('the server responded with status 409') + 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::NilStatusError for nil status in response' do + expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex| + expect(ex.message).to eq('http status could not be derived from the server response') + 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::ServerError 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 + end +end diff --git a/test/response_middleware_test.rb b/test/response_middleware_test.rb index 066b2dc9e..b336dc014 100644 --- a/test/response_middleware_test.rb +++ b/test/response_middleware_test.rb @@ -31,7 +31,7 @@ def test_raises_not_found end def test_raises_error - error = assert_raises Faraday::ClientError do + error = assert_raises Faraday::ServerError do @conn.get('error') end assert_equal 'the server responded with status 500', error.message