stoplight 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +78 -96
- data/lib/stoplight/default.rb +3 -12
- data/lib/stoplight/error.rb +13 -0
- data/lib/stoplight/light.rb +16 -27
- data/lib/stoplight/light/runnable.rb +4 -9
- data/lib/stoplight/version.rb +1 -1
- data/spec/stoplight/default_spec.rb +12 -32
- data/spec/stoplight/light/runnable_spec.rb +21 -75
- data/spec/stoplight/light_spec.rb +24 -53
- metadata +9 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97f3aac00bec5d8f4a86a709adacab7e81246b2c
|
4
|
+
data.tar.gz: ed4f8f332e6111245ff0c6555b337bded9f417a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43be4295193fbd9977303ddb15baf0d1ef3a4f04eeea57302d58e89d97ed3af2066b4c37294d335213f2b5841b987bf3b0e2a74eda00bff619e1504092ada523
|
7
|
+
data.tar.gz: 5b8e1cfda7283a49082308f9f5d9f696ca2508066ddba7707240cc88e31e61f6f2d9611600c0468c68468eef0eb3851a16b1a1f1e85520c5618f043ddbe4fa62
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# [Stoplight][]
|
2
2
|
|
3
|
-
[![Version][]]
|
4
|
-
[![Build][]]
|
5
|
-
[![Coverage][]]
|
6
|
-
[![Grade][]]
|
7
|
-
[![Climate][]]
|
8
|
-
[![Dependencies][]]
|
3
|
+
[![Version badge][]][version]
|
4
|
+
[![Build badge][]][build]
|
5
|
+
[![Coverage badge][]][coverage]
|
6
|
+
[![Grade badge][]][grade]
|
7
|
+
[![Climate badge][]][climate]
|
8
|
+
[![Dependencies badge][]][dependencies]
|
9
9
|
|
10
10
|
Stoplight is traffic control for code. It's an implementation of the circuit
|
11
11
|
breaker pattern in Ruby.
|
@@ -23,7 +23,7 @@ Check out [stoplight-admin][] for controlling your stoplights.
|
|
23
23
|
- [Custom errors](#custom-errors)
|
24
24
|
- [Custom fallback](#custom-fallback)
|
25
25
|
- [Custom threshold](#custom-threshold)
|
26
|
-
- [Custom
|
26
|
+
- [Custom cool off time](#custom-cool-off-time)
|
27
27
|
- [Rails](#rails)
|
28
28
|
- [Setup](#setup)
|
29
29
|
- [Data store](#data-store)
|
@@ -57,14 +57,14 @@ $ gem install stoplight
|
|
57
57
|
Stoplight uses [Semantic Versioning][]. Check out [the change log][] for a
|
58
58
|
detailed list of changes.
|
59
59
|
|
60
|
-
Stoplight works with all supported versions of Ruby (2.
|
60
|
+
Stoplight works with all supported versions of Ruby (2.1 through 2.3).
|
61
61
|
|
62
62
|
## Basic usage
|
63
63
|
|
64
64
|
To get started, create a stoplight:
|
65
65
|
|
66
66
|
``` rb
|
67
|
-
light = Stoplight('example-
|
67
|
+
light = Stoplight('example-pi') { 22.0 / 7 }
|
68
68
|
# => #<Stoplight::Light:...>
|
69
69
|
```
|
70
70
|
|
@@ -84,7 +84,7 @@ stoplight. That's not very interesting though, so let's create a failing
|
|
84
84
|
stoplight:
|
85
85
|
|
86
86
|
``` rb
|
87
|
-
light = Stoplight('example-
|
87
|
+
light = Stoplight('example-zero') { 1 / 0 }
|
88
88
|
# => #<Stoplight::Light:...>
|
89
89
|
```
|
90
90
|
|
@@ -99,10 +99,10 @@ light.run
|
|
99
99
|
light.run
|
100
100
|
# ZeroDivisionError: divided by 0
|
101
101
|
light.run
|
102
|
-
# Switching example-
|
102
|
+
# Switching example-zero from green to red because ZeroDivisionError divided by 0
|
103
103
|
# ZeroDivisionError: divided by 0
|
104
104
|
light.run
|
105
|
-
# Stoplight::Error::RedLight: example-
|
105
|
+
# Stoplight::Error::RedLight: example-zero
|
106
106
|
light.color
|
107
107
|
# => "red"
|
108
108
|
```
|
@@ -112,70 +112,47 @@ notifier. See [the notifiers section][] to learn more about notifiers.
|
|
112
112
|
|
113
113
|
The stoplight will move into the yellow state after being in the red state for
|
114
114
|
a while. (The yellow state corresponds to the half open state for circuit
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
breakers.) To configure how long it takes to switch into the yellow state,
|
116
|
+
check out [the cool off time section][] When stoplights are yellow, they will
|
117
|
+
try to run their code. If it fails, they'll switch back to red. If it succeeds,
|
118
|
+
they'll switch to green.
|
119
119
|
|
120
120
|
### Custom errors
|
121
121
|
|
122
|
-
##### Whitelisted errors
|
123
|
-
|
124
122
|
Some errors shouldn't cause your stoplight to move into the red state. Usually
|
125
123
|
these are handled elsewhere in your stack and don't represent real failures. A
|
126
124
|
good example is `ActiveRecord::RecordNotFound`.
|
127
125
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
# => #<Stoplight::Light:...>
|
132
|
-
light.run
|
133
|
-
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
134
|
-
light.run
|
135
|
-
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
136
|
-
light.run
|
137
|
-
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
138
|
-
light.color
|
139
|
-
# => "green"
|
140
|
-
```
|
141
|
-
|
142
|
-
The following errors are always whitelisted: `NoMemoryError`, `ScriptError`,
|
143
|
-
`SecurityError`, `SignalException`, `SystemExit`, and `SystemStackError`.
|
126
|
+
To prevent some errors from changing the state of your stoplight, you can
|
127
|
+
provide a custom block that will be called with the error and a handler
|
128
|
+
`Proc`. It can do one of three things:
|
144
129
|
|
145
|
-
|
130
|
+
1. Re-raise the error. This causes Stoplight to ignore the error. Do this for
|
131
|
+
errors like `ActiveRecord::RecordNotFound` that don't represent real
|
132
|
+
failures.
|
146
133
|
|
147
|
-
|
134
|
+
2. Call the handler with the error. This is the default behavior. Stoplight
|
135
|
+
will only ignore the error if it shouldn't have been caught in the first
|
136
|
+
place. See `Stoplight::Error::AVOID_RESCUING` for a list of errors that
|
137
|
+
will be ignored.
|
148
138
|
|
149
|
-
|
150
|
-
|
139
|
+
3. Do nothing. This is **not recommended**. Doing nothing causes Stoplight to
|
140
|
+
never ignore the error. That means a `NoMemoryError` could change the color
|
141
|
+
of your stoplights.
|
151
142
|
|
152
143
|
``` rb
|
153
|
-
light = Stoplight('example-
|
154
|
-
.
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
light.run
|
159
|
-
# ZeroDivisionError: divided by 0
|
160
|
-
light.run
|
161
|
-
# ZeroDivisionError: divided by 0
|
162
|
-
light.color
|
163
|
-
# => "red"
|
164
|
-
```
|
165
|
-
|
166
|
-
This will cause all other errors to be raised normally. They won't affect the
|
167
|
-
state of your stoplight.
|
168
|
-
|
169
|
-
``` rb
|
170
|
-
light = Stoplight('example-5') { fail }
|
171
|
-
.with_blacklisted_errors([ZeroDivisionError])
|
144
|
+
light = Stoplight('example-not-found') { User.find(123) }
|
145
|
+
.with_error_handler do |error, handle|
|
146
|
+
raise error if error.is_a?(ActiveRecord::RecordNotFound)
|
147
|
+
handle.call(error)
|
148
|
+
end
|
172
149
|
# => #<Stoplight::Light:...>
|
173
150
|
light.run
|
174
|
-
#
|
151
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
175
152
|
light.run
|
176
|
-
#
|
153
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
177
154
|
light.run
|
178
|
-
#
|
155
|
+
# ActiveRecord::RecordNotFound: Couldn't find User with ID=123
|
179
156
|
light.color
|
180
157
|
# => "green"
|
181
158
|
```
|
@@ -188,7 +165,7 @@ fallback that will be called in both of these cases. It will be passed the
|
|
188
165
|
error if the light was green.
|
189
166
|
|
190
167
|
``` rb
|
191
|
-
light = Stoplight('example-
|
168
|
+
light = Stoplight('example-fallback') { 1 / 0 }
|
192
169
|
.with_fallback { |e| p e; 'default' }
|
193
170
|
# => #<Stoplight::Light:..>
|
194
171
|
light.run
|
@@ -198,7 +175,7 @@ light.run
|
|
198
175
|
# #<ZeroDivisionError: divided by 0>
|
199
176
|
# => "default"
|
200
177
|
light.run
|
201
|
-
# Switching example-
|
178
|
+
# Switching example-fallback from green to red because ZeroDivisionError divided by 0
|
202
179
|
# #<ZeroDivisionError: divided by 0>
|
203
180
|
# => "default"
|
204
181
|
light.run
|
@@ -212,34 +189,34 @@ Some bits of code might be allowed to fail more or less frequently than others.
|
|
212
189
|
You can configure this by setting a custom threshold.
|
213
190
|
|
214
191
|
``` rb
|
215
|
-
light = Stoplight('example-
|
192
|
+
light = Stoplight('example-threshold') { fail }
|
216
193
|
.with_threshold(1)
|
217
194
|
# => #<Stoplight::Light:...>
|
218
195
|
light.run
|
219
|
-
# Switching example-
|
196
|
+
# Switching example-threshold from green to red because RuntimeError
|
220
197
|
# RuntimeError:
|
221
198
|
light.run
|
222
|
-
# Stoplight::Error::RedLight: example-
|
199
|
+
# Stoplight::Error::RedLight: example-threshold
|
223
200
|
```
|
224
201
|
|
225
202
|
The default threshold is `3`.
|
226
203
|
|
227
|
-
### Custom
|
204
|
+
### Custom cool off time
|
228
205
|
|
229
206
|
Stoplights will automatically attempt to recover after a certain amount of
|
230
|
-
time. A light in the red state for longer than the
|
231
|
-
the yellow state. This
|
207
|
+
time. A light in the red state for longer than the cool of period will
|
208
|
+
transition to the yellow state. This cool off time is customizable.
|
232
209
|
|
233
210
|
``` rb
|
234
|
-
light = Stoplight('example-
|
235
|
-
.
|
211
|
+
light = Stoplight('example-cool-off') { fail }
|
212
|
+
.with_cool_off_time(1)
|
236
213
|
# => #<Stoplight::Light:...>
|
237
214
|
light.run
|
238
215
|
# RuntimeError:
|
239
216
|
light.run
|
240
217
|
# RuntimeError:
|
241
218
|
light.run
|
242
|
-
# Switching example-
|
219
|
+
# Switching example-cool-off from green to red because RuntimeError
|
243
220
|
# RuntimeError:
|
244
221
|
sleep(1)
|
245
222
|
# => 1
|
@@ -249,10 +226,10 @@ light.run
|
|
249
226
|
# RuntimeError:
|
250
227
|
```
|
251
228
|
|
252
|
-
The default
|
253
|
-
|
254
|
-
|
255
|
-
replaces the red state with yellow.
|
229
|
+
The default cool off time is `60` seconds. To disable automatic recovery, set
|
230
|
+
the cool off to `Float::INFINITY`. To make automatic recovery instantaneous,
|
231
|
+
set the cool off to `0` seconds. Note that this is not recommended, as it
|
232
|
+
effectively replaces the red state with yellow.
|
256
233
|
|
257
234
|
### Rails
|
258
235
|
|
@@ -267,7 +244,6 @@ class ApplicationController < ActionController::Base
|
|
267
244
|
|
268
245
|
def stoplight(&block)
|
269
246
|
Stoplight("#{params[:controller]}##{params[:action]}", &block)
|
270
|
-
.with_whitelisted_errors([ActiveRecord::RecordNotFound])
|
271
247
|
.with_fallback do |error|
|
272
248
|
Rails.logger.error(error)
|
273
249
|
render(nothing: true, status: :service_unavailable)
|
@@ -322,7 +298,7 @@ If you want to send notifications elsewhere, you'll have to set them up.
|
|
322
298
|
|
323
299
|
#### Bugsnag
|
324
300
|
|
325
|
-
Make sure you have [the Bugsnag gem][] (`~>
|
301
|
+
Make sure you have [the Bugsnag gem][] (`~> 4.0`) installed before configuring
|
326
302
|
Stoplight.
|
327
303
|
|
328
304
|
``` rb
|
@@ -419,14 +395,14 @@ override the default behavior. You can lock a light in either the green or red
|
|
419
395
|
state using `set_state`.
|
420
396
|
|
421
397
|
``` rb
|
422
|
-
light = Stoplight('example-
|
398
|
+
light = Stoplight('example-locked') { true }
|
423
399
|
# => #<Stoplight::Light:..>
|
424
400
|
light.run
|
425
401
|
# => true
|
426
402
|
light.data_store.set_state(light, Stoplight::State::LOCKED_RED)
|
427
403
|
# => "locked_red"
|
428
404
|
light.run
|
429
|
-
# Stoplight::Error::RedLight: example-
|
405
|
+
# Stoplight::Error::RedLight: example-locked
|
430
406
|
```
|
431
407
|
|
432
408
|
**Code in locked red lights may still run under certain conditions!** If you
|
@@ -477,27 +453,33 @@ Martin Fowler's [CircuitBreaker][] article.
|
|
477
453
|
|
478
454
|
Stoplight is licensed under [the MIT License][].
|
479
455
|
|
480
|
-
[
|
481
|
-
[
|
482
|
-
[
|
483
|
-
[
|
484
|
-
[
|
485
|
-
[
|
486
|
-
[
|
456
|
+
[Stoplight]: https://github.com/orgsync/stoplight
|
457
|
+
[Version badge]: https://img.shields.io/gem/v/stoplight.svg?label=version
|
458
|
+
[version]: https://rubygems.org/gems/stoplight
|
459
|
+
[Build badge]: https://img.shields.io/travis/orgsync/stoplight/master.svg?label=build
|
460
|
+
[build]: https://travis-ci.org/orgsync/stoplight
|
461
|
+
[Coverage badge]: https://img.shields.io/coveralls/orgsync/stoplight/master.svg?label=coverage
|
462
|
+
[coverage]: https://coveralls.io/r/orgsync/stoplight
|
463
|
+
[Grade badge]: https://img.shields.io/badge/grade-A-brightgreen.svg
|
464
|
+
[grade]: http://www.libgrader.com/libraries/ruby/stoplight
|
465
|
+
[Climate badge]: https://img.shields.io/codeclimate/github/orgsync/stoplight.svg?label=climate
|
466
|
+
[climate]: https://codeclimate.com/github/orgsync/stoplight
|
467
|
+
[Dependencies badge]: https://img.shields.io/gemnasium/orgsync/stoplight.svg?label=dependencies
|
468
|
+
[dependencies]: https://gemnasium.com/orgsync/stoplight
|
487
469
|
[stoplight-admin]: https://github.com/orgsync/stoplight-admin
|
488
|
-
[
|
470
|
+
[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
|
489
471
|
[the change log]: CHANGELOG.md
|
490
472
|
[the notifiers section]: #notifiers
|
491
|
-
[the
|
492
|
-
[the
|
493
|
-
[the
|
494
|
-
[the
|
495
|
-
[the
|
496
|
-
[the
|
497
|
-
[the
|
473
|
+
[the cool off time section]: #custom-cool-off-time
|
474
|
+
[the Redis gem]: https://rubygems.org/gems/redis
|
475
|
+
[the Bugsnag gem]: https://rubygems.org/gems/bugsnag
|
476
|
+
[the HipChat gem]: https://rubygems.org/gems/hipchat
|
477
|
+
[the Honeybadger gem]: https://rubygems.org/gems/honeybadger
|
478
|
+
[the Logger class]: http://ruby-doc.org/stdlib-2.2.3/libdoc/logger/rdoc/Logger.html
|
479
|
+
[the Slack gem]: https://rubygems.org/gems/slack-notifier
|
498
480
|
[@camdez]: https://github.com/camdez
|
499
481
|
[@tfausak]: https://github.com/tfausak
|
500
482
|
[@orgsync]: https://github.com/OrgSync
|
501
483
|
[complete list of contributors]: https://github.com/orgsync/stoplight/graphs/contributors
|
502
|
-
[
|
503
|
-
[the
|
484
|
+
[CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
|
485
|
+
[the MIT license]: LICENSE.md
|
data/lib/stoplight/default.rb
CHANGED
@@ -2,19 +2,12 @@
|
|
2
2
|
|
3
3
|
module Stoplight
|
4
4
|
module Default
|
5
|
-
|
6
|
-
NoMemoryError,
|
7
|
-
ScriptError,
|
8
|
-
SecurityError,
|
9
|
-
SignalException,
|
10
|
-
SystemExit,
|
11
|
-
SystemStackError
|
12
|
-
].freeze
|
13
|
-
|
14
|
-
BLACKLISTED_ERRORS = [].freeze
|
5
|
+
COOL_OFF_TIME = 60.0
|
15
6
|
|
16
7
|
DATA_STORE = DataStore::Memory.new
|
17
8
|
|
9
|
+
ERROR_HANDLER = -> (error, handler) { handler.call(error) }
|
10
|
+
|
18
11
|
ERROR_NOTIFIER = -> (error) { warn error }
|
19
12
|
|
20
13
|
FALLBACK = nil
|
@@ -30,7 +23,5 @@ module Stoplight
|
|
30
23
|
].freeze
|
31
24
|
|
32
25
|
THRESHOLD = 3
|
33
|
-
|
34
|
-
TIMEOUT = 60.0
|
35
26
|
end
|
36
27
|
end
|
data/lib/stoplight/error.rb
CHANGED
@@ -2,6 +2,19 @@
|
|
2
2
|
|
3
3
|
module Stoplight
|
4
4
|
module Error
|
5
|
+
HANDLER = lambda do |error|
|
6
|
+
raise error if AVOID_RESCUING.any? { |klass| error.is_a?(klass) }
|
7
|
+
end
|
8
|
+
|
9
|
+
AVOID_RESCUING = [
|
10
|
+
NoMemoryError,
|
11
|
+
ScriptError,
|
12
|
+
SecurityError,
|
13
|
+
SignalException,
|
14
|
+
SystemExit,
|
15
|
+
SystemStackError
|
16
|
+
].freeze
|
17
|
+
|
5
18
|
Base = Class.new(StandardError)
|
6
19
|
RedLight = Class.new(Base)
|
7
20
|
end
|
data/lib/stoplight/light.rb
CHANGED
@@ -4,15 +4,15 @@ module Stoplight
|
|
4
4
|
class Light # rubocop:disable Style/Documentation
|
5
5
|
include Runnable
|
6
6
|
|
7
|
-
# @return [Array<Exception>]
|
8
|
-
attr_reader :whitelisted_errors
|
9
|
-
# @return [Array<Exception>]
|
10
|
-
attr_reader :blacklisted_errors
|
11
7
|
# @return [Proc]
|
12
8
|
attr_reader :code
|
9
|
+
# @return [Float]
|
10
|
+
attr_reader :cool_off_time
|
13
11
|
# @return [DataStore::Base]
|
14
12
|
attr_reader :data_store
|
15
13
|
# @return [Proc]
|
14
|
+
attr_reader :error_handler
|
15
|
+
# @return [Proc]
|
16
16
|
attr_reader :error_notifier
|
17
17
|
# @return [Proc, nil]
|
18
18
|
attr_reader :fallback
|
@@ -22,8 +22,6 @@ module Stoplight
|
|
22
22
|
attr_reader :notifiers
|
23
23
|
# @return [Fixnum]
|
24
24
|
attr_reader :threshold
|
25
|
-
# @return [Float]
|
26
|
-
attr_reader :timeout
|
27
25
|
|
28
26
|
class << self
|
29
27
|
# @return [DataStore::Base]
|
@@ -44,36 +42,34 @@ module Stoplight
|
|
44
42
|
@name = name
|
45
43
|
@code = code
|
46
44
|
|
47
|
-
@
|
48
|
-
@blacklisted_errors = Default::BLACKLISTED_ERRORS
|
45
|
+
@cool_off_time = Default::COOL_OFF_TIME
|
49
46
|
@data_store = self.class.default_data_store
|
47
|
+
@error_handler = Default::ERROR_HANDLER
|
50
48
|
@error_notifier = self.class.default_error_notifier
|
51
49
|
@fallback = Default::FALLBACK
|
52
50
|
@notifiers = self.class.default_notifiers
|
53
51
|
@threshold = Default::THRESHOLD
|
54
|
-
@timeout = Default::TIMEOUT
|
55
52
|
end
|
56
53
|
|
57
|
-
# @param
|
54
|
+
# @param cool_off_time [Float]
|
58
55
|
# @return [self]
|
59
|
-
def
|
60
|
-
@
|
56
|
+
def with_cool_off_time(cool_off_time)
|
57
|
+
@cool_off_time = cool_off_time
|
61
58
|
self
|
62
59
|
end
|
63
60
|
|
64
|
-
|
65
|
-
|
66
|
-
# @param blacklisted_errors [Array<Exception>]
|
61
|
+
# @param data_store [DataStore::Base]
|
67
62
|
# @return [self]
|
68
|
-
def
|
69
|
-
@
|
63
|
+
def with_data_store(data_store)
|
64
|
+
@data_store = data_store
|
70
65
|
self
|
71
66
|
end
|
72
67
|
|
73
|
-
# @
|
68
|
+
# @yieldparam error [Exception]
|
69
|
+
# @yieldparam handle [Proc]
|
74
70
|
# @return [self]
|
75
|
-
def
|
76
|
-
@
|
71
|
+
def with_error_handler(&error_handler)
|
72
|
+
@error_handler = error_handler
|
77
73
|
self
|
78
74
|
end
|
79
75
|
|
@@ -104,12 +100,5 @@ module Stoplight
|
|
104
100
|
@threshold = threshold
|
105
101
|
self
|
106
102
|
end
|
107
|
-
|
108
|
-
# @param timeout [Float]
|
109
|
-
# @return [self]
|
110
|
-
def with_timeout(timeout)
|
111
|
-
@timeout = timeout
|
112
|
-
self
|
113
|
-
end
|
114
103
|
end
|
115
104
|
end
|
@@ -12,7 +12,8 @@ module Stoplight
|
|
12
12
|
when state == State::LOCKED_GREEN then Color::GREEN
|
13
13
|
when state == State::LOCKED_RED then Color::RED
|
14
14
|
when failures.size < threshold then Color::GREEN
|
15
|
-
when failure && Time.new - failure.time >=
|
15
|
+
when failure && Time.new - failure.time >= cool_off_time
|
16
|
+
Color::YELLOW
|
16
17
|
else Color::RED
|
17
18
|
end
|
18
19
|
end
|
@@ -52,18 +53,12 @@ module Stoplight
|
|
52
53
|
failures = clear_failures
|
53
54
|
on_success.call(failures) if on_success
|
54
55
|
result
|
55
|
-
rescue => error
|
56
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
56
57
|
handle_error(error, on_failure)
|
57
58
|
end
|
58
59
|
|
59
|
-
def not_blacklisted_error?(error)
|
60
|
-
!blacklisted_errors.empty? &&
|
61
|
-
blacklisted_errors.none? { |klass| error.is_a?(klass) }
|
62
|
-
end
|
63
|
-
|
64
60
|
def handle_error(error, on_failure)
|
65
|
-
|
66
|
-
raise error if not_blacklisted_error?(error)
|
61
|
+
error_handler.call(error, Error::HANDLER)
|
67
62
|
size = record_failure(error)
|
68
63
|
on_failure.call(size, error) if on_failure
|
69
64
|
raise error unless fallback
|
data/lib/stoplight/version.rb
CHANGED
@@ -7,39 +7,25 @@ RSpec.describe Stoplight::Default do
|
|
7
7
|
expect(described_class).to be_a(Module)
|
8
8
|
end
|
9
9
|
|
10
|
-
describe '::
|
11
|
-
it 'is
|
12
|
-
expect(Stoplight::Default::
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'contains exception classes' do
|
16
|
-
Stoplight::Default::WHITELISTED_ERRORS.each do |whitelisted_error|
|
17
|
-
expect(whitelisted_error).to be < Exception
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'is frozen' do
|
22
|
-
expect(Stoplight::Default::WHITELISTED_ERRORS).to be_frozen
|
10
|
+
describe '::COOL_OFF_TIME' do
|
11
|
+
it 'is a float' do
|
12
|
+
expect(Stoplight::Default::COOL_OFF_TIME).to be_a(Float)
|
23
13
|
end
|
24
14
|
end
|
25
15
|
|
26
|
-
describe '::
|
27
|
-
it 'is
|
28
|
-
expect(Stoplight::Default::
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'is empty' do
|
32
|
-
expect(Stoplight::Default::BLACKLISTED_ERRORS).to be_empty
|
16
|
+
describe '::DATA_STORE' do
|
17
|
+
it 'is a data store' do
|
18
|
+
expect(Stoplight::Default::DATA_STORE).to be_a(Stoplight::DataStore::Base)
|
33
19
|
end
|
20
|
+
end
|
34
21
|
|
35
|
-
|
36
|
-
|
22
|
+
describe '::ERROR_HANDLER' do
|
23
|
+
it 'is a proc' do
|
24
|
+
expect(Stoplight::Default::ERROR_HANDLER).to be_a(Proc)
|
37
25
|
end
|
38
|
-
end
|
39
26
|
|
40
|
-
|
41
|
-
|
42
|
-
expect(Stoplight::Default::DATA_STORE).to be_a(Stoplight::DataStore::Base)
|
27
|
+
it 'has an arity of 2' do
|
28
|
+
expect(Stoplight::Default::ERROR_HANDLER.arity).to eql(2)
|
43
29
|
end
|
44
30
|
end
|
45
31
|
|
@@ -91,10 +77,4 @@ RSpec.describe Stoplight::Default do
|
|
91
77
|
expect(Stoplight::Default::THRESHOLD).to be_a(Fixnum)
|
92
78
|
end
|
93
79
|
end
|
94
|
-
|
95
|
-
describe '::TIMEOUT' do
|
96
|
-
it 'is a float' do
|
97
|
-
expect(Stoplight::Default::TIMEOUT).to be_a(Float)
|
98
|
-
end
|
99
|
-
end
|
100
80
|
end
|
@@ -51,14 +51,14 @@ RSpec.describe Stoplight::Light::Runnable do
|
|
51
51
|
subject.data_store.record_failure(subject, failure)
|
52
52
|
end
|
53
53
|
other = Stoplight::Failure.new(
|
54
|
-
error.class.name, error.message, Time.new - subject.
|
54
|
+
error.class.name, error.message, Time.new - subject.cool_off_time)
|
55
55
|
subject.data_store.record_failure(subject, other)
|
56
56
|
expect(subject.color).to eql(Stoplight::Color::YELLOW)
|
57
57
|
end
|
58
58
|
|
59
59
|
it 'is red when the least recent failure is old' do
|
60
60
|
other = Stoplight::Failure.new(
|
61
|
-
error.class.name, error.message, Time.new - subject.
|
61
|
+
error.class.name, error.message, Time.new - subject.cool_off_time)
|
62
62
|
subject.data_store.record_failure(subject, other)
|
63
63
|
(subject.threshold - 1).times do
|
64
64
|
subject.data_store.record_failure(subject, failure)
|
@@ -119,88 +119,34 @@ RSpec.describe Stoplight::Light::Runnable do
|
|
119
119
|
expect(io.string).to_not eql('')
|
120
120
|
end
|
121
121
|
|
122
|
-
context '
|
123
|
-
let(:
|
124
|
-
|
125
|
-
before { subject.with_whitelisted_errors(whitelisted_errors) }
|
126
|
-
|
127
|
-
it 'does not record the failure' do
|
128
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
129
|
-
begin
|
130
|
-
subject.run
|
131
|
-
rescue error.class
|
132
|
-
nil
|
133
|
-
end
|
134
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
context 'when the error is blacklisted' do
|
139
|
-
let(:blacklisted_errors) { [error.class] }
|
140
|
-
|
141
|
-
before { subject.with_blacklisted_errors(blacklisted_errors) }
|
142
|
-
|
143
|
-
it 'records the failure' do
|
144
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
145
|
-
begin
|
146
|
-
subject.run
|
147
|
-
rescue error.class
|
148
|
-
nil
|
149
|
-
end
|
150
|
-
expect(subject.data_store.get_failures(subject).size).to eql(1)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
context 'when the error is not blacklisted' do
|
155
|
-
let(:blacklisted_errors) { [RuntimeError] }
|
156
|
-
|
157
|
-
before { subject.with_blacklisted_errors(blacklisted_errors) }
|
158
|
-
|
159
|
-
it 'does not record the failure' do
|
160
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
122
|
+
context 'with an error handler' do
|
123
|
+
let(:result) do
|
161
124
|
begin
|
162
125
|
subject.run
|
126
|
+
expect(false).to be(true)
|
163
127
|
rescue error.class
|
164
|
-
|
128
|
+
expect(true).to be(true)
|
165
129
|
end
|
166
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
167
130
|
end
|
168
|
-
end
|
169
131
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
it 'records the failure' do
|
176
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
177
|
-
begin
|
178
|
-
subject.run
|
179
|
-
rescue error.class
|
180
|
-
nil
|
181
|
-
end
|
182
|
-
expect(subject.data_store.get_failures(subject).size).to eql(1)
|
132
|
+
it 'records the failure when the handler does nothing' do
|
133
|
+
subject.with_error_handler { |_error, _handler| }
|
134
|
+
expect { result }
|
135
|
+
.to change { subject.data_store.get_failures(subject).size }
|
136
|
+
.by(1)
|
183
137
|
end
|
184
|
-
end
|
185
138
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
subject
|
192
|
-
.with_whitelisted_errors(whitelisted_errors)
|
193
|
-
.with_blacklisted_errors(blacklisted_errors)
|
139
|
+
it 'records the failure when the handler calls handle' do
|
140
|
+
subject.with_error_handler { |error, handle| handle.call(error) }
|
141
|
+
expect { result }
|
142
|
+
.to change { subject.data_store.get_failures(subject).size }
|
143
|
+
.by(1)
|
194
144
|
end
|
195
145
|
|
196
|
-
it 'does not record the failure' do
|
197
|
-
|
198
|
-
|
199
|
-
subject.
|
200
|
-
rescue error.class
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
expect(subject.data_store.get_failures(subject).size).to eql(0)
|
146
|
+
it 'does not record the failure when the handler raises' do
|
147
|
+
subject.with_error_handler { |error, _handle| raise error }
|
148
|
+
expect { result }
|
149
|
+
.to_not change { subject.data_store.get_failures(subject).size }
|
204
150
|
end
|
205
151
|
end
|
206
152
|
|
@@ -254,7 +200,7 @@ RSpec.describe Stoplight::Light::Runnable do
|
|
254
200
|
end
|
255
201
|
|
256
202
|
other = Stoplight::Failure.new(
|
257
|
-
error.class.name, error.message, time - subject.
|
203
|
+
error.class.name, error.message, time - subject.cool_off_time)
|
258
204
|
subject.data_store.record_failure(subject, other)
|
259
205
|
end
|
260
206
|
|
@@ -67,25 +67,15 @@ RSpec.describe Stoplight::Light do
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
describe '#
|
71
|
-
it '
|
72
|
-
expect(light.
|
73
|
-
Stoplight::Default::WHITELISTED_ERRORS
|
74
|
-
)
|
70
|
+
describe '#code' do
|
71
|
+
it 'reads the code' do
|
72
|
+
expect(light.code).to eql(code)
|
75
73
|
end
|
76
74
|
end
|
77
75
|
|
78
|
-
describe '#
|
76
|
+
describe '#cool_off_time' do
|
79
77
|
it 'is initially the default' do
|
80
|
-
expect(light.
|
81
|
-
Stoplight::Default::BLACKLISTED_ERRORS
|
82
|
-
)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe '#code' do
|
87
|
-
it 'reads the code' do
|
88
|
-
expect(light.code).to eql(code)
|
78
|
+
expect(light.cool_off_time).to eql(Stoplight::Default::COOL_OFF_TIME)
|
89
79
|
end
|
90
80
|
end
|
91
81
|
|
@@ -95,6 +85,12 @@ RSpec.describe Stoplight::Light do
|
|
95
85
|
end
|
96
86
|
end
|
97
87
|
|
88
|
+
describe '#error_handler' do
|
89
|
+
it 'it initially the default' do
|
90
|
+
expect(light.error_handler).to eql(Stoplight::Default::ERROR_HANDLER)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
98
94
|
describe '#error_notifier' do
|
99
95
|
it 'it initially the default' do
|
100
96
|
expect(light.error_notifier)
|
@@ -126,36 +122,11 @@ RSpec.describe Stoplight::Light do
|
|
126
122
|
end
|
127
123
|
end
|
128
124
|
|
129
|
-
describe '#
|
130
|
-
it '
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
describe '#with_whitelisted_errors' do
|
136
|
-
it 'adds the whitelisted errors to the default' do
|
137
|
-
whitelisted_errors = [StandardError]
|
138
|
-
light.with_whitelisted_errors(whitelisted_errors)
|
139
|
-
expect(light.whitelisted_errors)
|
140
|
-
.to eql(Stoplight::Default::WHITELISTED_ERRORS + whitelisted_errors)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
describe '#with_allowed_errors' do
|
145
|
-
it 'sets whitelisted_errors' do
|
146
|
-
allowed_errors = [StandardError]
|
147
|
-
light.with_allowed_errors(allowed_errors)
|
148
|
-
expect(light.whitelisted_errors)
|
149
|
-
.to eql(Stoplight::Default::WHITELISTED_ERRORS + allowed_errors)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
describe '#with_blacklisted_errors' do
|
154
|
-
it 'adds the blacklisted errors to the default' do
|
155
|
-
blacklisted_errors = [StandardError]
|
156
|
-
light.with_blacklisted_errors(blacklisted_errors)
|
157
|
-
expect(light.blacklisted_errors)
|
158
|
-
.to eql(Stoplight::Default::BLACKLISTED_ERRORS + blacklisted_errors)
|
125
|
+
describe '#with_cool_off_time' do
|
126
|
+
it 'sets the cool off time' do
|
127
|
+
cool_off_time = 1.2
|
128
|
+
light.with_cool_off_time(cool_off_time)
|
129
|
+
expect(light.cool_off_time).to eql(cool_off_time)
|
159
130
|
end
|
160
131
|
end
|
161
132
|
|
@@ -167,6 +138,14 @@ RSpec.describe Stoplight::Light do
|
|
167
138
|
end
|
168
139
|
end
|
169
140
|
|
141
|
+
describe '#with_error_handler' do
|
142
|
+
it 'sets the error handler' do
|
143
|
+
error_handler = -> (_, _) {}
|
144
|
+
light.with_error_handler(&error_handler)
|
145
|
+
expect(light.error_handler).to eql(error_handler)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
170
149
|
describe '#with_error_notifier' do
|
171
150
|
it 'sets the error notifier' do
|
172
151
|
error_notifier = -> (_) {}
|
@@ -198,12 +177,4 @@ RSpec.describe Stoplight::Light do
|
|
198
177
|
expect(light.threshold).to eql(threshold)
|
199
178
|
end
|
200
179
|
end
|
201
|
-
|
202
|
-
describe '#with_timeout' do
|
203
|
-
it 'sets the timeout' do
|
204
|
-
timeout = 1.2
|
205
|
-
light.with_timeout(timeout)
|
206
|
-
expect(light.timeout).to eql(timeout)
|
207
|
-
end
|
208
|
-
end
|
209
180
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stoplight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cameron Desautels
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-04-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: concurrent-ruby
|
@@ -46,14 +46,14 @@ dependencies:
|
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '
|
49
|
+
version: '4.0'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '
|
56
|
+
version: '4.0'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: coveralls
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,48 +82,6 @@ dependencies:
|
|
82
82
|
- - "~>"
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0.5'
|
85
|
-
- !ruby/object:Gem::Dependency
|
86
|
-
name: guard
|
87
|
-
requirement: !ruby/object:Gem::Requirement
|
88
|
-
requirements:
|
89
|
-
- - "~>"
|
90
|
-
- !ruby/object:Gem::Version
|
91
|
-
version: '2.13'
|
92
|
-
type: :development
|
93
|
-
prerelease: false
|
94
|
-
version_requirements: !ruby/object:Gem::Requirement
|
95
|
-
requirements:
|
96
|
-
- - "~>"
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
version: '2.13'
|
99
|
-
- !ruby/object:Gem::Dependency
|
100
|
-
name: guard-rspec
|
101
|
-
requirement: !ruby/object:Gem::Requirement
|
102
|
-
requirements:
|
103
|
-
- - "~>"
|
104
|
-
- !ruby/object:Gem::Version
|
105
|
-
version: '4.6'
|
106
|
-
type: :development
|
107
|
-
prerelease: false
|
108
|
-
version_requirements: !ruby/object:Gem::Requirement
|
109
|
-
requirements:
|
110
|
-
- - "~>"
|
111
|
-
- !ruby/object:Gem::Version
|
112
|
-
version: '4.6'
|
113
|
-
- !ruby/object:Gem::Dependency
|
114
|
-
name: guard-rubocop
|
115
|
-
requirement: !ruby/object:Gem::Requirement
|
116
|
-
requirements:
|
117
|
-
- - "~>"
|
118
|
-
- !ruby/object:Gem::Version
|
119
|
-
version: '1.2'
|
120
|
-
type: :development
|
121
|
-
prerelease: false
|
122
|
-
version_requirements: !ruby/object:Gem::Requirement
|
123
|
-
requirements:
|
124
|
-
- - "~>"
|
125
|
-
- !ruby/object:Gem::Version
|
126
|
-
version: '1.2'
|
127
85
|
- !ruby/object:Gem::Dependency
|
128
86
|
name: hipchat
|
129
87
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,14 +116,14 @@ dependencies:
|
|
158
116
|
requirements:
|
159
117
|
- - "~>"
|
160
118
|
- !ruby/object:Gem::Version
|
161
|
-
version: '
|
119
|
+
version: '11.1'
|
162
120
|
type: :development
|
163
121
|
prerelease: false
|
164
122
|
version_requirements: !ruby/object:Gem::Requirement
|
165
123
|
requirements:
|
166
124
|
- - "~>"
|
167
125
|
- !ruby/object:Gem::Version
|
168
|
-
version: '
|
126
|
+
version: '11.1'
|
169
127
|
- !ruby/object:Gem::Dependency
|
170
128
|
name: redis
|
171
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -200,14 +158,14 @@ dependencies:
|
|
200
158
|
requirements:
|
201
159
|
- - "~>"
|
202
160
|
- !ruby/object:Gem::Version
|
203
|
-
version: 0.
|
161
|
+
version: 0.39.0
|
204
162
|
type: :development
|
205
163
|
prerelease: false
|
206
164
|
version_requirements: !ruby/object:Gem::Requirement
|
207
165
|
requirements:
|
208
166
|
- - "~>"
|
209
167
|
- !ruby/object:Gem::Version
|
210
|
-
version: 0.
|
168
|
+
version: 0.39.0
|
211
169
|
- !ruby/object:Gem::Dependency
|
212
170
|
name: slack-notifier
|
213
171
|
requirement: !ruby/object:Gem::Requirement
|
@@ -305,7 +263,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
305
263
|
requirements:
|
306
264
|
- - ">="
|
307
265
|
- !ruby/object:Gem::Version
|
308
|
-
version: '2'
|
266
|
+
version: '2.1'
|
309
267
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
310
268
|
requirements:
|
311
269
|
- - ">="
|