rescue_registry 0.1.0 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -1
- data/lib/rescue_registry.rb +26 -2
- data/lib/rescue_registry/action_dispatch.rb +5 -69
- data/lib/rescue_registry/action_dispatch/debug_exceptions.rb +43 -0
- data/lib/rescue_registry/action_dispatch/exception_wrapper.rb +9 -0
- data/lib/rescue_registry/action_dispatch/show_exceptions.rb +17 -0
- data/lib/rescue_registry/context.rb +37 -0
- data/lib/rescue_registry/controller.rb +1 -22
- data/lib/rescue_registry/exception_handler.rb +19 -12
- data/lib/rescue_registry/exceptions_app.rb +15 -17
- data/lib/rescue_registry/rails_test_helpers.rb +42 -0
- data/lib/rescue_registry/railtie.rb +6 -2
- data/lib/rescue_registry/registry.rb +31 -12
- data/lib/rescue_registry/reset_context.rb +14 -0
- data/lib/rescue_registry/show_exceptions.rb +59 -0
- data/lib/rescue_registry/version.rb +1 -1
- metadata +20 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '059708756e6a55e037d23bea568cfe2d58818204d21da641b15a824eeed8aeb6'
|
4
|
+
data.tar.gz: 72f2989656beee1dd63caff80fde2808d9ad82aa36e2d5f48c9ada3eda529fab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c40c4d7c189ed217216eca7f37ab81d3fc7bf9ae906a868e0e676f11e7384f67125e8d305bf55db75284ec5b37d70355719dbfee4cd2b72f009229238bcd154
|
7
|
+
data.tar.gz: de9fb10f2fd414e44b7a50be904505bb42889088ab71d76ec34f004501b297996018cd398629c6f78cea17f9fbfad77f79a530be4e0b2ee8c8b93fb2c0787888
|
data/CHANGELOG.md
CHANGED
@@ -7,4 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
---
|
9
9
|
|
10
|
-
|
10
|
+
## [0.2.3] - 2020-09-01
|
11
|
+
### Fixed
|
12
|
+
- Properly handle exceptions for invalid format types - #25 (thanks @shanet)
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
- Lower minimum Ruby requirement to 2.3 (thanks @elDub)
|
16
|
+
|
17
|
+
## [0.2.2] - 2019-06-13
|
18
|
+
### Changed
|
19
|
+
- Lower minimum Ruby requirement to 2.3 (thanks @elDub)
|
20
|
+
|
21
|
+
## [0.2.1] - 2019-05-22
|
22
|
+
### Added
|
23
|
+
- RescueRegistry::RailsTestingHelpers provides some helpers for easier testing in Rails applications.
|
24
|
+
|
25
|
+
## [0.2.0] - 2019-05-21
|
26
|
+
### Added
|
27
|
+
- Support for non-Rails applications.
|
28
|
+
### Changed
|
29
|
+
- Passthrough statuses are currently only support for Rails applications.
|
30
|
+
- Added a new Rails middleware for handling the current context.
|
31
|
+
### Fixed
|
32
|
+
- Registry is now properly inherited so that new registrations in subclasses do not affect the parent.
|
33
|
+
- Default exception handler now works property in the root context.
|
34
|
+
|
35
|
+
## [0.1.0] - 2019-05-15
|
36
|
+
### Added
|
37
|
+
- Everything, it's the first release!
|
data/lib/rescue_registry.rb
CHANGED
@@ -3,11 +3,16 @@
|
|
3
3
|
require 'active_support'
|
4
4
|
|
5
5
|
module RescueRegistry
|
6
|
+
autoload :ActionDispatch, "rescue_registry/action_dispatch"
|
7
|
+
autoload :Context, "rescue_registry/context"
|
6
8
|
autoload :Controller, "rescue_registry/controller"
|
7
9
|
autoload :ExceptionsApp, "rescue_registry/exceptions_app"
|
8
10
|
autoload :ExceptionHandler, "rescue_registry/exception_handler"
|
9
11
|
autoload :RailsExceptionHandler, "rescue_registry/exception_handler"
|
12
|
+
autoload :RailsTestHelpers, "rescue_registry/rails_test_helpers"
|
10
13
|
autoload :Registry, "rescue_registry/registry"
|
14
|
+
autoload :ResetContext, "rescue_registry/reset_context"
|
15
|
+
autoload :ShowExceptions, "rescue_registry/show_exceptions"
|
11
16
|
|
12
17
|
class HandlerNotFound < StandardError; end
|
13
18
|
|
@@ -41,11 +46,30 @@ module RescueRegistry
|
|
41
46
|
context.rescue_registry.public_send(method, *args)
|
42
47
|
end
|
43
48
|
end
|
49
|
+
|
50
|
+
# the Module#ruby2_keywords is added starting at ruby 2.7. It lets us mark
|
51
|
+
# these methods as using ruby2-style keyword argument semantics. This will
|
52
|
+
# keep them working correctly on ruby3, and clears a deprecation that
|
53
|
+
# otherwise fires in 2.7+.
|
54
|
+
if respond_to?(:ruby2_keywords, true)
|
55
|
+
class << self
|
56
|
+
REGISTRY_METHODS.each do |method|
|
57
|
+
ruby2_keywords(method)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveSupport.on_load(:before_initialize) do
|
64
|
+
ActionDispatch::ExceptionWrapper.singleton_class.prepend RescueRegistry::ActionDispatch::ExceptionWrapper
|
65
|
+
ActionDispatch::DebugExceptions.prepend RescueRegistry::ActionDispatch::DebugExceptions
|
66
|
+
ActionDispatch::ShowExceptions.prepend RescueRegistry::ActionDispatch::ShowExceptions
|
44
67
|
end
|
45
68
|
|
46
69
|
ActiveSupport.on_load(:action_controller) do
|
47
70
|
include RescueRegistry::Controller
|
48
71
|
end
|
49
72
|
|
50
|
-
|
51
|
-
require 'rescue_registry/railtie'
|
73
|
+
if defined?(Rails)
|
74
|
+
require 'rescue_registry/railtie'
|
75
|
+
end
|
@@ -1,71 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
def status_code_for_exception(class_name)
|
7
|
-
RescueRegistry.status_code_for_exception(class_name) ||
|
8
|
-
status_code_for_exception_without_rescue_registry(class_name)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
# Since this is for debugging only it's less critical to make changes here. The main area that matters is
|
14
|
-
# returning the correct status code. Since all this code relies upon the ExceptionWrapper which we've monkeypatched,
|
15
|
-
# it should work correctly without changes. However, we can provide more details in some areas so we hook in for that.
|
16
|
-
ActionDispatch::DebugExceptions.class_eval do
|
17
|
-
private
|
18
|
-
|
19
|
-
# `#log_error`
|
20
|
-
# TODO: We may be able to add more information, though the details remain to be determined
|
21
|
-
|
22
|
-
# `#render_for_browser_request`
|
23
|
-
# TODO: We may be able to add more information, though the details remain to be determined
|
24
|
-
|
25
|
-
# This would work without changes, but the formatting would be incorrect. Since it's for debugging only,
|
26
|
-
# we could choose to ignore it, but the detailed information would definitely be useful.
|
27
|
-
alias_method :render_for_api_request_without_rescue_registry, :render_for_api_request
|
28
|
-
def render_for_api_request(content_type, wrapper)
|
29
|
-
response = nil
|
30
|
-
|
31
|
-
if RescueRegistry.handles_exception?(wrapper.exception)
|
32
|
-
# Ideally `render_for_api_request` would be split up so we could avoid some duplication in RescueRegistry
|
33
|
-
begin
|
34
|
-
response = RescueRegistry.response_for_debugging(content_type, wrapper.exception, traces: wrapper.traces)
|
35
|
-
rescue Exception => e
|
36
|
-
# Replace the original exception (still available via `cause`) and let it get handled with default handlers
|
37
|
-
wrapper = ActionDispatch::ExceptionWrapper.new(wrapper.backtrace_cleaner, e)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if response
|
42
|
-
render(*response)
|
43
|
-
else
|
44
|
-
# One of the following is true:
|
45
|
-
# - No handler for the exception
|
46
|
-
# - No response for content_type
|
47
|
-
# - An exception while generating the response
|
48
|
-
# In any case, we go with the default here.
|
49
|
-
render_for_api_request_without_rescue_registry(content_type, wrapper)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
ActionDispatch::ShowExceptions.class_eval do
|
55
|
-
# @private
|
56
|
-
alias_method :initialize_without_rescue_registry, :initialize
|
57
|
-
|
58
|
-
# @private
|
59
|
-
def initialize(*args)
|
60
|
-
initialize_without_rescue_registry(*args)
|
61
|
-
@exceptions_app = RescueRegistry::ExceptionsApp.new(@exceptions_app)
|
62
|
-
end
|
63
|
-
|
64
|
-
alias_method :call_without_rescue_registry, :call
|
65
|
-
def call(*args)
|
66
|
-
warn "Didn't expect RescueRegistry context to be set in middleware" if RescueRegistry.context
|
67
|
-
call_without_rescue_registry(*args)
|
68
|
-
ensure
|
69
|
-
RescueRegistry.context = nil
|
1
|
+
module RescueRegistry
|
2
|
+
module ActionDispatch
|
3
|
+
autoload :DebugExceptions, "rescue_registry/action_dispatch/debug_exceptions"
|
4
|
+
autoload :ExceptionWrapper, "rescue_registry/action_dispatch/exception_wrapper"
|
5
|
+
autoload :ShowExceptions, "rescue_registry/action_dispatch/show_exceptions"
|
70
6
|
end
|
71
7
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RescueRegistry
|
2
|
+
module ActionDispatch
|
3
|
+
# Since this is for debugging only it's less critical to make changes here. The main area that matters is
|
4
|
+
# returning the correct status code. Since all this code relies upon the ExceptionWrapper which we've monkeypatched,
|
5
|
+
# it should work correctly without changes. However, we can provide more details in some areas so we hook in for that.
|
6
|
+
module DebugExceptions
|
7
|
+
private
|
8
|
+
|
9
|
+
# `#log_error`
|
10
|
+
# TODO: We may be able to add more information, though the details remain to be determined
|
11
|
+
|
12
|
+
# `#render_for_browser_request`
|
13
|
+
# TODO: We may be able to add more information, though the details remain to be determined
|
14
|
+
|
15
|
+
# This would work without changes, but the formatting would be incorrect. Since it's for debugging only,
|
16
|
+
# we could choose to ignore it, but the detailed information would definitely be useful.
|
17
|
+
def render_for_api_request(content_type, wrapper)
|
18
|
+
response = nil
|
19
|
+
|
20
|
+
if RescueRegistry.handles_exception?(wrapper.exception)
|
21
|
+
# Ideally `render_for_api_request` would be split up so we could avoid some duplication in RescueRegistry
|
22
|
+
begin
|
23
|
+
response = RescueRegistry.response_for_debugging(content_type, wrapper.exception, traces: wrapper.traces)
|
24
|
+
rescue Exception => e
|
25
|
+
# Replace the original exception (still available via `cause`) and let it get handled with default handlers
|
26
|
+
wrapper = ActionDispatch::ExceptionWrapper.new(wrapper.backtrace_cleaner, e)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if response
|
31
|
+
render(*response)
|
32
|
+
else
|
33
|
+
# One of the following is true:
|
34
|
+
# - No handler for the exception
|
35
|
+
# - No response for content_type
|
36
|
+
# - An exception while generating the response
|
37
|
+
# In any case, we go with the default here.
|
38
|
+
super(content_type, wrapper)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RescueRegistry
|
2
|
+
module ActionDispatch
|
3
|
+
module ShowExceptions
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
@exceptions_app = RescueRegistry::ExceptionsApp.new(@exceptions_app)
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(*args)
|
10
|
+
warn "Didn't expect RescueRegistry context to be set in middleware" if RescueRegistry.context
|
11
|
+
super
|
12
|
+
ensure
|
13
|
+
RescueRegistry.context = nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RescueRegistry
|
2
|
+
module Context
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
if respond_to?(:class_attribute)
|
7
|
+
# Prevents subclasses from sharing, but only available to classes
|
8
|
+
class_attribute :rescue_registry
|
9
|
+
else
|
10
|
+
# Allows this module to be included in a module
|
11
|
+
mattr_accessor :rescue_registry
|
12
|
+
end
|
13
|
+
|
14
|
+
self.rescue_registry = Registry.new(self)
|
15
|
+
|
16
|
+
class << self
|
17
|
+
delegate :register_exception, to: :rescue_registry
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def rescue_registry
|
22
|
+
self.class.rescue_registry
|
23
|
+
end
|
24
|
+
|
25
|
+
class_methods do
|
26
|
+
def inherited(subklass)
|
27
|
+
super
|
28
|
+
subklass.rescue_registry = rescue_registry.dup
|
29
|
+
subklass.rescue_registry.owner = subklass
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_exception_handler
|
33
|
+
ExceptionHandler
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -3,16 +3,7 @@ module RescueRegistry
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
|
7
|
-
self.rescue_registry = Registry.new(self)
|
8
|
-
|
9
|
-
class << self
|
10
|
-
delegate :register_exception, to: :rescue_registry
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def rescue_registry
|
15
|
-
self.class.rescue_registry
|
6
|
+
include Context
|
16
7
|
end
|
17
8
|
|
18
9
|
def process_action(*args)
|
@@ -32,17 +23,5 @@ module RescueRegistry
|
|
32
23
|
|
33
24
|
super
|
34
25
|
end
|
35
|
-
|
36
|
-
class_methods do
|
37
|
-
def inherited(subklass)
|
38
|
-
super
|
39
|
-
subklass.rescue_registry = rescue_registry.dup
|
40
|
-
subklass.rescue_registry.owner = subklass
|
41
|
-
end
|
42
|
-
|
43
|
-
def default_exception_handler
|
44
|
-
ExceptionHandler
|
45
|
-
end
|
46
|
-
end
|
47
26
|
end
|
48
27
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module RescueRegistry
|
2
4
|
class ExceptionHandler
|
3
5
|
def self.default_status
|
@@ -8,11 +10,7 @@ module RescueRegistry
|
|
8
10
|
def initialize(exception, **options)
|
9
11
|
@exception = exception
|
10
12
|
|
11
|
-
status = options[:status]
|
12
|
-
if status == :passthrough
|
13
|
-
status = ActionDispatch::ExceptionWrapper.status_code_for_exception_without_rescue_registry(exception.class.name)
|
14
|
-
end
|
15
|
-
@status = status
|
13
|
+
@status = options[:status]
|
16
14
|
|
17
15
|
@title = options[:title]
|
18
16
|
|
@@ -46,10 +44,14 @@ module RescueRegistry
|
|
46
44
|
when Proc
|
47
45
|
@detail.call(exception)
|
48
46
|
else
|
49
|
-
@detail.
|
47
|
+
if @detail.respond_to?(:to_s)
|
48
|
+
val = @detail.to_s
|
49
|
+
# Don't return empty string
|
50
|
+
val.empty? ? nil : val
|
51
|
+
end
|
50
52
|
end
|
51
53
|
|
52
|
-
detail
|
54
|
+
detail || default_detail_for_status
|
53
55
|
end
|
54
56
|
|
55
57
|
def meta
|
@@ -83,20 +85,25 @@ module RescueRegistry
|
|
83
85
|
}
|
84
86
|
end
|
85
87
|
|
86
|
-
|
88
|
+
# `content_type` should be an object with:
|
89
|
+
# * `to_sym` returning sane name for the content_type (e.g. :jsonapi, :json, :xml, :html)
|
90
|
+
# * `to_s` returning the content_type string (e.g. "application/vnd.api+json", "application/json", "text/xml", "text/html")
|
91
|
+
def formatted_response(content_type, fallback: :none, **options)
|
87
92
|
body = build_payload(**options)
|
88
93
|
|
89
94
|
# TODO: Maybe make a helper to register these types?
|
90
|
-
to_format = content_type == :jsonapi ? "to_json" : "to_#{content_type.to_sym}"
|
95
|
+
to_format = content_type.to_sym == :jsonapi ? "to_json" : "to_#{content_type.to_sym}"
|
91
96
|
|
92
97
|
if content_type && body.respond_to?(to_format)
|
93
98
|
formatted_body = body.public_send(to_format)
|
94
99
|
format = content_type
|
95
100
|
else
|
96
|
-
|
101
|
+
case fallback.to_sym
|
102
|
+
when :json
|
97
103
|
formatted_body = body.to_json
|
98
|
-
|
99
|
-
|
104
|
+
# FIXME: This won't work without Rails
|
105
|
+
format = fallback
|
106
|
+
when :none
|
100
107
|
return nil
|
101
108
|
else
|
102
109
|
raise ArgumentError, "unknown fallback=#{fallback}"
|
@@ -5,30 +5,28 @@ module RescueRegistry
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def call(env)
|
8
|
-
request = ActionDispatch::Request.new(env)
|
8
|
+
request = ::ActionDispatch::Request.new(env)
|
9
9
|
exception = request.get_header "action_dispatch.exception"
|
10
10
|
|
11
11
|
if RescueRegistry.handles_exception?(exception)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
content_type = Mime[:text]
|
16
|
-
end
|
17
|
-
|
18
|
-
if (response = RescueRegistry.response_for_public(content_type, exception))
|
19
|
-
status, body, format = response
|
12
|
+
content_type = request.formats.first || Mime[:text]
|
13
|
+
response = RescueRegistry.response_for_public(content_type, exception)
|
14
|
+
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
16
|
+
if response
|
17
|
+
status, body, format = response
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
else
|
28
|
-
# If we have no response, it means we couldn't render for the content_type, use the default handler instead
|
29
|
-
@app.call(env)
|
19
|
+
if request.path_info != "/#{status}"
|
20
|
+
warn "status mismatch; path_info=#{request.path_info}; status=#{status}"
|
30
21
|
end
|
22
|
+
|
23
|
+
[status, { "Content-Type" => "#{format}; charset=#{::ActionDispatch::Response.default_charset}",
|
24
|
+
"Content-Length" => body.bytesize.to_s }, [body]]
|
31
25
|
else
|
26
|
+
# If we have no response, it means one of the following:
|
27
|
+
# * RescueRegistry doesn't handle this exception
|
28
|
+
# * RescueRegistry doesn't have a response to render for this content_type.
|
29
|
+
# In either case, we use the default handler instead
|
32
30
|
@app.call(env)
|
33
31
|
end
|
34
32
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RescueRegistry
|
2
|
+
# Helpers to improve the ease of testing error handling in Rails tests.
|
3
|
+
# These are not actually specific to RescueRegistry, but will certainly be useful for it.
|
4
|
+
module RailsTestHelpers
|
5
|
+
def handle_request_exceptions(handle = true, &block)
|
6
|
+
set_action_dispatch_property(:show_exceptions, handle, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle_request_exceptions?
|
10
|
+
Rails.application.config.action_dispatch.show_exceptions
|
11
|
+
end
|
12
|
+
|
13
|
+
def show_detailed_exceptions(show = true, &block)
|
14
|
+
set_action_dispatch_property(:show_detailed_exceptions, show, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def show_detailed_exceptions?
|
18
|
+
Rails.application.config.action_dispatch.show_detailed_exceptions
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_action_dispatch_property(key, value)
|
24
|
+
if block_given?
|
25
|
+
original_value = Rails.application.config.action_dispatch.send(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
Rails.application.config.action_dispatch.send("#{key}=", value)
|
29
|
+
# Also set this since it may have been cached
|
30
|
+
Rails.application.env_config["action_dispatch.#{key}"] = value
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
begin
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
Rails.application.env_config["action_dispatch.#{key}"] = original_value
|
37
|
+
Rails.application.config.action_dispatch.send("#{key}=", original_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rails/railtie'
|
4
|
-
|
5
3
|
module RescueRegistry
|
6
4
|
class Railtie < Rails::Railtie
|
5
|
+
initializer "rescue_registry.add_middleware" do |app|
|
6
|
+
# We add this middleware to ensure that the RescueRegistry.context is properly handled.
|
7
|
+
# The context is set in the controller action and will be available until the Middleware
|
8
|
+
# returns. Any middleware that are before this one will not have access to the context.
|
9
|
+
app.middleware.insert_before ::ActionDispatch::ShowExceptions, RescueRegistry::ResetContext
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
@@ -3,7 +3,7 @@ module RescueRegistry
|
|
3
3
|
attr_accessor :owner
|
4
4
|
|
5
5
|
def initialize(owner)
|
6
|
-
@owner =
|
6
|
+
@owner = owner
|
7
7
|
@handlers = { }
|
8
8
|
end
|
9
9
|
|
@@ -11,16 +11,28 @@ module RescueRegistry
|
|
11
11
|
@handlers = @handlers.dup
|
12
12
|
end
|
13
13
|
|
14
|
+
def passthrough_allowed?
|
15
|
+
defined?(ActionDispatch::ExceptionWrapper)
|
16
|
+
end
|
17
|
+
|
18
|
+
def passthrough_status(exception)
|
19
|
+
::ActionDispatch::ExceptionWrapper.status_code_for_exception(exception.class.name)
|
20
|
+
end
|
21
|
+
|
14
22
|
# TODO: Support a shorthand for handler
|
15
23
|
def register_exception(exception_class, handler: nil, **options)
|
16
24
|
raise ArgumentError, "#{exception_class} is not an Exception" unless exception_class <= Exception
|
17
25
|
|
18
|
-
|
26
|
+
if owner.respond_to?(:default_exception_handler)
|
27
|
+
handler ||= owner.default_exception_handler
|
28
|
+
end
|
19
29
|
raise ArgumentError, "handler must be provided" unless handler
|
20
30
|
|
21
31
|
status = options[:status] ||= handler.default_status
|
22
32
|
raise ArgumentError, "status must be provided" unless status
|
23
|
-
|
33
|
+
unless status.is_a?(Integer) || (passthrough_allowed? && status == :passthrough)
|
34
|
+
raise ArgumentError, "invalid status: #{status}"
|
35
|
+
end
|
24
36
|
|
25
37
|
# TODO: Validate options here
|
26
38
|
|
@@ -33,20 +45,27 @@ module RescueRegistry
|
|
33
45
|
raise HandlerNotFound, "no handler found for #{exception.class}" unless handler_info
|
34
46
|
|
35
47
|
handler_class, handler_options = handler_info
|
48
|
+
|
49
|
+
if handler_options[:status] == :passthrough
|
50
|
+
handler_options = handler_options.merge(status: passthrough_status(exception))
|
51
|
+
end
|
52
|
+
|
36
53
|
handler_class.new(exception, **handler_options)
|
37
54
|
end
|
38
55
|
|
39
56
|
def handles_exception?(exception)
|
40
|
-
handler_info_for_exception(exception).
|
57
|
+
!handler_info_for_exception(exception).nil?
|
41
58
|
end
|
42
59
|
|
43
|
-
def status_code_for_exception(exception)
|
60
|
+
def status_code_for_exception(exception, passthrough: true)
|
44
61
|
_, options = handler_info_for_exception(exception)
|
45
62
|
return unless options
|
46
63
|
|
47
|
-
|
48
|
-
|
49
|
-
|
64
|
+
if options[:status] == :passthrough
|
65
|
+
passthrough ? passthrough_status(exception) : nil
|
66
|
+
else
|
67
|
+
options[:status]
|
68
|
+
end
|
50
69
|
end
|
51
70
|
|
52
71
|
def build_response(content_type, exception, **options)
|
@@ -54,12 +73,12 @@ module RescueRegistry
|
|
54
73
|
handler.formatted_response(content_type, **options)
|
55
74
|
end
|
56
75
|
|
57
|
-
def response_for_debugging(content_type, exception, traces: nil)
|
58
|
-
build_response(content_type, exception, show_details: true, traces: traces)
|
76
|
+
def response_for_debugging(content_type, exception, traces: nil, fallback: :none)
|
77
|
+
build_response(content_type, exception, show_details: true, traces: traces, fallback: fallback)
|
59
78
|
end
|
60
79
|
|
61
|
-
def response_for_public(content_type, exception)
|
62
|
-
build_response(content_type, exception, fallback:
|
80
|
+
def response_for_public(content_type, exception, fallback: :none)
|
81
|
+
build_response(content_type, exception, fallback: fallback)
|
63
82
|
end
|
64
83
|
|
65
84
|
private
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RescueRegistry
|
2
|
+
class ResetContext
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
warn "Didn't expect RescueRegistry context to already be set in middleware" if RescueRegistry.context
|
9
|
+
@app.call(*args)
|
10
|
+
ensure
|
11
|
+
RescueRegistry.context = nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This is for use in non-Rails Rack apps. Rails apps will handle exceptions with
|
4
|
+
# ActionDispatch::DebugExceptions and ActionDispatch::ShowExceptions
|
5
|
+
module RescueRegistry
|
6
|
+
class ShowExceptions
|
7
|
+
# Unfortunately, Rail's nice mime types are in ActionDispatch which we'd rather not require
|
8
|
+
CONTENT_TYPES = {
|
9
|
+
jsonapi: "application/vnd.api+json",
|
10
|
+
json: "application/json",
|
11
|
+
xml: ["application/xml", "text/xml"],
|
12
|
+
plain: "text/plain"
|
13
|
+
}
|
14
|
+
|
15
|
+
# Match Rail's Mime::Type API on a basic level
|
16
|
+
MimeType = Struct.new(:to_sym, :to_s)
|
17
|
+
|
18
|
+
def initialize(app, debug: false, content_types: CONTENT_TYPES)
|
19
|
+
@app = app
|
20
|
+
@content_types = content_types
|
21
|
+
@debug = debug
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
@app.call(env)
|
26
|
+
rescue Exception => exception
|
27
|
+
handle_exception(env, exception)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def handle_exception(env, exception)
|
33
|
+
if RescueRegistry.handles_exception?(exception)
|
34
|
+
accept = Rack::Utils.best_q_match(env["HTTP_ACCEPT"], @content_types.values.flatten)
|
35
|
+
accept ||= "text/plain"
|
36
|
+
|
37
|
+
symbol = CONTENT_TYPES.find { |(k,v)| Array(v).include?(accept) }.first
|
38
|
+
content_type = MimeType.new(symbol, accept)
|
39
|
+
|
40
|
+
# We need a fallback to ensure that we actually do render something. Outside of Rails, there's no situation where
|
41
|
+
# we would want to pass through on a handled exception.
|
42
|
+
fallback = MimeType.new(:json, CONTENT_TYPES[:json])
|
43
|
+
if @debug
|
44
|
+
response = RescueRegistry.response_for_debugging(content_type, exception, traces: { "Full Trace" => exception.backtrace }, fallback: fallback)
|
45
|
+
else
|
46
|
+
response = RescueRegistry.response_for_public(content_type, exception, fallback: fallback)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if response
|
51
|
+
status, body, format = response
|
52
|
+
[status, { "Content-Type" => "#{format}", "Content-Length" => body.bytesize.to_s }, [body]]
|
53
|
+
else
|
54
|
+
# Let next middleware handle
|
55
|
+
raise exception
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -1,25 +1,22 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rescue_registry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Wagenet
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '5.0'
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '7.0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,9 +24,6 @@ dependencies:
|
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '5.0'
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '7.0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: appraisal
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,34 +66,6 @@ dependencies:
|
|
72
66
|
- - "~>"
|
73
67
|
- !ruby/object:Gem::Version
|
74
68
|
version: '3.3'
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: rspec-rails
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - "~>"
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '3.8'
|
82
|
-
type: :development
|
83
|
-
prerelease: false
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - "~>"
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '3.8'
|
89
|
-
- !ruby/object:Gem::Dependency
|
90
|
-
name: sqlite3
|
91
|
-
requirement: !ruby/object:Gem::Requirement
|
92
|
-
requirements:
|
93
|
-
- - "~>"
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: '1.3'
|
96
|
-
type: :development
|
97
|
-
prerelease: false
|
98
|
-
version_requirements: !ruby/object:Gem::Requirement
|
99
|
-
requirements:
|
100
|
-
- - "~>"
|
101
|
-
- !ruby/object:Gem::Version
|
102
|
-
version: '1.3'
|
103
69
|
- !ruby/object:Gem::Dependency
|
104
70
|
name: yard
|
105
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,7 +80,7 @@ dependencies:
|
|
114
80
|
- - "~>"
|
115
81
|
- !ruby/object:Gem::Version
|
116
82
|
version: '0.9'
|
117
|
-
description:
|
83
|
+
description:
|
118
84
|
email:
|
119
85
|
- peter.wagenet@gmail.com
|
120
86
|
executables: []
|
@@ -127,11 +93,18 @@ files:
|
|
127
93
|
- Rakefile
|
128
94
|
- lib/rescue_registry.rb
|
129
95
|
- lib/rescue_registry/action_dispatch.rb
|
96
|
+
- lib/rescue_registry/action_dispatch/debug_exceptions.rb
|
97
|
+
- lib/rescue_registry/action_dispatch/exception_wrapper.rb
|
98
|
+
- lib/rescue_registry/action_dispatch/show_exceptions.rb
|
99
|
+
- lib/rescue_registry/context.rb
|
130
100
|
- lib/rescue_registry/controller.rb
|
131
101
|
- lib/rescue_registry/exception_handler.rb
|
132
102
|
- lib/rescue_registry/exceptions_app.rb
|
103
|
+
- lib/rescue_registry/rails_test_helpers.rb
|
133
104
|
- lib/rescue_registry/railtie.rb
|
134
105
|
- lib/rescue_registry/registry.rb
|
106
|
+
- lib/rescue_registry/reset_context.rb
|
107
|
+
- lib/rescue_registry/show_exceptions.rb
|
135
108
|
- lib/rescue_registry/version.rb
|
136
109
|
- lib/tasks/rescue_registry_tasks.rake
|
137
110
|
homepage: https://github.com/wagenet/rescue_registry
|
@@ -141,23 +114,26 @@ metadata:
|
|
141
114
|
bug_tracker_uri: https://github.com/wagenet/rescue_registry/issues
|
142
115
|
changelog_uri: https://github.com/wagenet/rescue_registry/CHANGELOG.md
|
143
116
|
source_code_uri: https://github.com/wagenet/rescue_registry
|
144
|
-
post_install_message:
|
117
|
+
post_install_message:
|
145
118
|
rdoc_options: []
|
146
119
|
require_paths:
|
147
120
|
- lib
|
148
121
|
required_ruby_version: !ruby/object:Gem::Requirement
|
149
122
|
requirements:
|
150
|
-
- - "
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '2.3'
|
126
|
+
- - "<"
|
151
127
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
128
|
+
version: '3.1'
|
153
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
130
|
requirements:
|
155
131
|
- - ">="
|
156
132
|
- !ruby/object:Gem::Version
|
157
133
|
version: '0'
|
158
134
|
requirements: []
|
159
|
-
rubygems_version: 3.
|
160
|
-
signing_key:
|
135
|
+
rubygems_version: 3.1.4
|
136
|
+
signing_key:
|
161
137
|
specification_version: 4
|
162
138
|
summary: Registry for Rails Exceptions
|
163
139
|
test_files: []
|