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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e8f2a2c82dbcebacbd22a8e7c945ded26322e528
4
- data.tar.gz: 499150fa4ddaa7280f609e9171ab3d01a5486c6e
3
+ metadata.gz: 97f3aac00bec5d8f4a86a709adacab7e81246b2c
4
+ data.tar.gz: ed4f8f332e6111245ff0c6555b337bded9f417a7
5
5
  SHA512:
6
- metadata.gz: fb0064324bbfdf24238b17eb0d7f486981a1c327799441d0571549df292be4b5e0abf61a8d4697bceae3c7bca2c12f66cf6a6fc090349cb5592fb141c171dab1
7
- data.tar.gz: 7cf937362b94a968b2ce0cd425e3cb7bf289f6c6708f5daa9386b4e06fedf493d736f9662fd878493894c9f5e1a39d35f0067bfeb06b134f962b6303c6d0e436
6
+ metadata.gz: 43be4295193fbd9977303ddb15baf0d1ef3a4f04eeea57302d58e89d97ed3af2066b4c37294d335213f2b5841b987bf3b0e2a74eda00bff619e1504092ada523
7
+ data.tar.gz: 5b8e1cfda7283a49082308f9f5d9f696ca2508066ddba7707240cc88e31e61f6f2d9611600c0468c68468eef0eb3851a16b1a1f1e85520c5618f043ddbe4fa62
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # [Stoplight][]
2
2
 
3
- [![Version][]](https://rubygems.org/gems/stoplight)
4
- [![Build][]](https://travis-ci.org/orgsync/stoplight)
5
- [![Coverage][]](https://coveralls.io/r/orgsync/stoplight)
6
- [![Grade][]](http://www.libgrader.com/libraries/ruby/stoplight)
7
- [![Climate][]](https://codeclimate.com/github/orgsync/stoplight)
8
- [![Dependencies][]](https://gemnasium.com/orgsync/stoplight)
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 timeout](#custom-timeout)
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.0 through 2.3).
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-1') { 22.0 / 7 }
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-2') { 1 / 0 }
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-2 from green to red because ZeroDivisionError divided by 0
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-2
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
- breakers.) To configure how long it takes to switch into the yellow state,
116
- check out [the timeout section][] When stoplights are yellow, they will try
117
- to run their code. If it fails, they'll switch back to red. If it succeeds,
118
- they'll switch to green.
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
- ``` rb
129
- light = Stoplight('example-3') { User.find(123) }
130
- .with_whitelisted_errors([ActiveRecord::RecordNotFound])
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
- Whitelisted errors take precedence over [blacklisted errors](#blacklisted-errors).
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
- ##### Blacklisted errors
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
- You may want only certain errors to cause your stoplight to move into the red
150
- state.
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-4') { 1 / 0 }
154
- .with_blacklisted_errors([ZeroDivisionError])
155
- # => #<Stoplight::Light:...>
156
- light.run
157
- # ZeroDivisionError: divided by 0
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
- # RuntimeError:
151
+ # ActiveRecord::RecordNotFound: Couldn't find User with ID=123
175
152
  light.run
176
- # RuntimeError:
153
+ # ActiveRecord::RecordNotFound: Couldn't find User with ID=123
177
154
  light.run
178
- # RuntimeError:
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-6') { 1 / 0 }
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-4 from green to red because ZeroDivisionError divided by 0
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-7') { fail }
192
+ light = Stoplight('example-threshold') { fail }
216
193
  .with_threshold(1)
217
194
  # => #<Stoplight::Light:...>
218
195
  light.run
219
- # Switching example-5 from green to red because RuntimeError
196
+ # Switching example-threshold from green to red because RuntimeError
220
197
  # RuntimeError:
221
198
  light.run
222
- # Stoplight::Error::RedLight: example-5
199
+ # Stoplight::Error::RedLight: example-threshold
223
200
  ```
224
201
 
225
202
  The default threshold is `3`.
226
203
 
227
- ### Custom timeout
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 timeout will transition to
231
- the yellow state. This timeout is customizable.
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-8') { fail }
235
- .with_timeout(1)
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-6 from green to red because RuntimeError
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 timeout is `60` seconds. To disable automatic recovery, set the
253
- timeout to `Float::INFINITY`. To make automatic recovery instantaneous, set the
254
- timeout to `0` seconds. Note that this is not recommended, as it effectively
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][] (`~> 2.8`) installed before configuring
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-9') { true }
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-7
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
- [stoplight]: https://github.com/orgsync/stoplight
481
- [version]: https://img.shields.io/gem/v/stoplight.svg?label=version
482
- [build]: https://img.shields.io/travis/orgsync/stoplight/master.svg?label=build
483
- [grade]: https://img.shields.io/badge/grade-A-brightgreen.svg
484
- [coverage]: https://img.shields.io/coveralls/orgsync/stoplight/master.svg?label=coverage
485
- [climate]: https://img.shields.io/codeclimate/github/orgsync/stoplight.svg?label=climate
486
- [dependencies]: https://img.shields.io/gemnasium/orgsync/stoplight.svg?label=dependencies
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
- [semantic versioning]: http://semver.org/spec/v2.0.0.html
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 timeout section]: #custom-timeout
492
- [the redis gem]: https://rubygems.org/gems/redis
493
- [the bugsnag gem]: https://rubygems.org/gems/bugsnag
494
- [the hipchat gem]: https://rubygems.org/gems/hipchat
495
- [the honeybadger gem]: https://rubygems.org/gems/honeybadger
496
- [the logger class]: http://ruby-doc.org/stdlib-2.2.3/libdoc/logger/rdoc/Logger.html
497
- [the slack gem]: https://rubygems.org/gems/slack-notifier
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
- [circuitbreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
503
- [the mit license]: LICENSE.md
484
+ [CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
485
+ [the MIT license]: LICENSE.md
@@ -2,19 +2,12 @@
2
2
 
3
3
  module Stoplight
4
4
  module Default
5
- WHITELISTED_ERRORS = [
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
@@ -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
@@ -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
- @whitelisted_errors = Default::WHITELISTED_ERRORS
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 whitelisted_errors [Array<Exception>]
54
+ # @param cool_off_time [Float]
58
55
  # @return [self]
59
- def with_whitelisted_errors(whitelisted_errors)
60
- @whitelisted_errors = Default::WHITELISTED_ERRORS + whitelisted_errors
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
- alias with_allowed_errors with_whitelisted_errors
65
-
66
- # @param blacklisted_errors [Array<Exception>]
61
+ # @param data_store [DataStore::Base]
67
62
  # @return [self]
68
- def with_blacklisted_errors(blacklisted_errors)
69
- @blacklisted_errors = Default::BLACKLISTED_ERRORS + blacklisted_errors
63
+ def with_data_store(data_store)
64
+ @data_store = data_store
70
65
  self
71
66
  end
72
67
 
73
- # @param data_store [DataStore::Base]
68
+ # @yieldparam error [Exception]
69
+ # @yieldparam handle [Proc]
74
70
  # @return [self]
75
- def with_data_store(data_store)
76
- @data_store = data_store
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 >= timeout then Color::YELLOW
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
- raise error if whitelisted_errors.any? { |klass| error.is_a?(klass) }
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
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Stoplight
4
- VERSION = Gem::Version.new('1.4.0')
4
+ VERSION = Gem::Version.new('2.0.0')
5
5
  end
@@ -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 '::WHITELISTED_ERRORS' do
11
- it 'is an array' do
12
- expect(Stoplight::Default::WHITELISTED_ERRORS).to be_an(Array)
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 '::BLACKLISTED_ERRORS' do
27
- it 'is an array' do
28
- expect(Stoplight::Default::BLACKLISTED_ERRORS).to be_an(Array)
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
- it 'is frozen' do
36
- expect(Stoplight::Default::BLACKLISTED_ERRORS).to be_frozen
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
- describe '::DATA_STORE' do
41
- it 'is a data store' do
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.timeout)
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.timeout)
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 'when the error is whitelisted' do
123
- let(:whitelisted_errors) { [error.class] }
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
- nil
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
- context 'when the list of blacklisted errors is empty' do
171
- let(:blacklisted_errors) { [] }
172
-
173
- before { subject.with_blacklisted_errors(blacklisted_errors) }
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
- context 'when the error is both whitelisted and blacklisted' do
187
- let(:whitelisted_errors) { [error.class] }
188
- let(:blacklisted_errors) { [error.class] }
189
-
190
- before do
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
- expect(subject.data_store.get_failures(subject).size).to eql(0)
198
- begin
199
- subject.run
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.timeout)
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 '#whitelisted_errors' do
71
- it 'is initially the default' do
72
- expect(light.whitelisted_errors).to eql(
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 '#blacklisted_errors' do
76
+ describe '#cool_off_time' do
79
77
  it 'is initially the default' do
80
- expect(light.blacklisted_errors).to eql(
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 '#timeout' do
130
- it 'is initially the default' do
131
- expect(light.timeout).to eql(Stoplight::Default::TIMEOUT)
132
- end
133
- end
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: 1.4.0
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-03-01 00:00:00.000000000 Z
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: '3.0'
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: '3.0'
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: '10.4'
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: '10.4'
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.37.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.37.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
  - - ">="