Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions lib/faraday/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,23 @@ def exc_msg_and_response!(exc, response = nil)

# Pulls out potential parent exception and response hash.
def exc_msg_and_response(exc, response = nil)
return [exc, exc.message, response] if exc.respond_to?(:backtrace)
if exc.is_a?(Exception)
[exc, exc.message, response]
elsif exc.is_a?(Hash)
http_status = exc.fetch(:status)
Comment on lines +82 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is perfectly fine, duck typing was OK back in the day, but I think people expect more of this nowadays with the advent of Sorbet and RBS


return [nil, "the server responded with status #{exc[:status]}", exc] \
if exc.respond_to?(:each_key)
request = exc.fetch(:request, nil)

[nil, exc.to_s, response]
exception_message = if request.nil?
"the server responded with status #{http_status} - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can overcome the Rubocop error by using Ruby's line continuation:

"the server responded with status #{http_status} - method and url are " \
'not available due to include_request: false on Faraday::Response::RaiseError middleware'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I can see why the limitation with the include_request: false option. Since that is NOT the default and the error message clearly explains what is going on, I'd say this is fine 👍

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just went on a bit of a wild goose chase because of this error message because the moneybird gem raises an exception descended from Faraday::Error but passes in a hash without request information. This lead to the "due to include_request: false" text appearing in my logs, even though I don't set that option anywhere.

Should the moneybird just not do that, or is there some way to make sure the error actually comes from Faraday::Response::RaiseError before emitting that message?

For reference: https://github.com/maartenvanvliet/moneybird/blob/master/lib/moneybird/middleware/error_handling.rb

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mvz

I'm not the architect of Faraday and I can see that this change I submitted last month has caused regressions for some people. 😢

I did not assume that anyone was subclassing the Faraday errors or using their own middleware to raise them. If you want to raise subclassed errors, I think you should include the request property in the hash you generate in your response_values method. Alternatively, maybe subclass from StandardError or RuntimeError instead of Faraday::Error.

Perhaps you don't need your own implementation of this middleware, considering that Faraday ships with the RaiseError middleware. However, this would break your consumers error handling if they are rescuing Moneybird specific exceptions, as those would now become Faraday errors.

Maybe @iMacTia can provide a better perspective.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nielsbuus. I'll open a ticket in the moneybird gem repo as well and see what their thoughts are.

else
"the server responded with status #{http_status} for #{request.fetch(:method).upcase} #{request.fetch(:url)}"
end

[nil, exception_message, exc]
else
[nil, exc.to_s, response]
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/faraday/error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

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.message).to eq('the server responded with status 400 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
if RUBY_VERSION >= '3.4'
it { expect(subject.inspect).to eq('#<Faraday::Error response={status: 400}>') }
else
Expand Down
20 changes: 10 additions & 10 deletions spec/faraday/response/raise_error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

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.message).to eq('the server responded with status 400 for GET http:/bad-request')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(400)
expect(ex.response_status).to eq(400)
Expand All @@ -39,7 +39,7 @@

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.message).to eq('the server responded with status 401 for GET http:/unauthorized')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(401)
expect(ex.response_status).to eq(401)
Expand All @@ -50,7 +50,7 @@

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.message).to eq('the server responded with status 403 for GET http:/forbidden')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(403)
expect(ex.response_status).to eq(403)
Expand All @@ -61,7 +61,7 @@

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.message).to eq('the server responded with status 404 for GET http:/not-found')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(404)
expect(ex.response_status).to eq(404)
Expand All @@ -83,7 +83,7 @@

it 'raises Faraday::RequestTimeoutError for 408 responses' do
expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
expect(ex.message).to eq('the server responded with status 408')
expect(ex.message).to eq('the server responded with status 408 for GET http:/request-timeout')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(408)
expect(ex.response_status).to eq(408)
Expand All @@ -94,7 +94,7 @@

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.message).to eq('the server responded with status 409 for GET http:/conflict')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(409)
expect(ex.response_status).to eq(409)
Expand All @@ -105,7 +105,7 @@

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.message).to eq('the server responded with status 422 for GET http:/unprocessable-entity')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(422)
expect(ex.response_status).to eq(422)
Expand All @@ -116,7 +116,7 @@

it 'raises Faraday::TooManyRequestsError for 429 responses' do
expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
expect(ex.message).to eq('the server responded with status 429')
expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(429)
expect(ex.response_status).to eq(429)
Expand All @@ -138,7 +138,7 @@

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.message).to eq('the server responded with status 499 for GET http:/4xx')
expect(ex.response[:headers]['X-Reason']).to eq('because')
expect(ex.response[:status]).to eq(499)
expect(ex.response_status).to eq(499)
Expand All @@ -149,7 +149,7 @@

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.message).to eq('the server responded with status 500 for GET http:/server-error')
expect(ex.response[:headers]['X-Error']).to eq('bailout')
expect(ex.response[:status]).to eq(500)
expect(ex.response_status).to eq(500)
Expand Down
Loading