stoplight 1.4.0 → 2.0.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/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
|
- - ">="
|