rescue_registry 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 528eb94a1296262f6cb1d20d7b8209072147c72e392dabb468fdbb0d2e4d9f43
4
+ data.tar.gz: 296ea4a7f1a08fa3fbd981c1ec08e35e528befc17b4167a51b5cdcd480783229
5
+ SHA512:
6
+ metadata.gz: 076d737ba7c477015a3b984e34d2c3782b56688d039db35b27d9cf8d89b682755e1a01c0cae34996ab63ed1fdf2a6e20c8b945d8f1b84c1a5a196f002b060b6c
7
+ data.tar.gz: 46c7d35672ecb8d4f04b5d2cad6f4d9844ef3d6d5600c6ba2eaa090a638c2bce77f180163523c5bda7ca646db6355f172ce70da996bc9fbc5b9f6d9b4bcf7191
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ No releases means no changes...
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Peter Wagenet
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ # RescueRegistry
2
+
3
+ RescueRegistry improves error handling with Rails while still hewing as close to the defaults as possible.
4
+
5
+
6
+
7
+ ## Features
8
+
9
+ ### Better than `rescue_from`
10
+ `rescue_from` works fine, but it it's a big hammer. Using it completely bypasses Rail's built-in exception handling middlewares,
11
+ which actually do some nice things for us (e.g. automatically handling different data formats). RescueRegistry allows the built-in
12
+ middleware to handle the exceptions but with our custom handlers.
13
+
14
+ ### Better default exception handling
15
+ Rails also has some built-in support for assigning different exception classes to status types (See `config.action_dispatch.rescue_responses`).
16
+ Unfortunately, all this allows you to do is assign a status code. If you want more complex error handling, or to use different codes in
17
+ different controllers, you're out of luck. With RescueRegistry you can register and exception with a custom handler or with different status
18
+ codes in different controllers.
19
+
20
+ ## In Action
21
+
22
+ ### graphiti-rails
23
+ The [graphiti-rails gem](https://github.com/wagenet/graphiti-rails) uses RescueRegistry to facilitate better JSON:API error handling, since
24
+ Rails' out-of-the-box behavior doesn't adhere to the JSON:API spec.
25
+
26
+ ## Usage
27
+
28
+ Example usages:
29
+
30
+ ```ruby
31
+ class MyController < ActionController::Base
32
+ class CustomStatusError < StandardError; end
33
+ class CustomTitleError < StandardError; end
34
+ class DetailExceptionError < StandardError; end
35
+ class DetailProcError < StandardError; end
36
+ class DetailStringError < StandardError; end
37
+ class MetaProcError < StandardError; end
38
+ class LogFalseError < StandardError; end
39
+ class CustomHandlerError < StandardError; end
40
+ class RailsError < StandardError; end
41
+ class SubclassedError < CustomStatusError; end
42
+
43
+ class CustomErrorHandler < RescueRegistry::ExceptionHandler
44
+ def self.default_status
45
+ 302
46
+ end
47
+
48
+ def title
49
+ "Custom Title"
50
+ end
51
+ end
52
+
53
+ register_exception CustomStatusError, status: 401
54
+ register_exception CustomTitleError, title: "My Title"
55
+ register_exception DetailExceptionError, detail: :exception
56
+ register_exception DetailProcError, detail: -> (e) { e.class.name.upcase }
57
+ register_exception DetailStringError, detail: "Custom Detail"
58
+ register_exception MetaProcError, meta: -> (e) { { class_name: e.class.name.upcase } }
59
+ register_exception CustomHandlerError, handler: CustomErrorHandler
60
+ register_exception RailsError, status: 403, handler: RescueRegistry::RailsExceptionHandler
61
+ end
62
+ ```
63
+
64
+ ## Installation
65
+ Add this line to your application's Gemfile:
66
+
67
+ ```ruby
68
+ gem 'rescue_registry'
69
+ ```
70
+
71
+ ## Contributing
72
+ We'd love to have your help improving rescue_registry, send a PR!
73
+
74
+ ## License
75
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,20 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'yard'
8
+ require 'rspec/core/rake_task'
9
+ require 'bundler/gem_tasks'
10
+
11
+ YARD::Rake::YardocTask.new
12
+
13
+ RSpec::Core::RakeTask.new(:spec) do |t|
14
+ t.rspec_opts = "--order random"
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load "rails/tasks/engine.rake"
19
+
20
+ task default: :spec
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module RescueRegistry
6
+ autoload :Controller, "rescue_registry/controller"
7
+ autoload :ExceptionsApp, "rescue_registry/exceptions_app"
8
+ autoload :ExceptionHandler, "rescue_registry/exception_handler"
9
+ autoload :RailsExceptionHandler, "rescue_registry/exception_handler"
10
+ autoload :Registry, "rescue_registry/registry"
11
+
12
+ class HandlerNotFound < StandardError; end
13
+
14
+ def self.context
15
+ Thread.current[:rescue_registry_context]
16
+ end
17
+
18
+ def self.context=(value)
19
+ Thread.current[:rescue_registry_context] = value
20
+ end
21
+
22
+ def self.with_context(value)
23
+ original = context
24
+ self.context = value
25
+ yield
26
+ ensure
27
+ self.context = original
28
+ end
29
+
30
+ REGISTRY_METHODS = %i[
31
+ handler_for_exception
32
+ handles_exception?
33
+ status_code_for_exception
34
+ response_for_debugging
35
+ response_for_public
36
+ ]
37
+
38
+ REGISTRY_METHODS.each do |method|
39
+ define_singleton_method(method) do |*args|
40
+ return unless context.respond_to?(:rescue_registry)
41
+ context.rescue_registry.public_send(method, *args)
42
+ end
43
+ end
44
+ end
45
+
46
+ ActiveSupport.on_load(:action_controller) do
47
+ include RescueRegistry::Controller
48
+ end
49
+
50
+ require 'rescue_registry/action_dispatch'
51
+ require 'rescue_registry/railtie'
@@ -0,0 +1,71 @@
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
70
+ end
71
+ end
@@ -0,0 +1,48 @@
1
+ module RescueRegistry
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
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
16
+ end
17
+
18
+ def process_action(*args)
19
+ if RescueRegistry.context
20
+ # Controller logger isn't yet available
21
+ Rails.logger.warn "Didn't expect RescueRegistry context to be set in controller"
22
+ Rails.logger.debug caller.join("\n")
23
+ end
24
+
25
+ # Setting something globally is not very nice, but it allows us to access it without
26
+ # having to change a whole lot of internal Rails APIs. This especially matters when
27
+ # getting the status code via ExceptionWrapper.
28
+ # We don't unset here so that it is available to middleware
29
+ # Unsetting happens in ActionDispatch::ShowExceptions. This _should_ be ok since we shouldn't
30
+ # be processing multiple requests in the same thread.
31
+ RescueRegistry.context = self
32
+
33
+ super
34
+ 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
+ end
48
+ end
@@ -0,0 +1,148 @@
1
+ module RescueRegistry
2
+ class ExceptionHandler
3
+ def self.default_status
4
+ 500
5
+ end
6
+
7
+ # Use a glob for options so that unknown values won't throw errors. Also, this list could get long...
8
+ def initialize(exception, **options)
9
+ @exception = exception
10
+
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
16
+
17
+ @title = options[:title]
18
+
19
+ @detail = options[:detail]
20
+ if options[:message] && @detail.nil?
21
+ # Deprecated, from GraphitiErrors
22
+ @detail = (options[:message] == true) ? :exception : options[:message]
23
+ end
24
+
25
+ @meta = options[:meta]
26
+
27
+ # TODO: Warn about unrecognized options
28
+ end
29
+
30
+ attr_reader :exception, :status
31
+ alias_method :status_code, :status
32
+
33
+ def error_code
34
+ error_code_from_status
35
+ end
36
+
37
+ def title
38
+ @title || title_from_status
39
+ end
40
+
41
+ def detail
42
+ detail =
43
+ case @detail
44
+ when :exception
45
+ exception.message
46
+ when Proc
47
+ @detail.call(exception)
48
+ else
49
+ @detail.try(:to_s)
50
+ end
51
+
52
+ detail.presence || default_detail_for_status
53
+ end
54
+
55
+ def meta
56
+ if @meta.is_a?(Proc)
57
+ @meta.call(exception)
58
+ else
59
+ { }
60
+ end
61
+ end
62
+
63
+ def build_payload(show_details: false, traces: nil)
64
+ payload_meta = meta
65
+
66
+ if show_details
67
+ payload_meta = payload_meta.merge(
68
+ __details__: {
69
+ exception: exception.inspect,
70
+ traces: traces || [exception.backtrace]
71
+ }
72
+ )
73
+ end
74
+
75
+ {
76
+ errors: [
77
+ code: error_code,
78
+ status: status_code.to_s,
79
+ title: title,
80
+ detail: detail,
81
+ meta: payload_meta
82
+ ]
83
+ }
84
+ end
85
+
86
+ def formatted_response(content_type, fallback: :json, **options)
87
+ body = build_payload(**options)
88
+
89
+ # TODO: Maybe make a helper to register these types?
90
+ to_format = content_type == :jsonapi ? "to_json" : "to_#{content_type.to_sym}"
91
+
92
+ if content_type && body.respond_to?(to_format)
93
+ formatted_body = body.public_send(to_format)
94
+ format = content_type
95
+ else
96
+ if fallback == :json
97
+ formatted_body = body.to_json
98
+ format = Mime[:json]
99
+ elsif fallback == :none
100
+ return nil
101
+ else
102
+ raise ArgumentError, "unknown fallback=#{fallback}"
103
+ end
104
+ end
105
+
106
+ [status_code, formatted_body, format]
107
+ end
108
+
109
+ private
110
+
111
+ def error_code_from_status
112
+ code_to_symbol = Rack::Utils::SYMBOL_TO_STATUS_CODE.invert
113
+ code_to_symbol.fetch(status_code, code_to_symbol[500])
114
+ end
115
+
116
+ def title_from_status
117
+ Rack::Utils::HTTP_STATUS_CODES.fetch(
118
+ status_code,
119
+ Rack::Utils::HTTP_STATUS_CODES[500]
120
+ )
121
+ end
122
+
123
+ def default_detail_for_status
124
+ if status_code >= 500
125
+ "We've notified our engineers and hope to address this issue shortly."
126
+ end
127
+ end
128
+ end
129
+
130
+ # Builds a payload in the style of the Rails default handling for compatibility
131
+ class RailsExceptionHandler < ExceptionHandler
132
+ def build_payload(show_details: false, traces: nil)
133
+ body = {
134
+ status: status_code,
135
+ error: title
136
+ }
137
+
138
+ if show_details
139
+ body[:exception] = exception.inspect
140
+ if traces
141
+ body[:traces] = traces
142
+ end
143
+ end
144
+
145
+ body
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,36 @@
1
+ module RescueRegistry
2
+ class ExceptionsApp
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ request = ActionDispatch::Request.new(env)
9
+ exception = request.get_header "action_dispatch.exception"
10
+
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
20
+
21
+ if request.path_info != "/#{status}"
22
+ warn "status mismatch; path_info=#{request.path_info}; status=#{status}"
23
+ end
24
+
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)
30
+ end
31
+ else
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module RescueRegistry
6
+ class Railtie < Rails::Railtie
7
+ end
8
+ end
@@ -0,0 +1,100 @@
1
+ module RescueRegistry
2
+ class Registry
3
+ attr_accessor :owner
4
+
5
+ def initialize(owner)
6
+ @owner = self
7
+ @handlers = { }
8
+ end
9
+
10
+ def initialize_dup(_other)
11
+ @handlers = @handlers.dup
12
+ end
13
+
14
+ # TODO: Support a shorthand for handler
15
+ def register_exception(exception_class, handler: nil, **options)
16
+ raise ArgumentError, "#{exception_class} is not an Exception" unless exception_class <= Exception
17
+
18
+ handler ||= owner.try(:default_exception_handler)
19
+ raise ArgumentError, "handler must be provided" unless handler
20
+
21
+ status = options[:status] ||= handler.default_status
22
+ raise ArgumentError, "status must be provided" unless status
23
+ raise ArgumentError, "invalid status: #{status}" unless status.is_a?(Integer) || status == :passthrough
24
+
25
+ # TODO: Validate options here
26
+
27
+ # We assign the status here as a default when looking up by class (and not instance)
28
+ @handlers[exception_class] = [handler, options]
29
+ end
30
+
31
+ def handler_for_exception(exception)
32
+ handler_info = handler_info_for_exception(exception)
33
+ raise HandlerNotFound, "no handler found for #{exception.class}" unless handler_info
34
+
35
+ handler_class, handler_options = handler_info
36
+ handler_class.new(exception, **handler_options)
37
+ end
38
+
39
+ def handles_exception?(exception)
40
+ handler_info_for_exception(exception).present?
41
+ end
42
+
43
+ def status_code_for_exception(exception)
44
+ _, options = handler_info_for_exception(exception)
45
+ return unless options
46
+
47
+ # Return no code for passthrough.
48
+ # Alternatively we could handle this explicitly in the ExceptionWrapper monkeypatch.
49
+ options[:status] == :passthrough ? nil : options[:status]
50
+ end
51
+
52
+ def build_response(content_type, exception, **options)
53
+ handler = handler_for_exception(exception)
54
+ handler.formatted_response(content_type, **options)
55
+ end
56
+
57
+ def response_for_debugging(content_type, exception, traces: nil)
58
+ build_response(content_type, exception, show_details: true, traces: traces)
59
+ end
60
+
61
+ def response_for_public(content_type, exception)
62
+ build_response(content_type, exception, fallback: :none)
63
+ end
64
+
65
+ private
66
+
67
+ def handler_info_for_exception(exception)
68
+ exception_class =
69
+ case exception
70
+ when String
71
+ exception.safe_constantize
72
+ when Class
73
+ exception
74
+ else
75
+ exception.class
76
+ end
77
+
78
+ return unless exception_class
79
+
80
+ raise ArgumentError, "#{exception_class} is not an Exception" unless exception_class <= Exception
81
+
82
+ # Reverse so most recently defined takes precedence
83
+ registry = @handlers.to_a.reverse
84
+
85
+ # Look for an exact class, then for the superclass and so on.
86
+ # There might be a more efficient way to do this, but this is pretty readable
87
+ match_class = exception_class
88
+ loop do
89
+ if (found = registry.find { |(klass, _)| klass == match_class })
90
+ return found.last
91
+ elsif match_class == Exception
92
+ # We've exhausted our options
93
+ return nil
94
+ end
95
+
96
+ match_class = match_class.superclass
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RescueRegistry
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rescue_registry do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rescue_registry
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Wagenet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.2'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: kramdown-parser-gfm
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rouge
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.3'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ 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
+ - !ruby/object:Gem::Dependency
104
+ name: yard
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.9'
117
+ description:
118
+ email:
119
+ - peter.wagenet@gmail.com
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - CHANGELOG.md
125
+ - MIT-LICENSE
126
+ - README.md
127
+ - Rakefile
128
+ - lib/rescue_registry.rb
129
+ - lib/rescue_registry/action_dispatch.rb
130
+ - lib/rescue_registry/controller.rb
131
+ - lib/rescue_registry/exception_handler.rb
132
+ - lib/rescue_registry/exceptions_app.rb
133
+ - lib/rescue_registry/railtie.rb
134
+ - lib/rescue_registry/registry.rb
135
+ - lib/rescue_registry/version.rb
136
+ - lib/tasks/rescue_registry_tasks.rake
137
+ homepage: https://github.com/wagenet/rescue_registry
138
+ licenses:
139
+ - MIT
140
+ metadata:
141
+ bug_tracker_uri: https://github.com/wagenet/rescue_registry/issues
142
+ changelog_uri: https://github.com/wagenet/rescue_registry/CHANGELOG.md
143
+ source_code_uri: https://github.com/wagenet/rescue_registry
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.4'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubygems_version: 3.0.3
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Registry for Rails Exceptions
163
+ test_files: []