Skip to content

Commit

Permalink
Route unknown error to parent exception's handler
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Müller <straightshoota@gmail.com>
  • Loading branch information
syeopite and straight-shoota committed Sep 20, 2024
1 parent 9c64861 commit bf19e27
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
23 changes: 23 additions & 0 deletions spec/exception_handler_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,29 @@ describe "Kemal::ExceptionHandler" do
response.body.should eq "A custom exception of CustomExceptionType has occurred"
end

it "renders custom error for a child of a custom exception" do
error CustomExceptionType do |env, error|
"A custom exception of #{error.class} has occurred"
end

get "/" do
raise ChildCustomExceptionType.new
end

request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
Kemal::ExceptionHandler::INSTANCE.next = Kemal::RouteHandler::INSTANCE
Kemal::ExceptionHandler::INSTANCE.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)
response.status_code.should eq 500
response.headers["Content-Type"].should eq "text/html"
response.body.should eq "A custom exception of ChildCustomExceptionType has occurred"
end

it "overrides the content type for filters" do
before_get do |env|
env.response.content_type = "application/json"
Expand Down
3 changes: 3 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ end
class CustomExceptionType < Exception
end

class ChildCustomExceptionType < CustomExceptionType
end

add_context_storage_type(TestContextStorageType)
add_context_storage_type(AnotherContextStorageType)

Expand Down
24 changes: 21 additions & 3 deletions src/kemal/exception_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,38 @@ module Kemal
rescue ex : Exception
# Use error handler defined for the current exception if it exists
return call_exception_with_exception(context, ex, 500) if Kemal.config.error_handlers.has_key?(ex.class)

# Use error handler for an ancestor of the current exception if it exists
Kemal.config.error_handlers.each_key do |key|
if key.is_a? Exception.class && ex.class <= key
return call_exception_with_exception(context, ex, 500, override_handler_used: key)
end
end

log("Exception: #{ex.inspect_with_backtrace}")
# Else use generic 500 handler if defined
return call_exception_with_status_code(context, ex, 500) if Kemal.config.error_handlers.has_key?(500)
verbosity = Kemal.config.env == "production" ? false : true
render_500(context, ex, verbosity)
end

private def call_exception_with_exception(context : HTTP::Server::Context, exception : Exception, status_code : Int32 = 500)
# Calls the defined error handler for the given exception if it exists
#
# By default it tries to use the handler that is defined for the given exception. However, another
# handler can be used via the `override_handler_used` parameter.
private def call_exception_with_exception(context : HTTP::Server::Context, exception : Exception, status_code : Int32 = 500, override_handler_used : (Exception.class)? = nil)
return if context.response.closed?

if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(exception.class)
if !override_handler_used
handler_to_use = exception.class
else
handler_to_use = override_handler_used
end

if !Kemal.config.error_handlers.empty? && Kemal.config.error_handlers.has_key?(handler_to_use)
context.response.content_type = "text/html" unless context.response.headers.has_key?("Content-Type")
context.response.status_code = status_code
context.response.print Kemal.config.error_handlers[exception.class].call(context, exception)
context.response.print Kemal.config.error_handlers[handler_to_use].call(context, exception)
context
end
end
Expand Down

0 comments on commit bf19e27

Please sign in to comment.