nested_exceptions 1.0.0 → 1.0.1
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.
- data/README.md +75 -0
- data/examples/example.rb +66 -0
- data/lib/nested_exceptions.rb +41 -10
- data/lib/nested_exceptions/define.rb +2 -2
- data/lib/nested_exceptions/version.rb +1 -1
- metadata +5 -3
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# NestedExceptions
|
2
|
+
|
3
|
+
This simple library adds support for nested exceptions in Ruby.
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
It can be really painful to debug a situation where a library rescues an
|
8
|
+
exception and throws a different one, or when it has a bug in its
|
9
|
+
exception handling logic. This gem goes a diving catch and recovers the
|
10
|
+
play!
|
11
|
+
|
12
|
+
The best way to learn more about this topic is to investigate the great
|
13
|
+
source of information at the [Exceptional Ruby](http://exceptionalruby.com/) site.
|
14
|
+
|
15
|
+
## How do I use it?
|
16
|
+
|
17
|
+
The best way to use it is to simply add the following line of code to
|
18
|
+
your project:
|
19
|
+
|
20
|
+
require 'nested_exceptions/global'
|
21
|
+
|
22
|
+
That includes the NestedExceptions module in all of Ruby's base
|
23
|
+
exception classes (except the Exception root class), magically enabling
|
24
|
+
you to debug the trickiest, buggiest libraries.
|
25
|
+
|
26
|
+
### I'm not sure I want to include it globally
|
27
|
+
|
28
|
+
If you want to try it out without extending Ruby's exception classes,
|
29
|
+
you can also do the following:
|
30
|
+
|
31
|
+
require 'nested_exceptions'
|
32
|
+
|
33
|
+
class MyException < StandardError
|
34
|
+
include NestedExceptions
|
35
|
+
end
|
36
|
+
|
37
|
+
That will only extend your class and leave the rest of the exception
|
38
|
+
classes untouched.
|
39
|
+
|
40
|
+
## Does it change anything?
|
41
|
+
|
42
|
+
Yes. A bit of extra information is added to your backtraces. In addition
|
43
|
+
to the standard backtrace, you'll get a little bit of nesting
|
44
|
+
information.
|
45
|
+
|
46
|
+
ruby examples/example.rb original
|
47
|
+
|
48
|
+
examples/example.rb:32:in `rescue in double_bug': oops (RuntimeError)
|
49
|
+
from examples/example.rb:30:in `double_bug'
|
50
|
+
from examples/example.rb:42:in `<main>'
|
51
|
+
|
52
|
+
|
53
|
+
ruby examples/example.rb nested
|
54
|
+
|
55
|
+
examples/example.rb:30:in `rescue in double_bug': oops (RuntimeError)
|
56
|
+
from --- cause: ErrorSpec::InternalError: problem
|
57
|
+
from examples/example.rb:20:in `rescue in problem'
|
58
|
+
from --- cause: RuntimeError: bug
|
59
|
+
from examples/example.rb:10:in `bug'
|
60
|
+
from examples/example.rb:14:in `nested_bug'
|
61
|
+
from examples/example.rb:18:in `problem'
|
62
|
+
from examples/example.rb:24:in `nested_problem'
|
63
|
+
from examples/example.rb:28:in `double_bug'
|
64
|
+
from examples/example.rb:35:in `<main>'
|
65
|
+
|
66
|
+
It also adds two methods to the exception:
|
67
|
+
|
68
|
+
* #root_cause: allows you to easily get the first exception
|
69
|
+
* #cause: get the exception (if any) that may have caused this one
|
70
|
+
|
71
|
+
## Gotchas
|
72
|
+
|
73
|
+
Do not include the NestedExceptions module in a subclass before adding
|
74
|
+
it to its superclass. That will cause the nested exceptions feature to
|
75
|
+
deactivate itself to prevent a stack overflow.
|
data/examples/example.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
$: << './lib'
|
4
|
+
require 'nested_exceptions'
|
5
|
+
|
6
|
+
module ErrorSpec
|
7
|
+
|
8
|
+
NestedExceptions.define_standard_exception_classes_in ErrorSpec
|
9
|
+
|
10
|
+
class Example
|
11
|
+
def bug
|
12
|
+
raise 'bug'
|
13
|
+
end
|
14
|
+
|
15
|
+
def nested_bug
|
16
|
+
bug
|
17
|
+
end
|
18
|
+
|
19
|
+
def problem
|
20
|
+
nested_bug
|
21
|
+
rescue
|
22
|
+
raise InternalError, 'problem'
|
23
|
+
end
|
24
|
+
|
25
|
+
def nested_problem
|
26
|
+
problem
|
27
|
+
end
|
28
|
+
|
29
|
+
def double_bug
|
30
|
+
nested_problem
|
31
|
+
rescue
|
32
|
+
raise 'oops'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
example = ErrorSpec::Example.new
|
38
|
+
|
39
|
+
if ARGV.first == 'original'
|
40
|
+
|
41
|
+
puts "Original exception:"
|
42
|
+
example.double_bug
|
43
|
+
|
44
|
+
elsif ARGV.first == 'nested'
|
45
|
+
|
46
|
+
require 'nested_exceptions/global'
|
47
|
+
begin
|
48
|
+
example.double_bug
|
49
|
+
rescue StandardError => e
|
50
|
+
if Object.const_defined? :RUBY_ENGINE and (RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx')
|
51
|
+
puts "Note that JRuby and Rubinius do not display the modified backtrace:"
|
52
|
+
pp e.backtrace
|
53
|
+
puts
|
54
|
+
end
|
55
|
+
puts "Nested exception:"
|
56
|
+
raise
|
57
|
+
end
|
58
|
+
|
59
|
+
else
|
60
|
+
|
61
|
+
puts "Usage:"
|
62
|
+
puts
|
63
|
+
puts "ruby #{ $0 } [original|nested]"
|
64
|
+
|
65
|
+
end
|
66
|
+
|
data/lib/nested_exceptions.rb
CHANGED
@@ -6,18 +6,33 @@ module NestedExceptions
|
|
6
6
|
attr_reader :cause
|
7
7
|
|
8
8
|
def initialize(message = nil, cause = nil)
|
9
|
-
@cause
|
10
|
-
|
9
|
+
# @cause could be defined if this module is included multiple
|
10
|
+
# times in a class heirarchy.
|
11
|
+
@illegal_nesting = defined? @cause
|
12
|
+
@recursing = false
|
13
|
+
if @illegal_nesting
|
14
|
+
warn "WARNING: NestedExceptions is included in the class heirarchy of #{ self.class } more than once."
|
15
|
+
warn "- Ensure if you require 'nested_exceptions/global' or manually add"
|
16
|
+
warn " NestedExceptions to a built-in Ruby exception class, you must do it at"
|
17
|
+
warn " the beginning of the program."
|
18
|
+
else
|
19
|
+
@cause = cause || $!
|
20
|
+
super(message)
|
21
|
+
end
|
11
22
|
end
|
12
23
|
|
13
|
-
if Object.const_defined? :RUBY_ENGINE and RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx'
|
24
|
+
if Object.const_defined? :RUBY_ENGINE and (RUBY_ENGINE == 'jruby' or RUBY_ENGINE == 'rbx')
|
14
25
|
def backtrace
|
15
|
-
|
16
|
-
|
26
|
+
prevent_recursion do
|
27
|
+
return @processed_backtrace if defined? @processed_backtrace and @processed_backtrace
|
28
|
+
@processed_backtrace = process_backtrace(super)
|
29
|
+
end
|
17
30
|
end
|
18
31
|
else
|
19
32
|
def set_backtrace(bt)
|
20
|
-
|
33
|
+
prevent_recursion do
|
34
|
+
super process_backtrace(bt)
|
35
|
+
end
|
21
36
|
end
|
22
37
|
end
|
23
38
|
|
@@ -33,18 +48,34 @@ module NestedExceptions
|
|
33
48
|
|
34
49
|
protected
|
35
50
|
|
51
|
+
# This shouldn't be necessary anymore
|
52
|
+
def prevent_recursion
|
53
|
+
if not @recursing and not @illegal_nesting
|
54
|
+
begin
|
55
|
+
@recursing = true
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@recursing = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
36
63
|
def process_backtrace(bt)
|
37
|
-
|
38
|
-
|
64
|
+
bt ||= []
|
65
|
+
if @cause and not @illegal_nesting
|
66
|
+
cause_backtrace = @cause.backtrace || []
|
67
|
+
cause_backtrace.reverse.each do |line|
|
39
68
|
if bt.last == line
|
40
69
|
bt.pop
|
41
70
|
else
|
42
71
|
break
|
43
72
|
end
|
44
73
|
end
|
45
|
-
bt << "--- cause: #{cause.class
|
46
|
-
bt.concat
|
74
|
+
bt << "--- cause: #{@cause.class}: #{@cause}"
|
75
|
+
bt.concat cause_backtrace
|
47
76
|
end
|
48
77
|
bt
|
78
|
+
rescue Exception => e
|
79
|
+
warn "exception processing backtrace: #{ e.class }: #{ e.message }\n #{ caller(0).join("\n ") }"
|
49
80
|
end
|
50
81
|
end
|
@@ -5,9 +5,9 @@ module NestedExceptions
|
|
5
5
|
#
|
6
6
|
# I actually recommend that you just define these manually, but this may be
|
7
7
|
# a handy shortcut in smaller projects.
|
8
|
-
def define_standard_exception_classes_in(target_module)
|
8
|
+
def define_standard_exception_classes_in(target_module, add_module = false)
|
9
9
|
definitions = %{
|
10
|
-
class Error < StandardError; include NestedExceptions; end
|
10
|
+
class Error < StandardError; #{ add_module ? 'include NestedExceptions;' : '' } end
|
11
11
|
class UserError < Error; end
|
12
12
|
class LogicError < Error; end
|
13
13
|
class ClientError < LogicError; end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nested_exceptions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3005118107932468525
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Darrick Wiebe
|
@@ -45,7 +45,9 @@ files:
|
|
45
45
|
- .gitignore
|
46
46
|
- .rspec
|
47
47
|
- Gemfile
|
48
|
+
- README.md
|
48
49
|
- Rakefile
|
50
|
+
- examples/example.rb
|
49
51
|
- lib/nested_exceptions.rb
|
50
52
|
- lib/nested_exceptions/define.rb
|
51
53
|
- lib/nested_exceptions/global.rb
|