rescue_registry 0.1.0 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 528eb94a1296262f6cb1d20d7b8209072147c72e392dabb468fdbb0d2e4d9f43
4
- data.tar.gz: 296ea4a7f1a08fa3fbd981c1ec08e35e528befc17b4167a51b5cdcd480783229
3
+ metadata.gz: '059708756e6a55e037d23bea568cfe2d58818204d21da641b15a824eeed8aeb6'
4
+ data.tar.gz: 72f2989656beee1dd63caff80fde2808d9ad82aa36e2d5f48c9ada3eda529fab
5
5
  SHA512:
6
- metadata.gz: 076d737ba7c477015a3b984e34d2c3782b56688d039db35b27d9cf8d89b682755e1a01c0cae34996ab63ed1fdf2a6e20c8b945d8f1b84c1a5a196f002b060b6c
7
- data.tar.gz: 46c7d35672ecb8d4f04b5d2cad6f4d9844ef3d6d5600c6ba2eaa090a638c2bce77f180163523c5bda7ca646db6355f172ce70da996bc9fbc5b9f6d9b4bcf7191
6
+ metadata.gz: 6c40c4d7c189ed217216eca7f37ab81d3fc7bf9ae906a868e0e676f11e7384f67125e8d305bf55db75284ec5b37d70355719dbfee4cd2b72f009229238bcd154
7
+ data.tar.gz: de9fb10f2fd414e44b7a50be904505bb42889088ab71d76ec34f004501b297996018cd398629c6f78cea17f9fbfad77f79a530be4e0b2ee8c8b93fb2c0787888
@@ -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
- No releases means no changes...
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!
@@ -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
- require 'rescue_registry/action_dispatch'
51
- require 'rescue_registry/railtie'
73
+ if defined?(Rails)
74
+ require 'rescue_registry/railtie'
75
+ end
@@ -1,71 +1,7 @@
1
- require 'action_dispatch'
2
-
3
- ActionDispatch::ExceptionWrapper.class_eval do
4
- class << self
5
- alias_method :status_code_for_exception_without_rescue_registry, :status_code_for_exception
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,9 @@
1
+ module RescueRegistry
2
+ module ActionDispatch
3
+ module ExceptionWrapper
4
+ def status_code_for_exception(class_name)
5
+ RescueRegistry.status_code_for_exception(class_name, passthrough: false) || super
6
+ end
7
+ end
8
+ end
9
+ 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
- cattr_accessor :rescue_registry
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.try(:to_s)
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.presence || default_detail_for_status
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
- def formatted_response(content_type, fallback: :json, **options)
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
- if fallback == :json
101
+ case fallback.to_sym
102
+ when :json
97
103
  formatted_body = body.to_json
98
- format = Mime[:json]
99
- elsif fallback == :none
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
- begin
13
- content_type = request.formats.first
14
- rescue Mime::Type::InvalidMimeType
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
- if request.path_info != "/#{status}"
22
- warn "status mismatch; path_info=#{request.path_info}; status=#{status}"
23
- end
16
+ if response
17
+ status, body, format = response
24
18
 
25
- [status, { "Content-Type" => "#{format}; charset=#{ActionDispatch::Response.default_charset}",
26
- "Content-Length" => body.bytesize.to_s }, [body]]
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 = self
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
- handler ||= owner.try(:default_exception_handler)
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
- raise ArgumentError, "invalid status: #{status}" unless status.is_a?(Integer) || status == :passthrough
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).present?
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
- # Return no code for passthrough.
48
- # Alternatively we could handle this explicitly in the ExceptionWrapper monkeypatch.
49
- options[:status] == :passthrough ? nil : options[:status]
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: :none)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RescueRegistry
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.4"
5
5
  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.1.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: 2019-05-15 00:00:00.000000000 Z
11
+ date: 2021-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
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: '2.4'
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.0.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: []