mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Introduce ActionDispatch::DebugExceptions interceptors
Plugins interacting with the exceptions caught and displayed by ActionDispatch::DebugExceptions currently have to monkey patch it to get the much needed exception for their calculation. With DebugExceptions.register_interceptor, plugin authors can hook into DebugExceptions and process the exception, before being rendered. They can store it into the request and process it on the way back of the middleware chain execution or act on it straight in the interceptor. The interceptors can be play blocks, procs, lambdas or any object that responds to `#call`.
This commit is contained in:
parent
1694b02909
commit
d25fba89d4
3 changed files with 59 additions and 1 deletions
|
@ -1,3 +1,15 @@
|
|||
* Introduce ActionDispatch::DebugExceptions.register_interceptor
|
||||
|
||||
Exception aware plugin authors can use the newly introduced
|
||||
`.register_interceptor` method to get the processed exception, instead of
|
||||
monkey patching DebugExceptions.
|
||||
|
||||
ActionDispatch::DebugExceptions.register_interceptor do |request, exception|
|
||||
HypoteticalPlugin.capture_exception(request, exception)
|
||||
end
|
||||
|
||||
*Genadi Samokovarov*
|
||||
|
||||
* Output only one Content-Security-Policy nonce header value per request.
|
||||
|
||||
Fixes #32597.
|
||||
|
|
|
@ -50,10 +50,18 @@ module ActionDispatch
|
|||
end
|
||||
end
|
||||
|
||||
def initialize(app, routes_app = nil, response_format = :default)
|
||||
cattr_reader :interceptors, instance_accessor: false, default: []
|
||||
|
||||
def self.register_interceptor(object = nil, &block)
|
||||
interceptor = object || block
|
||||
interceptors << interceptor
|
||||
end
|
||||
|
||||
def initialize(app, routes_app = nil, response_format = :default, interceptors = self.class.interceptors)
|
||||
@app = app
|
||||
@routes_app = routes_app
|
||||
@response_format = response_format
|
||||
@interceptors = interceptors
|
||||
end
|
||||
|
||||
def call(env)
|
||||
|
@ -67,12 +75,26 @@ module ActionDispatch
|
|||
|
||||
response
|
||||
rescue Exception => exception
|
||||
invoke_interceptors(request, exception)
|
||||
raise exception unless request.show_exceptions?
|
||||
render_exception(request, exception)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invoke_interceptors(request, exception)
|
||||
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
||||
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
||||
|
||||
@interceptors.each do |interceptor|
|
||||
begin
|
||||
interceptor.call(request, exception)
|
||||
rescue Exception
|
||||
log_error(request, wrapper)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_exception(request, exception)
|
||||
backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
|
||||
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
require "abstract_unit"
|
||||
|
||||
class DebugExceptionsTest < ActionDispatch::IntegrationTest
|
||||
InterceptedErrorInstance = StandardError.new
|
||||
|
||||
class Boomer
|
||||
attr_accessor :closed
|
||||
|
||||
|
@ -36,6 +38,8 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
|
|||
raise RuntimeError
|
||||
when %r{/method_not_allowed}
|
||||
raise ActionController::MethodNotAllowed
|
||||
when %r{/intercepted_error}
|
||||
raise InterceptedErrorInstance
|
||||
when %r{/unknown_http_method}
|
||||
raise ActionController::UnknownHttpMethod
|
||||
when %r{/not_implemented}
|
||||
|
@ -76,9 +80,13 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
|
||||
Interceptor = proc { |request, exception| request.set_header("int", exception) }
|
||||
BadInterceptor = proc { |request, exception| raise "bad" }
|
||||
RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
|
||||
ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
|
||||
DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
|
||||
InterceptedApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :default, [Interceptor])
|
||||
BadInterceptedApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :default, [BadInterceptor])
|
||||
|
||||
test "skip diagnosis if not showing detailed exceptions" do
|
||||
@app = ProductionApp
|
||||
|
@ -499,4 +507,20 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "invoke interceptors before rendering" do
|
||||
@app = InterceptedApp
|
||||
get "/intercepted_error", headers: { "action_dispatch.show_exceptions" => true }
|
||||
|
||||
assert_equal InterceptedErrorInstance, request.get_header("int")
|
||||
end
|
||||
|
||||
test "bad interceptors doesn't debug exceptions" do
|
||||
@app = BadInterceptedApp
|
||||
|
||||
get "/puke", headers: { "action_dispatch.show_exceptions" => true }
|
||||
|
||||
assert_response 500
|
||||
assert_match(/puke/, body)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue