Skip to content

Commit cf32578

Browse files
authored
Improve error handling logic and add missing test coverage (#1633)
1 parent e76e60d commit cf32578

File tree

2 files changed

+118
-15
lines changed

2 files changed

+118
-15
lines changed

lib/faraday/error.rb

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,26 +79,47 @@ def exc_msg_and_response!(exc, response = nil)
7979

8080
# Pulls out potential parent exception and response hash.
8181
def exc_msg_and_response(exc, response = nil)
82-
if exc.is_a?(Exception)
82+
case exc
83+
when Exception
8384
[exc, exc.message, response]
84-
elsif exc.is_a?(Hash) || exc.is_a?(Faraday::Env)
85-
http_status = exc.fetch(:status)
86-
87-
request = exc.fetch(:request, nil)
88-
89-
if request.nil?
90-
exception_message = "the server responded with status #{http_status} - method and url are not available " \
91-
'due to include_request: false on Faraday::Response::RaiseError middleware'
92-
else
93-
exception_message = "the server responded with status #{http_status} for #{request.fetch(:method).upcase} " \
94-
"#{request.fetch(:url)}"
95-
end
96-
97-
[nil, exception_message, exc]
85+
when Hash
86+
[nil, build_error_message_from_hash(exc), exc]
87+
when Faraday::Env
88+
[nil, build_error_message_from_env(exc), exc]
9889
else
9990
[nil, exc.to_s, response]
10091
end
10192
end
93+
94+
private
95+
96+
def build_error_message_from_hash(hash)
97+
# Be defensive with external Hash objects - they might be missing keys
98+
status = hash.fetch(:status, nil)
99+
request = hash.fetch(:request, nil)
100+
101+
return fallback_error_message(status) if request.nil?
102+
103+
method = request.fetch(:method, nil)
104+
url = request.fetch(:url, nil)
105+
build_status_error_message(status, method, url)
106+
end
107+
108+
def build_error_message_from_env(env)
109+
# Faraday::Env is internal - we can make reasonable assumptions about its structure
110+
build_status_error_message(env.status, env.method, env.url)
111+
end
112+
113+
def build_status_error_message(status, method, url)
114+
method_str = method ? method.to_s.upcase : ''
115+
url_str = url ? url.to_s : ''
116+
"the server responded with status #{status} for #{method_str} #{url_str}"
117+
end
118+
119+
def fallback_error_message(status)
120+
"the server responded with status #{status} - method and url are not available " \
121+
'due to include_request: false on Faraday::Response::RaiseError middleware'
122+
end
102123
end
103124

104125
# Faraday client error class. Represents 4xx status responses.

spec/faraday/error_spec.rb

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,87 @@
8989
it { expect(subject.response_headers).to eq(headers) }
9090
it { expect(subject.response_body).to eq(body) }
9191
end
92+
93+
context 'with hash missing status key' do
94+
let(:exception) { { body: 'error body' } }
95+
96+
it { expect(subject.wrapped_exception).to be_nil }
97+
it { expect(subject.response).to eq(exception) }
98+
it { expect(subject.message).to eq('the server responded with status - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
99+
end
100+
101+
context 'with hash with status but missing request data' do
102+
let(:exception) { { status: 404, body: 'not found' } } # missing request key
103+
104+
it { expect(subject.wrapped_exception).to be_nil }
105+
it { expect(subject.response).to eq(exception) }
106+
it { expect(subject.message).to eq('the server responded with status 404 - method and url are not available due to include_request: false on Faraday::Response::RaiseError middleware') }
107+
end
108+
109+
context 'with hash with status and request but missing method in request' do
110+
let(:exception) { { status: 404, body: 'not found', request: { url: 'http://example.com/test' } } } # missing method
111+
112+
it { expect(subject.wrapped_exception).to be_nil }
113+
it { expect(subject.response).to eq(exception) }
114+
it { expect(subject.message).to eq('the server responded with status 404 for http://example.com/test') }
115+
end
116+
117+
context 'with hash with status and request but missing url in request' do
118+
let(:exception) { { status: 404, body: 'not found', request: { method: :get } } } # missing url
119+
120+
it { expect(subject.wrapped_exception).to be_nil }
121+
it { expect(subject.response).to eq(exception) }
122+
it { expect(subject.message).to eq('the server responded with status 404 for GET ') }
123+
end
124+
125+
context 'with properly formed Faraday::Env' do
126+
# This represents the normal case - a well-formed Faraday::Env object
127+
# with all the standard properties populated as they would be during
128+
# a typical HTTP request/response cycle
129+
let(:exception) { Faraday::Env.new }
130+
131+
before do
132+
exception.status = 500
133+
exception.method = :post
134+
exception.url = URI('https://api.example.com/users')
135+
exception.request = Faraday::RequestOptions.new
136+
exception.response_headers = { 'content-type' => 'application/json' }
137+
exception.response_body = '{"error": "Internal server error"}'
138+
exception.request_headers = { 'authorization' => 'Bearer token123' }
139+
exception.request_body = '{"name": "John"}'
140+
end
141+
142+
it { expect(subject.wrapped_exception).to be_nil }
143+
it { expect(subject.response).to eq(exception) }
144+
it { expect(subject.message).to eq('the server responded with status 500 for POST https://api.example.com/users') }
145+
end
146+
147+
context 'with Faraday::Env missing status key' do
148+
let(:exception) { Faraday::Env.new }
149+
150+
before do
151+
exception[:body] = 'error body'
152+
# Intentionally not setting status
153+
end
154+
155+
it { expect(subject.wrapped_exception).to be_nil }
156+
it { expect(subject.response).to eq(exception) }
157+
it { expect(subject.message).to eq('the server responded with status for ') }
158+
end
159+
160+
context 'with Faraday::Env with direct method and url properties' do
161+
let(:exception) { Faraday::Env.new }
162+
163+
before do
164+
exception.status = 404
165+
exception.method = :get
166+
exception.url = URI('http://example.com/test')
167+
exception[:body] = 'not found'
168+
end
169+
170+
it { expect(subject.wrapped_exception).to be_nil }
171+
it { expect(subject.response).to eq(exception) }
172+
it { expect(subject.message).to eq('the server responded with status 404 for GET http://example.com/test') }
173+
end
92174
end
93175
end

0 commit comments

Comments
 (0)