rescuer 0.0.1 → 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 +4 -4
- data/.gitignore +1 -0
- data/README.md +26 -1
- data/lib/rescuer.rb +156 -7
- data/lib/rescuer/version.rb +1 -1
- data/spec/rescuer_spec.rb +459 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4faa4200edef84a1567bb09eccfbbe6e1418867b
|
4
|
+
data.tar.gz: d7a26758830d969a3f828da1573164c1150b700e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32557bb01bd39b5b179bd00b9e168ad00142e3ba9e8a1280000976a210c959d3f0d73daf5794bd6be25bc9f89b214b7558e3f4720d803bc70c4af2a1cae0e5b1
|
7
|
+
data.tar.gz: 4e3fde18020cf6a940653e62a6d0d78d51c02f913b5e4da10dbc24abf823031b78d9c399e0f9d07a6f2d6219fc3a8f7cde99f5884cd27b5a278b813429a93b8e
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -5,4 +5,29 @@
|
|
5
5
|
[](https://codeclimate.com/github/ms-ati/rescuer)
|
6
6
|
[](https://coveralls.io/r/ms-ati/rescuer)
|
7
7
|
|
8
|
-
A functional abstraction of rescuing exceptions inspired by Scala's Try class
|
8
|
+
A functional abstraction of rescuing exceptions inspired by Scala's [Try][1] class (here's an [explanation][2]).
|
9
|
+
|
10
|
+
[1]: http://www.scala-lang.org/api/2.10.3/index.html#scala.util.Try
|
11
|
+
[2]: http://danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
denominators = [0, 1]
|
17
|
+
|
18
|
+
fractions = denominators.map { |d| Rescuer.new { 1 / d } }
|
19
|
+
#=> [#<struct Rescuer::Failure exception=#<ZeroDivisionError: divided by 0>, exceptions_to_rescue=nil>,
|
20
|
+
# #<struct Rescuer::Success value=1, exceptions_to_rescue=nil>]
|
21
|
+
```
|
22
|
+
|
23
|
+
More coming soon...
|
24
|
+
|
25
|
+
## TODO
|
26
|
+
|
27
|
+
1. Add any missing test to ensure exception rescuing matches all the same cases in Scala's `Try` class
|
28
|
+
1. Come up with output for `#to_s` and `#inspect` that I like
|
29
|
+
1. Document using yard
|
30
|
+
1. Release 0.9.0 and solicit comments
|
31
|
+
1. Incorporate suggested changes
|
32
|
+
1. Release 1.0.0
|
33
|
+
1. Potentially incorporate as a depedendency into [Rumonade](https://github.com/ms-ati/rumonade)
|
data/lib/rescuer.rb
CHANGED
@@ -1,16 +1,165 @@
|
|
1
1
|
require 'rescuer/version'
|
2
2
|
|
3
3
|
module Rescuer
|
4
|
-
|
4
|
+
DEFAULT_EXCEPTIONS_TO_RESCUE = [StandardError]
|
5
5
|
|
6
6
|
def new(*exceptions)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
raise ArgumentError, 'no block given' unless block_given?
|
8
|
+
|
9
|
+
passed = exceptions && exceptions.flatten.compact
|
10
|
+
to_rescue, to_pass =
|
11
|
+
if passed.nil? || passed.empty? then [DEFAULT_EXCEPTIONS_TO_RESCUE, nil] else [passed, passed] end
|
12
|
+
|
13
|
+
begin
|
14
|
+
value = yield
|
15
|
+
Success.new(value, to_pass)
|
16
|
+
rescue *to_rescue => error
|
17
|
+
Failure.new(error, to_pass)
|
18
|
+
end
|
11
19
|
end
|
12
20
|
module_function :new
|
13
21
|
|
14
|
-
Success = Struct.new(:value)
|
15
|
-
|
22
|
+
Success = Struct.new(:value, :exceptions_to_rescue) do
|
23
|
+
def initialize(value, exceptions_to_rescue = nil)
|
24
|
+
super(value, exceptions_to_rescue)
|
25
|
+
freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def get
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_or_else(_)
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
44
|
+
def or_else(_)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def failed
|
49
|
+
Failure.new(TypeError.new('Success is not a Failure'))
|
50
|
+
end
|
51
|
+
|
52
|
+
def flat_map
|
53
|
+
Rescuer.new(exceptions_to_rescue) { yield value }.flatten(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
def map
|
57
|
+
flat_map do |v|
|
58
|
+
new_value = yield v
|
59
|
+
Success.new(new_value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def each
|
64
|
+
yield value
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def select
|
69
|
+
flat_map do |v|
|
70
|
+
predicate = yield v
|
71
|
+
if predicate then self else Failure.new(IndexError.new("predicate does not hold for #{v}")) end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
alias_method :find_all, :select
|
75
|
+
|
76
|
+
def transform(f_success, f_failure)
|
77
|
+
flat_map { |v| f_success.call(v) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def recover
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def recover_with
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def flatten(depth = nil)
|
89
|
+
raise ArgumentError, 'invalid depth' unless depth.nil? || (depth.is_a?(Integer) && depth >= 0)
|
90
|
+
if depth && depth.zero?
|
91
|
+
self
|
92
|
+
else
|
93
|
+
case value
|
94
|
+
when Success, Failure then value.flatten(depth && depth - 1)
|
95
|
+
else self
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Failure = Struct.new(:exception, :exceptions_to_rescue) do
|
102
|
+
def initialize(exception, exceptions_to_rescue = nil)
|
103
|
+
super(exception, exceptions_to_rescue)
|
104
|
+
raise ArgumentError, 'not an exception' unless exception.is_a? Exception
|
105
|
+
freeze
|
106
|
+
end
|
107
|
+
|
108
|
+
def success?
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def failure?
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def get
|
117
|
+
raise exception
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_or_else(default)
|
121
|
+
default
|
122
|
+
end
|
123
|
+
|
124
|
+
def or_else(other)
|
125
|
+
other
|
126
|
+
end
|
127
|
+
|
128
|
+
def failed
|
129
|
+
Success.new(exception)
|
130
|
+
end
|
131
|
+
|
132
|
+
def flat_map
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def map
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def each
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def select
|
145
|
+
self
|
146
|
+
end
|
147
|
+
alias_method :find_all, :select
|
148
|
+
|
149
|
+
def transform(f_success, f_failure)
|
150
|
+
failed.flat_map { |e| f_failure.call(e) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def recover
|
154
|
+
failed.map { |e| yield e }
|
155
|
+
end
|
156
|
+
|
157
|
+
def recover_with
|
158
|
+
failed.flat_map { |e| yield e }
|
159
|
+
end
|
160
|
+
|
161
|
+
def flatten(depth = nil)
|
162
|
+
self
|
163
|
+
end
|
164
|
+
end
|
16
165
|
end
|
data/lib/rescuer/version.rb
CHANGED
data/spec/rescuer_spec.rb
CHANGED
@@ -3,6 +3,9 @@ require 'rescuer'
|
|
3
3
|
|
4
4
|
describe Rescuer do
|
5
5
|
|
6
|
+
##
|
7
|
+
# Construct indirectly by wrapping a block which may raise an exception
|
8
|
+
##
|
6
9
|
describe '.new' do
|
7
10
|
|
8
11
|
context 'when block does *not* raise' do
|
@@ -27,26 +30,476 @@ describe Rescuer do
|
|
27
30
|
context 'when raise NoMemoryError' do
|
28
31
|
let(:e) { NoMemoryError.new }
|
29
32
|
subject { Rescuer.new(Exception) { raise e } }
|
30
|
-
it { is_expected.to eq(Rescuer::Failure.new(e)) }
|
33
|
+
it { is_expected.to eq(Rescuer::Failure.new(e, [Exception])) }
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
37
|
context 'when block raises and two subclasses of Exception are arguments' do
|
35
|
-
exceptions
|
38
|
+
let(:exceptions) { [NoMemoryError, SyntaxError] }
|
36
39
|
|
37
40
|
context 'when raise StandardError' do
|
38
41
|
subject { lambda { Rescuer.new(exceptions) { raise StandardError } } }
|
39
42
|
it { is_expected.to raise_error(StandardError) }
|
40
43
|
end
|
41
44
|
|
42
|
-
context 'when raise
|
43
|
-
let(:e) {
|
44
|
-
subject { Rescuer.new(
|
45
|
-
it { is_expected.to eq(Rescuer::Failure.new(e)) }
|
45
|
+
context 'when raise SyntaxError' do
|
46
|
+
let(:e) { SyntaxError.new }
|
47
|
+
subject { Rescuer.new(exceptions) { raise e } }
|
48
|
+
it { is_expected.to eq(Rescuer::Failure.new(e, exceptions)) }
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when no block given' do
|
54
|
+
subject { lambda { Rescuer.new } }
|
55
|
+
it { is_expected.to raise_error(ArgumentError, 'no block given') }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Construct directly as Success
|
62
|
+
##
|
63
|
+
describe Rescuer::Success do
|
64
|
+
context 'when given any object' do
|
65
|
+
let(:the_value) { 42 }
|
66
|
+
let(:a_success) { Rescuer::Success.new(the_value) }
|
67
|
+
|
68
|
+
describe '.new' do
|
69
|
+
subject { a_success }
|
70
|
+
it { is_expected.to be_frozen }
|
71
|
+
it { is_expected.to be_success }
|
72
|
+
it { is_expected.not_to be_failure }
|
73
|
+
it { is_expected.to respond_to :value }
|
74
|
+
it { is_expected.not_to respond_to :exception }
|
75
|
+
it { is_expected.to respond_to :exceptions_to_rescue }
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#==, #eql?, #equal?, #hash' do
|
79
|
+
subject { a_success }
|
80
|
+
|
81
|
+
context 'when same object' do
|
82
|
+
let(:another) { a_success }
|
83
|
+
it { is_expected.to eq another }
|
84
|
+
it { is_expected.to eql another }
|
85
|
+
it { is_expected.to equal another }
|
86
|
+
it { expect(subject.hash).to eq another.hash }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when same class and equal values' do
|
90
|
+
let(:another) { Rescuer::Success.new(the_value) }
|
91
|
+
it { is_expected.to eq another }
|
92
|
+
it { is_expected.to eql another }
|
93
|
+
it { is_expected.not_to equal another } # not the same object!
|
94
|
+
it { expect(subject.hash).to eq another.hash }
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when same class and different values' do
|
98
|
+
let(:another) { Rescuer::Success.new('a different value') }
|
99
|
+
it { is_expected.not_to eq another }
|
100
|
+
it { is_expected.not_to eql another }
|
101
|
+
it { is_expected.not_to equal another }
|
102
|
+
it { expect(subject.hash).not_to eq another.hash }
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when different class and same values' do
|
106
|
+
let(:another_class) { Struct.new(*a_success.members) }
|
107
|
+
let(:another) { another_class.new(*a_success.values) }
|
108
|
+
it { is_expected.not_to eq another }
|
109
|
+
it { is_expected.not_to eql another }
|
110
|
+
it { is_expected.not_to equal another }
|
111
|
+
#it { expect(subject.hash).not_to eq another.hash } -- fails on rbx?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#value' do
|
116
|
+
subject { a_success.value }
|
117
|
+
it { is_expected.to be the_value}
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#exceptions_to_rescue' do
|
121
|
+
context 'when not given' do
|
122
|
+
subject { a_success.exceptions_to_rescue }
|
123
|
+
it { is_expected.to be_nil }
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when given' do
|
127
|
+
let(:ex_to_rescue) { [ArgumentError, TypeError] }
|
128
|
+
subject { Rescuer::Success.new(the_value, ex_to_rescue).exceptions_to_rescue }
|
129
|
+
it { is_expected.to be ex_to_rescue }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#get' do
|
134
|
+
subject { a_success.get }
|
135
|
+
it { is_expected.to be the_value }
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#get_or_else' do
|
139
|
+
subject { a_success.get_or_else(123) }
|
140
|
+
it { is_expected.to be the_value }
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#or_else' do
|
144
|
+
let(:another) { Rescuer::Success.new(123) }
|
145
|
+
subject { a_success.or_else(another) }
|
146
|
+
it { is_expected.to be a_success }
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#failed' do
|
150
|
+
subject { a_success.failed }
|
151
|
+
it { is_expected.to be_instance_of Rescuer::Failure }
|
152
|
+
it { expect(subject.exception).to eq TypeError.new('Success is not a Failure') }
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#each' do
|
156
|
+
context 'when block does *not* raise' do
|
157
|
+
let(:acc) { arr = []; a_success.each { |v| arr << v }; arr }
|
158
|
+
subject { dummy = []; a_success.each { |v| dummy << v } }
|
159
|
+
it { is_expected.to be a_success }
|
160
|
+
it { expect(acc).to eq [the_value] }
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when block raises' do
|
164
|
+
let(:the_error) { StandardError.new('a standard error') }
|
165
|
+
subject { lambda { a_success.each { |_| raise the_error } } }
|
166
|
+
it { is_expected.to raise_error(the_error) }
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe '#map' do
|
171
|
+
let(:to_rescue) { [ArgumentError] }
|
172
|
+
let(:a_success_rescuing_ae) { Rescuer::Success.new(the_value, to_rescue) }
|
173
|
+
|
174
|
+
context 'when block returns a value' do
|
175
|
+
subject { a_success.map { |v| v + 1 } }
|
176
|
+
it { is_expected.to eq Rescuer::Success.new(the_value + 1) }
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'when block raises an exception that is to be rescued' do
|
180
|
+
let(:the_error) { ArgumentError.new('an error to be rescued') }
|
181
|
+
subject { a_success_rescuing_ae.map { |_| raise the_error } }
|
182
|
+
it { is_expected.to eq Rescuer::Failure.new(the_error, to_rescue) }
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'when block raises an exception that is *not* to be rescued' do
|
186
|
+
let(:the_error) { StandardError.new('a standard error') }
|
187
|
+
subject { lambda { a_success_rescuing_ae.map { |_| raise the_error } } }
|
188
|
+
it { is_expected.to raise_error(the_error) }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe '#flat_map' do
|
193
|
+
let(:to_rescue) { [ArgumentError] }
|
194
|
+
let(:a_success_rescuing_ae) { Rescuer::Success.new(the_value, to_rescue) }
|
195
|
+
|
196
|
+
context 'when block returns a Success' do
|
197
|
+
subject { a_success.flat_map { |v| Rescuer::Success.new(v + 1) } }
|
198
|
+
it { is_expected.to eq Rescuer::Success.new(the_value + 1) }
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when block raises an exception that is to be rescued' do
|
202
|
+
let(:the_error) { ArgumentError.new('an error to be rescued') }
|
203
|
+
subject { a_success_rescuing_ae.flat_map { |_| raise the_error } }
|
204
|
+
it { is_expected.to eq Rescuer::Failure.new(the_error, to_rescue) }
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'when block raises an exception that is *not* to be rescued' do
|
208
|
+
let(:the_error) { StandardError.new('a standard error') }
|
209
|
+
subject { lambda { a_success_rescuing_ae.flat_map { |_| raise the_error } } }
|
210
|
+
it { is_expected.to raise_error(the_error) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
shared_examples 'select methods' do
|
215
|
+
context 'when predicate returns true' do
|
216
|
+
subject { select_method { |v| v == the_value } }
|
217
|
+
it { is_expected.to be a_success }
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'when predicate returns false' do
|
221
|
+
let(:the_error) { IndexError.new('predicate does not hold for 42') }
|
222
|
+
subject { select_method { |v| v != the_value } }
|
223
|
+
it { is_expected.to eq Rescuer::Failure.new(the_error) }
|
224
|
+
end
|
225
|
+
|
226
|
+
context 'when predicate raises an exception that is to be rescued' do
|
227
|
+
let(:the_error) { StandardError.new('a standard error') }
|
228
|
+
subject { select_method { |_| raise the_error } }
|
229
|
+
it { is_expected.to eq Rescuer::Failure.new(the_error) }
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'when predicate raises an exception that is *not* to be rescued' do
|
233
|
+
subject { lambda { select_method { |_| raise NoMemoryError } } }
|
234
|
+
it { is_expected.to raise_error(NoMemoryError) }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#select' do
|
239
|
+
def select_method; a_success.select { |v| yield v }; end
|
240
|
+
it_behaves_like 'select methods'
|
241
|
+
end
|
242
|
+
|
243
|
+
describe '#find_all' do
|
244
|
+
def select_method; a_success.find_all { |v| yield v }; end
|
245
|
+
it_behaves_like 'select methods'
|
246
|
+
end
|
247
|
+
|
248
|
+
describe '#transform' do
|
249
|
+
subject { a_success.transform(lambda { |v| Rescuer::Success.new(v + 1) },
|
250
|
+
lambda { |e| Rescuer::Success.new(e.message.length) }) }
|
251
|
+
it { is_expected.to eq Rescuer::Success.new(the_value + 1) }
|
252
|
+
end
|
253
|
+
|
254
|
+
describe '#recover' do
|
255
|
+
subject { a_success.recover { |e| e.message.length } }
|
256
|
+
it { is_expected.to be a_success }
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#recover_with' do
|
260
|
+
subject { a_success.recover_with { |e| Rescuer::Success.new(e.message.length) } }
|
261
|
+
it { is_expected.to be a_success }
|
262
|
+
end
|
263
|
+
|
264
|
+
describe '#flatten' do
|
265
|
+
let(:nested_once) { Rescuer::Success.new(a_success) }
|
266
|
+
let(:nested_twice) { Rescuer::Success.new(nested_once) }
|
267
|
+
let(:nested_fail) { Rescuer::Success.new(Rescuer::Failure.new(StandardError.new('a standard error'))) }
|
268
|
+
|
269
|
+
context 'given an invalid depth' do
|
270
|
+
let(:string_depth) { lambda { a_success.flatten('hey!') } }
|
271
|
+
let(:negative_depth) { lambda { a_success.flatten(-1) } }
|
272
|
+
let(:float_depth) { lambda { a_success.flatten(1.23) } }
|
273
|
+
it { expect(string_depth).to raise_error(ArgumentError, 'invalid depth') }
|
274
|
+
it { expect(negative_depth).to raise_error(ArgumentError, 'invalid depth') }
|
275
|
+
it { expect(float_depth).to raise_error(ArgumentError, 'invalid depth') }
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'when *not* nested' do
|
279
|
+
context 'given depth is nil' do
|
280
|
+
subject { a_success.flatten }
|
281
|
+
it { is_expected.to be a_success }
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'given depth = 1' do
|
285
|
+
subject { a_success.flatten(1) }
|
286
|
+
it { is_expected.to be a_success }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
context 'when nested success containing success' do
|
291
|
+
context 'given depth is nil' do
|
292
|
+
subject { nested_once.flatten }
|
293
|
+
it { is_expected.to be a_success }
|
294
|
+
end
|
295
|
+
|
296
|
+
context 'given depth = 1' do
|
297
|
+
subject { nested_once.flatten(1) }
|
298
|
+
it { is_expected.to be a_success }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context 'when nested success containing success containing success' do
|
303
|
+
context 'given depth is nil' do
|
304
|
+
subject { nested_twice.flatten }
|
305
|
+
it { is_expected.to be a_success }
|
306
|
+
end
|
307
|
+
|
308
|
+
context 'given depth = 1' do
|
309
|
+
subject { nested_twice.flatten(1) }
|
310
|
+
it { is_expected.to be nested_once }
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context 'when nested success containing failure' do
|
315
|
+
context 'given depth is nil' do
|
316
|
+
subject { nested_fail.flatten }
|
317
|
+
it { is_expected.to be nested_fail.value }
|
318
|
+
end
|
319
|
+
|
320
|
+
context 'given depth = 1' do
|
321
|
+
subject { nested_fail.flatten(1) }
|
322
|
+
it { is_expected.to be nested_fail.value }
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
##
|
330
|
+
# Construct directly as Failure
|
331
|
+
##
|
332
|
+
describe Rescuer::Failure do
|
333
|
+
context 'when given an exception' do
|
334
|
+
let(:the_error) { StandardError.new('a standard error') }
|
335
|
+
let(:a_failure) { Rescuer::Failure.new(the_error) }
|
336
|
+
|
337
|
+
describe '.new' do
|
338
|
+
subject { a_failure }
|
339
|
+
it { is_expected.to be_frozen }
|
340
|
+
it { is_expected.not_to be_success }
|
341
|
+
it { is_expected.to be_failure }
|
342
|
+
it { is_expected.not_to respond_to :value }
|
343
|
+
it { is_expected.to respond_to :exception }
|
344
|
+
it { is_expected.to respond_to :exceptions_to_rescue }
|
345
|
+
end
|
346
|
+
|
347
|
+
describe '#==, #eql?, #equal?, #hash' do
|
348
|
+
subject { a_failure }
|
349
|
+
|
350
|
+
context 'when same object' do
|
351
|
+
let(:another) { a_failure }
|
352
|
+
it { is_expected.to eq another }
|
353
|
+
it { is_expected.to eql another }
|
354
|
+
it { is_expected.to equal another }
|
355
|
+
it { expect(subject.hash).to eq another.hash }
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'when same class and equal values' do
|
359
|
+
let(:another) { Rescuer::Failure.new(the_error) }
|
360
|
+
it { is_expected.to eq another }
|
361
|
+
it { is_expected.to eql another }
|
362
|
+
it { is_expected.not_to equal another } # not the same object!
|
363
|
+
it { expect(subject.hash).to eq another.hash }
|
364
|
+
end
|
365
|
+
|
366
|
+
context 'when same class and different values' do
|
367
|
+
let(:another) { Rescuer::Failure.new(StandardError.new('a different error')) }
|
368
|
+
it { is_expected.not_to eq another }
|
369
|
+
it { is_expected.not_to eql another }
|
370
|
+
it { is_expected.not_to equal another }
|
371
|
+
it { expect(subject.hash).not_to eq another.hash }
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'when different class and same values' do
|
375
|
+
let(:another_class) { Struct.new(*a_failure.members) }
|
376
|
+
let(:another) { another_class.new(*a_failure.values) }
|
377
|
+
it { is_expected.not_to eq another }
|
378
|
+
it { is_expected.not_to eql another }
|
379
|
+
it { is_expected.not_to equal another }
|
380
|
+
#it { expect(subject.hash).not_to eq another.hash } -- fails on rbx?
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe '#exception' do
|
385
|
+
subject { a_failure.exception }
|
386
|
+
it { is_expected.to be the_error }
|
387
|
+
end
|
388
|
+
|
389
|
+
describe '#exceptions_to_rescue' do
|
390
|
+
context 'when not given' do
|
391
|
+
subject { a_failure.exceptions_to_rescue }
|
392
|
+
it { is_expected.to be_nil }
|
393
|
+
end
|
394
|
+
|
395
|
+
context 'when given' do
|
396
|
+
let(:ex_to_rescue) { [ArgumentError, TypeError] }
|
397
|
+
subject { Rescuer::Failure.new(the_error, ex_to_rescue).exceptions_to_rescue }
|
398
|
+
it { is_expected.to be ex_to_rescue }
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
describe '#get' do
|
403
|
+
subject { lambda { a_failure.get } }
|
404
|
+
it { is_expected.to raise_error(the_error) }
|
46
405
|
end
|
47
406
|
|
407
|
+
describe '#get_or_else' do
|
408
|
+
subject { a_failure.get_or_else(123) }
|
409
|
+
it { is_expected.to be 123 }
|
410
|
+
end
|
411
|
+
|
412
|
+
describe '#or_else' do
|
413
|
+
let(:another) { Rescuer::Success.new(123) }
|
414
|
+
subject { a_failure.or_else(another) }
|
415
|
+
it { is_expected.to be another }
|
416
|
+
end
|
417
|
+
|
418
|
+
describe '#failed' do
|
419
|
+
subject { a_failure.failed }
|
420
|
+
it { is_expected.to be_instance_of Rescuer::Success }
|
421
|
+
it { expect(subject.value).to be the_error }
|
422
|
+
end
|
423
|
+
|
424
|
+
describe '#each' do
|
425
|
+
let(:acc) { arr = []; a_failure.each { |v| arr << v }; arr }
|
426
|
+
subject { dummy = []; a_failure.each { |v| dummy << v } }
|
427
|
+
it { is_expected.to be a_failure }
|
428
|
+
it { expect(acc).to eq [] }
|
429
|
+
end
|
430
|
+
|
431
|
+
describe '#map' do
|
432
|
+
subject { a_failure.map { |v| v + 1 } }
|
433
|
+
it { is_expected.to be a_failure }
|
434
|
+
end
|
435
|
+
|
436
|
+
describe '#flat_map' do
|
437
|
+
subject { a_failure.flat_map { |v| Rescuer::Success.new(v + 1) } }
|
438
|
+
it { is_expected.to be a_failure }
|
439
|
+
end
|
440
|
+
|
441
|
+
shared_examples 'select methods' do
|
442
|
+
context 'when predicate returns true' do
|
443
|
+
subject { select_method { |v| v == the_value } }
|
444
|
+
it { is_expected.to be a_failure }
|
445
|
+
end
|
446
|
+
|
447
|
+
context 'when predicate returns false' do
|
448
|
+
subject { select_method { |v| v != the_value } }
|
449
|
+
it { is_expected.to be a_failure }
|
450
|
+
end
|
451
|
+
|
452
|
+
context 'when predicate raises any exception' do
|
453
|
+
subject { select_method { |_| raise the_error } }
|
454
|
+
it { is_expected.to be a_failure }
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
describe '#select' do
|
459
|
+
def select_method; a_failure.select { |v| yield v }; end
|
460
|
+
it_behaves_like 'select methods'
|
461
|
+
end
|
462
|
+
|
463
|
+
describe '#find_all' do
|
464
|
+
def select_method; a_failure.find_all { |v| yield v }; end
|
465
|
+
it_behaves_like 'select methods'
|
466
|
+
end
|
467
|
+
|
468
|
+
describe '#transform' do
|
469
|
+
subject { a_failure.transform(lambda { |v| Rescuer::Success.new(v + 1) },
|
470
|
+
lambda { |e| Rescuer::Success.new(e.message.length) }) }
|
471
|
+
it { is_expected.to eq Rescuer::Success.new(the_error.message.length) }
|
472
|
+
end
|
473
|
+
|
474
|
+
describe '#recover' do
|
475
|
+
subject { a_failure.recover { |e| e.message.length } }
|
476
|
+
it { is_expected.to eq Rescuer::Success.new(the_error.message.length) }
|
477
|
+
end
|
478
|
+
|
479
|
+
describe '#recover_with' do
|
480
|
+
subject { a_failure.recover_with { |e| Rescuer::Success.new(e.message.length) } }
|
481
|
+
it { is_expected.to eq Rescuer::Success.new(the_error.message.length) }
|
482
|
+
end
|
483
|
+
|
484
|
+
describe '#flatten' do
|
485
|
+
context 'given depth is nil' do
|
486
|
+
subject { a_failure.flatten }
|
487
|
+
it { is_expected.to be a_failure }
|
488
|
+
end
|
489
|
+
|
490
|
+
context 'given depth = 1' do
|
491
|
+
subject { a_failure.flatten(1) }
|
492
|
+
it { is_expected.to be a_failure }
|
493
|
+
end
|
494
|
+
end
|
48
495
|
end
|
49
496
|
|
497
|
+
context 'when given a *non*-exception' do
|
498
|
+
describe '.new' do
|
499
|
+
subject { lambda { Rescuer::Failure.new(42) } }
|
500
|
+
it { is_expected.to raise_error(ArgumentError, 'not an exception') }
|
501
|
+
end
|
502
|
+
end
|
50
503
|
end
|
51
504
|
|
52
505
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rescuer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Siegel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|