Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
21 changes: 19 additions & 2 deletions lib/vend/exception.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def initialize(headers)

class BadRequest < HttpError; end
class Unauthorized < HttpError; end
class PaymentRequired < HttpError; end
class Forbidden < HttpError; end
class NotFound < HttpError; end
class MethodNotAllowed < HttpError; end
Expand All @@ -25,6 +26,7 @@ module HttpErrors
ERRORS = {
400 => Vend::BadRequest,
401 => Vend::Unauthorized,
402 => Vend::PaymentRequired,
403 => Vend::Forbidden,
404 => Vend::NotFound,
405 => Vend::MethodNotAllowed,
Expand All @@ -40,19 +42,34 @@ module HttpErrors
}.freeze

def throw_http_exception!(code, env)
return unless ERRORS.keys.include? code
return if (200..299).include?(code.to_i)

response_headers = extract_headers_for_error(env)
error_class = error_class_by(code)

raise error_class.new(response_headers), env.body
end

def error_class_by(code)
ERRORS.fetch(code, ::Vend::HttpError)
end

def extract_headers_for_error(env)
response_headers = {}

unless env.body.empty?
response_headers = begin
Oj.load(env.body, symbol_keys: true)
rescue
{}
end
end

unless env[:response_headers] && env[:response_headers]['X-Retry-After'].nil?
response_headers[:retry_after] = env[:response_headers]['X-Retry-After'].to_i
end
raise ERRORS[code].new(response_headers), env.body

response_headers
end
end
end
78 changes: 62 additions & 16 deletions spec/vend/unit/exception_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
RSpec.describe Vend::HttpErrors do
let(:dummy_class) { Class.new { extend Vend::HttpErrors } }
let(:code) { 200 }
let(:env) { double }
let(:body) { {} }
let(:headers) { {} }
Expand All @@ -14,18 +13,73 @@
expect(Vend::HttpErrors::ERRORS).not_to be_nil
end

context 'invalid response status' do
context 'when we get a 404' do
context 'when received a successful response' do
it 'does not throw an exception' do
(200..299).each do |code|
expect { dummy_class.throw_http_exception!(code, env) }.not_to raise_exception
end
end
end

context 'when response code is 0' do
subject { dummy_class.throw_http_exception!(0, env) }

let(:body) { 'Request timeout' }

it 'throws the default exception' do
expect { subject }.to raise_exception(Vend::HttpError, body)
end
end

context 'when response code is not successful' do
context 'when response code is mapped to a known exception class' do
let(:unknown_error_codes) { [*100..199] + [*300..599] - Vend::HttpErrors::ERRORS.keys }

let(:body) { 'Something web wrong' }
Copy link

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

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

There's a typo in the error message. 'Something web wrong' should be 'Something went wrong'.

Suggested change
let(:body) { 'Something web wrong' }
let(:body) { 'Something went wrong' }

Copilot uses AI. Check for mistakes.

it 'throws the default exception' do
expect(unknown_error_codes.count).to be_positive

unknown_error_codes.each do |code|
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::HttpError, body)
end
end
end

context 'when received code 402' do
let(:code) { 402 }
let(:body) do
<<~HTML
<html>
<head><title>402 Payment Required</title></head>
<body>
<center><h1>402 Payment Required</h1></center>
<hr><center>openresty</center>
</body>
</html>
HTML
end

it 'throws a corresponding exception' do
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::PaymentRequired, body)
end
end

context 'when received code 404' do
let(:code) { 404 }

it 'should throw an exception' do
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::HttpErrors::ERRORS[code])
it 'throws a corresponding exception' do
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::NotFound)
end
end

it 'should have a valid error' do
Vend::HttpErrors::ERRORS.keys.each do |code|
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::HttpErrors::ERRORS[code])
context 'when received a known code' do
let(:body) { '{ "error": "something went wrong" }' }

it 'throws a corresponding exception class' do
Vend::HttpErrors::ERRORS.keys.each do |code|
expect { dummy_class.throw_http_exception!(code, env) }.to raise_exception(Vend::HttpErrors::ERRORS[code], body)
end
end
end

Expand Down Expand Up @@ -56,12 +110,4 @@
end
end
end

context 'valid response status' do
let(:code) { 200 }

it 'should not throw an exception' do
expect { dummy_class.throw_http_exception!(code, env) }.to_not raise_exception
end
end
end