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 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: []