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 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
  - - ">="