diff --git a/.gitignore b/.gitignore index 9d0f30a4a..2fcd7fe12 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ docs/_build coverage +vendor/bundle diff --git a/lib/twilio-ruby/framework/rest/error.rb b/lib/twilio-ruby/framework/rest/error.rb index 05fb3769e..51bbe3a35 100644 --- a/lib/twilio-ruby/framework/rest/error.rb +++ b/lib/twilio-ruby/framework/rest/error.rb @@ -5,6 +5,9 @@ module REST class TwilioError < StandardError end + class NetworkError < TwilioError + end + class RestError < TwilioError attr_reader :message, :response, :code, :status_code, :details, :more_info, :error_message diff --git a/lib/twilio-ruby/http/http_client.rb b/lib/twilio-ruby/http/http_client.rb index 56a984c8e..ddbf1d768 100644 --- a/lib/twilio-ruby/http/http_client.rb +++ b/lib/twilio-ruby/http/http_client.rb @@ -76,7 +76,7 @@ def send(request) request.url, request.data) rescue Faraday::Error => e - raise Twilio::REST::TwilioError, e + raise Twilio::REST::NetworkError, e end def request(host, port, method, url, params = {}, data = {}, headers = {}, auth = nil, timeout = nil) diff --git a/spec/http/http_client_spec.rb b/spec/http/http_client_spec.rb index 422b62cd5..dbc8861ac 100644 --- a/spec/http/http_client_spec.rb +++ b/spec/http/http_client_spec.rb @@ -119,7 +119,7 @@ expect(Faraday).to receive(:new).and_return(Faraday::Connection.new) allow_any_instance_of(Faraday::Connection).to receive(:send).and_raise(Faraday::ConnectionFailed.new('BOOM')) - expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::TwilioError) + expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::NetworkError) expect(@client.last_response).to be_nil expect(@client.last_request).to_not be_nil expect(@client.last_request.host).to eq('host') @@ -146,7 +146,7 @@ it 'previous last_response should be cleared' do expect(Faraday).to receive(:new).and_return(Faraday::Connection.new) allow_any_instance_of(Faraday::Connection).to receive(:send).and_raise(Faraday::ConnectionFailed.new('BOOM')) - expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::TwilioError) + expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::NetworkError) expect(@client.last_response).to be_nil end @@ -166,4 +166,41 @@ end end end + + it 'should raise NetworkError for different types of Faraday errors' do + # Test TimeoutError + expect(Faraday).to receive(:new).and_return(Faraday::Connection.new) + allow_any_instance_of(Faraday::Connection).to receive(:send).and_raise(Faraday::TimeoutError.new('Timeout')) + expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::NetworkError) + end + + it 'should raise NetworkError for connection failures' do + # Test ConnectionFailed + expect(Faraday).to receive(:new).and_return(Faraday::Connection.new) + allow_any_instance_of(Faraday::Connection).to receive(:send).and_raise(Faraday::ConnectionFailed.new('DNS error')) + expect { @client.request('host', 'port', 'GET', 'url', nil, nil, {}, ['a', 'b']) }.to raise_exception(Twilio::REST::NetworkError) + end + + it 'should allow consumers to distinguish between network and API errors' do + begin + # This would normally make a real network call that fails + # but since we're mocking, we'll just test the error hierarchy + raise Twilio::REST::NetworkError, 'Network failure' + rescue Twilio::REST::NetworkError => e + # This should be caught as a retryable network error + expect(e).to be_a(Twilio::REST::NetworkError) + expect(e).to be_a(Twilio::REST::TwilioError) + end + + begin + # This represents an API error response + response = double('response', status_code: 400, body: { 'code' => 20_003, 'message' => 'Invalid parameter' }) + raise Twilio::REST::RestError.new('Authentication failed', response) + rescue Twilio::REST::RestError => e + # This should be caught as a non-retryable API error + expect(e).to be_a(Twilio::REST::RestError) + expect(e).to be_a(Twilio::REST::TwilioError) + expect(e).not_to be_a(Twilio::REST::NetworkError) + end + end end