exception_transformer 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.
- checksums.yaml +7 -0
- data/lib/exception_transformer/reportable.rb +102 -0
- data/lib/exception_transformer/transformer.rb +92 -0
- data/lib/exception_transformer/version.rb +3 -0
- data/lib/exception_transformer.rb +82 -0
- data/spec/exception_transformer/reportable_spec.rb +106 -0
- data/spec/exception_transformer_spec.rb +291 -0
- data/spec/helpers/class_helpers.rb +13 -0
- data/spec/spec_helper.rb +16 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d7ed6231e5c5ec775142f9bcbefbf8e5611491f11ca8210d97bd883a3f91f494
|
4
|
+
data.tar.gz: 8e676ba345439a39b6b36cf1139e2554127d4f1d8842982c115f7aa0ec85f982
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 495784ad163c57718c3d7ea4891937a5e2f909fefe4d9d6c6396da0663ec160d63570999e4adfb9447ec39bb9a0112d8b9f9c9dcbfc990a6994a72cf01136ff3
|
7
|
+
data.tar.gz: 4e2fabcfccd2915c81ece88e93aa20b6f9eb70fc20541d3597762bcc28307707b21276f3042d35bd95e5e9a7323d46d52ffee4580a532c4075d3c1ab8b9fbb9c
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ExceptionTransformer
|
2
|
+
# Include this module when declaring an exception class to add
|
3
|
+
# the `reportable?` flag to individual exceptions. The presence
|
4
|
+
# of this flag can then be checked when capturing exceptions to
|
5
|
+
# send to a crash reporter.
|
6
|
+
#
|
7
|
+
# @example Conditionally reporting exceptions
|
8
|
+
# class MyError < StandardError
|
9
|
+
# include ExceptionTransformer::ReportableException
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# begin
|
13
|
+
# # do something that may fail
|
14
|
+
# rescue MyError => e
|
15
|
+
# CrashReport.new(e) if e.reportable?
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# This flag can be set at the instance level with `mark_reportable!`.
|
19
|
+
# Alternatively, the class method `as_reportable` returns a subclass
|
20
|
+
# for which `reportable?` is true when raised.
|
21
|
+
module Reportable
|
22
|
+
def self.included(base)
|
23
|
+
raise TypeError, "#{base} is not a type of Exception" unless base <= Exception
|
24
|
+
base.extend ClassMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
# Returns a subclass 'Reportable_<name>' of the current class that
|
29
|
+
# includes `Reportable`. This subclass is created the first time
|
30
|
+
# time this method is called and reused for subsequent invocations.
|
31
|
+
def as_reportable
|
32
|
+
return self if self <= ReportableException
|
33
|
+
|
34
|
+
name = reportable_name
|
35
|
+
mod = parent
|
36
|
+
|
37
|
+
mod.const_defined?(name) ? mod.const_get(name) : mod.const_set(name, build_reportable)
|
38
|
+
end
|
39
|
+
|
40
|
+
def unload_reportable
|
41
|
+
name = reportable_name
|
42
|
+
mod = parent
|
43
|
+
|
44
|
+
mod.send(:remove_const, name) if mod.const_defined?(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reported_class
|
48
|
+
@reported_class ||= self
|
49
|
+
end
|
50
|
+
|
51
|
+
def reported_class=(klass)
|
52
|
+
@reported_class = klass
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_reportable
|
58
|
+
super_class = self
|
59
|
+
Class.new(super_class) do
|
60
|
+
include ReportableException
|
61
|
+
self.reported_class = super_class
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def reportable_name
|
66
|
+
[ReportableException.name, self.name].map(&:demodulize).join("_")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reportable?
|
71
|
+
@reportable ||= false
|
72
|
+
end
|
73
|
+
|
74
|
+
def mark_reportable!
|
75
|
+
@reportable = true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Every `Reportable` that includes `ReportableException` will be
|
80
|
+
# raised with the `reportable?` flag set to true.
|
81
|
+
module ReportableException
|
82
|
+
def self.included(base)
|
83
|
+
base.include(Reportable) unless base <= Reportable
|
84
|
+
base.extend ClassMethods
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
def exception(*args)
|
89
|
+
if reported_class == self
|
90
|
+
super.tap(&:mark_reportable!)
|
91
|
+
else
|
92
|
+
reported_class.exception(*args).tap(&:mark_reportable!)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def exception(*args)
|
98
|
+
mark_reportable!
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class ExceptionTransformer::Transformer
|
2
|
+
attr_accessor :strategy, :validator, :delegate, :mappings
|
3
|
+
|
4
|
+
MAX_MESSAGE_SIZE = 100
|
5
|
+
|
6
|
+
def initialize(strategy)
|
7
|
+
self.strategy = strategy
|
8
|
+
self.mappings = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_target(target, exceptions)
|
12
|
+
case strategy
|
13
|
+
when :validate
|
14
|
+
self.validator = target
|
15
|
+
when :delegate
|
16
|
+
self.delegate = target
|
17
|
+
when :rewrite, :regex
|
18
|
+
exceptions.each do |klass|
|
19
|
+
self.mappings[klass] = target
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_rescue(obj, e, calling_method, except: [], use_default: true, opts: {})
|
25
|
+
with_reporting do
|
26
|
+
case strategy
|
27
|
+
when :delegate
|
28
|
+
obj.instance_exec(e, calling_method, opts, &delegate)
|
29
|
+
when :rewrite, :regex
|
30
|
+
exception, message = find_target(e, except, use_default)
|
31
|
+
end
|
32
|
+
|
33
|
+
if exception.present?
|
34
|
+
raise exception, message.first(MAX_MESSAGE_SIZE), e.backtrace
|
35
|
+
else
|
36
|
+
# Couldn't transform the exception to a defined mapping.
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def after_yield(obj, result, calling_method, except: [], use_default: true, opts: {})
|
43
|
+
with_reporting do
|
44
|
+
case strategy
|
45
|
+
when :validate
|
46
|
+
obj.instance_exec(result, calling_method, opts, &validator)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @return [Array] `[exception, message]`
|
54
|
+
def find_target(e, exclude, use_default_match)
|
55
|
+
case strategy
|
56
|
+
when :rewrite
|
57
|
+
exception = find_mapping(e, exclude)
|
58
|
+
message = e.message
|
59
|
+
when :regex
|
60
|
+
patterns = find_mapping(e, exclude) || {}
|
61
|
+
|
62
|
+
pattern = patterns.keys.find { |re| re.is_a?(Regexp) && e.message =~ re }
|
63
|
+
pattern ||= :default if use_default_match
|
64
|
+
|
65
|
+
exception = patterns[pattern]
|
66
|
+
message = e.message.length <= MAX_MESSAGE_SIZE ? e.message : pattern.inspect.gsub(/([^\w\s]|i\z)*/, '')
|
67
|
+
end
|
68
|
+
|
69
|
+
[exception, message]
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [StandardError, Hash]
|
73
|
+
def find_mapping(e, exclude)
|
74
|
+
mappings
|
75
|
+
.select { |klass| e.is_a?(klass) && !exclude.include?(klass) }
|
76
|
+
.sort_by { |klass, _| klass.ancestors.count }
|
77
|
+
.last.try(:[], 1)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Report all exceptions that occur except those
|
81
|
+
# with the `reportable?` flag set to false.
|
82
|
+
def with_reporting
|
83
|
+
yield
|
84
|
+
rescue => e
|
85
|
+
unless e.respond_to?(:reportable?) && !e.reportable?
|
86
|
+
# @examples
|
87
|
+
# Raven.capture_exception(e)
|
88
|
+
end
|
89
|
+
|
90
|
+
raise
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'exception_transformer/version'
|
2
|
+
require 'exception_transformer/transformer'
|
3
|
+
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
|
7
|
+
require 'byebug'
|
8
|
+
|
9
|
+
module ExceptionTransformer
|
10
|
+
def self.included base
|
11
|
+
base.send :include, InstanceMethods
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Add exceptions to be transformed in `handle_exceptions` block.
|
17
|
+
# @examples
|
18
|
+
# 1. Transform several errors to a single error:
|
19
|
+
#
|
20
|
+
# transform_exceptions FooError, BazError, to: BarError
|
21
|
+
#
|
22
|
+
# 2. Transform a single error based on it's message:
|
23
|
+
#
|
24
|
+
# transform_exceptions FooError, where: {
|
25
|
+
# /Invalid API key/i => BarError,
|
26
|
+
# :default => RuntimeError
|
27
|
+
# }
|
28
|
+
#
|
29
|
+
# To prevent *all* errors being caught via the `:default` branch,
|
30
|
+
# pass `use_default: false` to `handle_exceptions`.
|
31
|
+
#
|
32
|
+
# 3. Validate a response with a Proc that takes two parameters. The
|
33
|
+
# first parameter is the response, and the second is the calling method.
|
34
|
+
#
|
35
|
+
# transform_exceptions validate: proc { |response, action| ... }
|
36
|
+
#
|
37
|
+
# 4. Inspect an error with a Proc that takes two parameters. The
|
38
|
+
# first parameter is the error, and the second is the calling method.
|
39
|
+
#
|
40
|
+
# transform_exceptions with: proc { |err, action| ... }
|
41
|
+
def transform_exceptions(*exceptions, group: :default, to: nil, where: nil, with: nil, validate: nil)
|
42
|
+
strategies = { validate: validate, delegate: with, rewrite: to, regex: where }
|
43
|
+
|
44
|
+
strategy = strategies.keys.find { |s| strategies[s].present? }
|
45
|
+
target = strategies[strategy]
|
46
|
+
|
47
|
+
transformer = find_or_create_exception_transformer(group, strategy)
|
48
|
+
transformer.register_target(target, exceptions)
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_exception_transformer(group)
|
52
|
+
exception_transformers[group]
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_or_create_exception_transformer(group, strategy)
|
56
|
+
exception_transformers[group] ||= Transformer.new(strategy)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def exception_transformers
|
62
|
+
@exception_transformers ||= {}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module InstanceMethods
|
67
|
+
def handle_exceptions(group = :default, **opts)
|
68
|
+
# NOTE: `base_label` returns the label of this frame without decoration,
|
69
|
+
# i.e. if `label` was 'block in test', then `base_label` would be `test`.
|
70
|
+
calling_method = caller_locations(1, 1)[0].base_label
|
71
|
+
transformer = self.class.find_exception_transformer(group)
|
72
|
+
|
73
|
+
result = yield
|
74
|
+
|
75
|
+
transformer.after_yield(self, result, calling_method, opts)
|
76
|
+
|
77
|
+
result
|
78
|
+
rescue => e
|
79
|
+
transformer.after_rescue(self, e, calling_method, opts)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'exception_transformer/reportable'
|
2
|
+
require 'helpers/class_helpers'
|
3
|
+
|
4
|
+
describe ExceptionTransformer::Reportable do
|
5
|
+
include ClassHelpers
|
6
|
+
|
7
|
+
context 'when included' do
|
8
|
+
context 'in a class that is not an Exception' do
|
9
|
+
before(:each) { build_class :NotAnException }
|
10
|
+
|
11
|
+
it 'raises TypeError' do
|
12
|
+
expect { NotAnException.include ExceptionTransformer::Reportable }
|
13
|
+
.to raise_error(TypeError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'in an Exception' do
|
18
|
+
before(:each) { build_class :MyException, Exception }
|
19
|
+
|
20
|
+
it 'it should extend it with class methods' do
|
21
|
+
expect { MyException.include ExceptionTransformer::Reportable }
|
22
|
+
.to change { MyException.singleton_class.ancestors }
|
23
|
+
.to include(ExceptionTransformer::Reportable::ClassMethods)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
shared_context :reportable, define_reportable: true do
|
29
|
+
before(:each) do
|
30
|
+
build_class :MyException, Exception do
|
31
|
+
include ExceptionTransformer::Reportable
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
after(:each) { MyException.unload_reportable }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '::as_reportable', define_reportable: true do
|
39
|
+
it 'returns a subclass of ReportableException' do
|
40
|
+
expect(MyException.as_reportable).to be < ExceptionTransformer::ReportableException
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'is idempotent' do
|
44
|
+
reportable_exception = MyException.as_reportable
|
45
|
+
expect(reportable_exception).to equal(reportable_exception.as_reportable)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'the return value' do
|
49
|
+
it 'is a subclass of the caller' do
|
50
|
+
expect(MyException.as_reportable).to be < MyException
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'is a constant' do
|
54
|
+
expect(MyException.as_reportable.name).not_to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when raised' do
|
58
|
+
it 'is reportable' do
|
59
|
+
expect { raise MyException.as_reportable, 'oops!' }.to raise_error(be_reportable, 'oops!')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '::unload_reportable', define_reportable: true do
|
66
|
+
context 'when the reportable exception is defined' do
|
67
|
+
it 'removes the constant' do
|
68
|
+
reportable_exception = MyException.as_reportable
|
69
|
+
MyException.unload_reportable
|
70
|
+
expect(Object.const_defined?(reportable_exception.name)).to be false
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns it' do
|
74
|
+
reportable_exception = MyException.as_reportable
|
75
|
+
expect(MyException.unload_reportable).to equal(reportable_exception)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when the reportable exception is not defined' do
|
80
|
+
it 'returns nil' do
|
81
|
+
expect(MyException.unload_reportable).to be_nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#reportable?', define_reportable: true do
|
87
|
+
context 'when ReportableException is not included' do
|
88
|
+
context 'when the exception is raised' do
|
89
|
+
it 'is false' do
|
90
|
+
# assert !MyException.include?(ExceptionTransformer::ReportableException)
|
91
|
+
expect { raise MyException.new }.to raise_error do |e|
|
92
|
+
expect(e).not_to be_reportable
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#mark_reportable!', define_reportable: true do
|
100
|
+
it 'marks the exception as reportable' do
|
101
|
+
exception = MyException.new
|
102
|
+
expect { exception.mark_reportable! }
|
103
|
+
.to change { exception.reportable? }.from(false).to(true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'exception_transformer'
|
2
|
+
|
3
|
+
describe ExceptionTransformer do
|
4
|
+
class FooError < StandardError; end
|
5
|
+
class BarError < StandardError; end
|
6
|
+
|
7
|
+
class BazError < StandardError; end
|
8
|
+
class QuxError < StandardError; end
|
9
|
+
|
10
|
+
class ClassWithExceptionTransformer
|
11
|
+
include ExceptionTransformer
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_obj(&block)
|
15
|
+
Class.new(ClassWithExceptionTransformer, &block).new
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the result if there are no errors' do
|
19
|
+
obj = build_obj do
|
20
|
+
transform_exceptions FooError, to: BarError
|
21
|
+
def task; :task end
|
22
|
+
end
|
23
|
+
|
24
|
+
expect(obj.task).to be(:task)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should preserve exception messages' do
|
28
|
+
obj = build_obj do
|
29
|
+
transform_exceptions FooError, to: BarError
|
30
|
+
|
31
|
+
def task; handle_exceptions { raise FooError, 'oops' } end
|
32
|
+
end
|
33
|
+
|
34
|
+
expect{obj.task}.to raise_error(BarError, 'oops')
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when using :validate strategy' do
|
38
|
+
it 'transforms exceptions' do
|
39
|
+
obj = build_obj do
|
40
|
+
transform_exceptions validate: proc { |res, action|
|
41
|
+
unless res == :success
|
42
|
+
fail FooError
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
def task1; handle_exceptions { :success } end
|
47
|
+
def task2; handle_exceptions { :failure } end
|
48
|
+
end
|
49
|
+
|
50
|
+
expect{obj.task1}.not_to raise_error
|
51
|
+
expect{obj.task2}.to raise_error(FooError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'executes the proc in instance context' do
|
55
|
+
obj = build_obj do
|
56
|
+
transform_exceptions validate: proc { |res, action|
|
57
|
+
raise FooError unless @foo
|
58
|
+
}
|
59
|
+
|
60
|
+
def task1; handle_exceptions { @foo = :foo } end
|
61
|
+
def task2; handle_exceptions { @foo = nil } end
|
62
|
+
end
|
63
|
+
|
64
|
+
expect{obj.task1}.not_to raise_error
|
65
|
+
expect{obj.task2}.to raise_error(FooError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when using :delegate strategy' do
|
70
|
+
it 'transforms exceptions' do
|
71
|
+
obj = build_obj do
|
72
|
+
transform_exceptions with: proc { |e, action|
|
73
|
+
next unless e.is_a? FooError
|
74
|
+
raise BazError
|
75
|
+
}
|
76
|
+
|
77
|
+
def task1; handle_exceptions { raise FooError } end
|
78
|
+
def task2; handle_exceptions { raise BarError } end
|
79
|
+
end
|
80
|
+
|
81
|
+
expect{obj.task1}.to raise_error(BazError)
|
82
|
+
expect{obj.task2}.to raise_error(BarError)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'executes the proc in instance context' do
|
86
|
+
obj = build_obj do
|
87
|
+
transform_exceptions with: proc { |err, action|
|
88
|
+
if @foo
|
89
|
+
raise FooError
|
90
|
+
else
|
91
|
+
raise BarError
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
def task1
|
96
|
+
handle_exceptions do
|
97
|
+
@foo = :foo
|
98
|
+
raise
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def task2
|
103
|
+
handle_exceptions do
|
104
|
+
@foo = nil
|
105
|
+
raise
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
expect{obj.task1}.to raise_error(FooError)
|
111
|
+
expect{obj.task2}.to raise_error(BarError)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'doesnt decorate the caller_location label' do
|
115
|
+
obj = build_obj do
|
116
|
+
transform_exceptions with: proc { |res, action|
|
117
|
+
raise action
|
118
|
+
}
|
119
|
+
|
120
|
+
def task1
|
121
|
+
handle_exceptions { raise }
|
122
|
+
end
|
123
|
+
|
124
|
+
def task2
|
125
|
+
[:foo, :bar].each do |sym|
|
126
|
+
handle_exceptions { raise }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
expect{obj.task1}.to raise_error('task1')
|
132
|
+
expect{obj.task2}.to raise_error('task2')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should limit exception messages to certain length' do
|
137
|
+
obj = build_obj do
|
138
|
+
transform_exceptions FooError, to: BarError
|
139
|
+
|
140
|
+
def task; handle_exceptions { raise FooError, 'oops' * 50 } end
|
141
|
+
end
|
142
|
+
|
143
|
+
begin
|
144
|
+
obj.task
|
145
|
+
rescue BarError => e
|
146
|
+
expect(e.message.length).to be <= ExceptionTransformer::Transformer::MAX_MESSAGE_SIZE
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when using :rewrite strategy' do
|
151
|
+
it 'transforms exceptions' do
|
152
|
+
obj = build_obj do
|
153
|
+
transform_exceptions FooError, to: BarError
|
154
|
+
|
155
|
+
def task; handle_exceptions { raise FooError } end
|
156
|
+
end
|
157
|
+
|
158
|
+
expect{obj.task}.to raise_error(BarError)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'transforms exceptions by inheritance order' do
|
162
|
+
obj = build_obj do
|
163
|
+
transform_exceptions FooError, to: BarError
|
164
|
+
transform_exceptions StandardError, to: QuxError
|
165
|
+
|
166
|
+
def task1; handle_exceptions { raise FooError } end
|
167
|
+
def task2; handle_exceptions { raise ArgumentError} end
|
168
|
+
end
|
169
|
+
|
170
|
+
expect{obj.task1}.to raise_error(BarError)
|
171
|
+
expect{obj.task2}.to raise_error(QuxError)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'doesnt transform exceptions to be skipped' do
|
175
|
+
obj = build_obj do
|
176
|
+
transform_exceptions FooError, to: BarError
|
177
|
+
|
178
|
+
def task; handle_exceptions(except: [FooError]) { raise FooError, 'oops' } end
|
179
|
+
end
|
180
|
+
|
181
|
+
expect{obj.task}.to raise_error(FooError)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'when using :regex strategy' do
|
186
|
+
it 'is able to transform exceptions' do
|
187
|
+
obj = build_obj do
|
188
|
+
transform_exceptions FooError, where: {
|
189
|
+
/oops/ => BarError,
|
190
|
+
:default => StandardError
|
191
|
+
}
|
192
|
+
|
193
|
+
def task1; handle_exceptions { raise FooError, 'oops' } end
|
194
|
+
def task2; handle_exceptions { raise FooError } end
|
195
|
+
end
|
196
|
+
|
197
|
+
expect{obj.task1}.to raise_error(BarError)
|
198
|
+
expect{obj.task2}.to raise_error(StandardError)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should fallback to the default transformation unless specified' do
|
202
|
+
obj = build_obj do
|
203
|
+
transform_exceptions FooError, where: {
|
204
|
+
/oops/ => BarError,
|
205
|
+
:default => StandardError
|
206
|
+
}
|
207
|
+
|
208
|
+
def task1; handle_exceptions { raise FooError, 'oops' } end
|
209
|
+
|
210
|
+
def task2
|
211
|
+
handle_exceptions use_default: false do
|
212
|
+
raise FooError
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
expect{obj.task1}.to raise_error(BarError)
|
218
|
+
expect{obj.task2}.to raise_error(FooError)
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'should keep the exception message if < 100 characters' do
|
222
|
+
obj = build_obj do
|
223
|
+
transform_exceptions FooError, where: {
|
224
|
+
/oops/ => BarError,
|
225
|
+
:default => StandardError
|
226
|
+
}
|
227
|
+
|
228
|
+
def task1; handle_exceptions { raise FooError, 'oops it broke' } end
|
229
|
+
end
|
230
|
+
|
231
|
+
begin
|
232
|
+
obj.task1
|
233
|
+
rescue BarError => e
|
234
|
+
expect(e.message).to eq('oops it broke')
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should use a readable version of the regex if message > 100 characters' do
|
239
|
+
obj = build_obj do
|
240
|
+
transform_exceptions FooError, where: {
|
241
|
+
/oops/ => BarError,
|
242
|
+
:default => StandardError
|
243
|
+
}
|
244
|
+
|
245
|
+
def task1; handle_exceptions { raise FooError, 'oops' + ((0..9).to_a.join * 10) } end
|
246
|
+
end
|
247
|
+
|
248
|
+
begin
|
249
|
+
obj.task1
|
250
|
+
rescue BarError => e
|
251
|
+
expect(e.message).to eq('oops')
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe '::find_exception_transformer' do
|
257
|
+
let!(:obj) do
|
258
|
+
build_obj do
|
259
|
+
transform_exceptions FooError, to: BarError, group: :a
|
260
|
+
transform_exceptions FooError, to: BazError, group: :b
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'returns the transformer defined for the given group' do
|
265
|
+
transformer = obj.class.find_exception_transformer(:a)
|
266
|
+
|
267
|
+
expect(transformer).to be
|
268
|
+
expect(transformer.mappings).to eq(FooError => BarError)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#handle_exceptions' do
|
273
|
+
context 'when a group is provided' do
|
274
|
+
let!(:obj) do
|
275
|
+
build_obj do
|
276
|
+
transform_exceptions FooError, to: BarError, group: :a
|
277
|
+
transform_exceptions FooError, to: BazError, group: :b
|
278
|
+
|
279
|
+
def task(group)
|
280
|
+
handle_exceptions(group) { raise FooError }
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'should use the transformer defined for the given group' do
|
286
|
+
expect { obj.task(:a) }.to raise_error(BarError)
|
287
|
+
expect { obj.task(:b) }.to raise_error(BazError)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ClassHelpers
|
2
|
+
# Defines a named class, descending from `super_class`.
|
3
|
+
# The constant assigned to the the class will be restored
|
4
|
+
# to it's original state when an example completes.
|
5
|
+
#
|
6
|
+
# @param name [Symbol, String] the constant name
|
7
|
+
# @param super_class [Class] the class the return value inherits from
|
8
|
+
# @yield [] block passed to `Class.new`
|
9
|
+
# @return [Class]
|
10
|
+
def build_class(name, super_class = Object, &block)
|
11
|
+
stub_const(name.to_s, Class.new(super_class, &block))
|
12
|
+
end
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "exception_transformer"
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
# Enable flags like --only-failures and --next-failure
|
6
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
7
|
+
|
8
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
|
11
|
+
config.expect_with :rspec do |c|
|
12
|
+
c.syntax = :expect
|
13
|
+
end
|
14
|
+
|
15
|
+
config.expose_dsl_globally = true
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exception_transformer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick McLaren
|
8
|
+
- Allina Dolor
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2019-01-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.17'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.17'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: byebug
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '10.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '10.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: activesupport
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '5.0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '5.0'
|
84
|
+
description: Add exceptions to be transformed.
|
85
|
+
email:
|
86
|
+
- patrick@privy.com
|
87
|
+
- allina@privy.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- lib/exception_transformer.rb
|
93
|
+
- lib/exception_transformer/reportable.rb
|
94
|
+
- lib/exception_transformer/transformer.rb
|
95
|
+
- lib/exception_transformer/version.rb
|
96
|
+
- spec/exception_transformer/reportable_spec.rb
|
97
|
+
- spec/exception_transformer_spec.rb
|
98
|
+
- spec/helpers/class_helpers.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
homepage: https://github.com/Privy/exception-transformer
|
101
|
+
licenses:
|
102
|
+
- MIT
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.7.6
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Error Handling
|
124
|
+
test_files: []
|