Handle errors: allow to return Twirp::Error from handlers, and also raise Twitp::Error exceptions

This commit is contained in:
Mario Izquierdo 2018-02-20 23:33:12 -08:00
Родитель 5561ff4564
Коммит ff72884a74
3 изменённых файлов: 60 добавлений и 11 удалений

Просмотреть файл

@ -29,7 +29,12 @@ module Twirp
ERROR_CODES = ERROR_CODES_TO_HTTP_STATUS.keys
# Twirp::Error represents a valid error from a Twirp service
class Error
class Error < StandardError
# Wrap an arbitrary error as a Twirp :internal
def self.InternalWith(err)
self.new :internal, err.message, "cause" => err.class.name
end
# Initialize a Twirp::Error
# The code MUST be one of the valid ERROR_CODES Symbols (e.g. :internal, :not_found, :permission_denied ...).
@ -74,6 +79,10 @@ module Twirp
JSON.generate(as_json)
end
def to_s
"Twirp::Error code:#{code} msg:#{msg.inspect} meta:#{meta.inspect}"
end
private
def validate_code(code)
@ -99,7 +108,7 @@ module Twirp
def validate_meta_key_value(key, value)
if !key.is_a?(String) || !value.is_a?(String)
raise ArgumentError.new("Twirp::Error meta must be a Hash with String keys and values")
raise ArgumentError.new("Twirp::Error meta must be a Hash with String keys and values. Invalid key<#{key.class}>: #{key.inspect}, value<#{value.class}>: #{value.inspect}")
end
end

Просмотреть файл

@ -85,15 +85,23 @@ module Twirp
# Rack app handler.
def call(env)
req = Rack::Request.new(env)
rpc, content_type, bad_route = parse_rack_request(req)
if bad_route
return error_response(bad_route)
begin
req = Rack::Request.new(env)
rpc, content_type, bad_route = parse_rack_request(req)
if bad_route
return error_response(bad_route)
end
proto_req = decode_request(rpc[:request_class], content_type, req.body.read)
resp = @handler.send(rpc[:handler_method], proto_req)
return rack_response_from_handler(rpc, content_type, resp)
rescue Twirp::Error => twerr
error_response(twerr)
rescue StandardError => err
puts err.backtrace
error_response(Twirp::Error.InternalWith(err))
end
proto_req = decode_request(rpc[:request_class], content_type, req.body.read)
resp = @handler.send(rpc[:handler_method], proto_req)
return rack_response_from_handler(rpc, content_type, resp)
end
def path_prefix

Просмотреть файл

@ -133,7 +133,7 @@ class ServiceTest < Minitest::Test
status, headers, body = haberdasher_service.call(env)
assert_equal 404, status
assert_equal 'application/json', headers['Content-Type'] # error messages are always JSON, even for Protobuf requests
assert_equal 'application/json', headers['Content-Type'] # error responses are always JSON, even for Protobuf requests
assert_equal({
"code" => 'bad_route',
"msg" => 'Invalid route. Expected format: POST {BaseURL}/twirp/(package.)?{Service}/{Method}',
@ -180,6 +180,38 @@ class ServiceTest < Minitest::Test
assert_equal Example::Hat.new(inches: 0, color: ""), Example::Hat.decode(body[0])
end
# Handler should be able to return Twirp::Exception values, that will trigger error responses
def test_handler_returns_twirp_exception
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
return Twirp::Error.new(:invalid_argument, "I don't like that size")
end)
env = proto_req "/twirp/example.Haberdasher/MakeHat", Example::Size.new(inches: 666)
status, headers, body = svc.call(env)
assert_equal 400, status
assert_equal 'application/json', headers['Content-Type'] # error responses are always JSON, even for Protobuf requests
assert_equal({
"code" => 'invalid_argument',
"msg" => "I don't like that size",
}, JSON.parse(body[0]))
end
# Handler should be able to raise a Twirp::Exception, that will trigger error responses
def test_handler_raises_twirp_exception
svc = Example::Haberdasher.new(HaberdasherHandler.new do |size|
raise Twirp::Error.new(:invalid_argument, "I don't like that size")
end)
env = proto_req "/twirp/example.Haberdasher/MakeHat", Example::Size.new(inches: 666)
status, headers, body = svc.call(env)
assert_equal 400, status
assert_equal 'application/json', headers['Content-Type'] # error responses are always JSON, even for Protobuf requests
assert_equal({
"code" => 'invalid_argument',
"msg" => "I don't like that size",
}, JSON.parse(body[0]))
end
# Test Helpers
# ------------