stoplight 5.0.3 → 5.2.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
  SHA256:
3
- metadata.gz: 1bf0d52652310dd2f35ddfca4b3fef8609ee4a27c69c14a8e8df56779a94dc65
4
- data.tar.gz: cb757bad124b5eb2fc145bc98dd11cd1cc115ab71bcd086b54d42c95b89085c8
3
+ metadata.gz: 4b9625eb129c8bbefdab0e3c9552bd6abe6d146c6ffaaab8f25edcb14ddf29db
4
+ data.tar.gz: db63d134926cceef9e46ed7b0dd72156fa1d55a883f116626a46ee1aaa7af9ff
5
5
  SHA512:
6
- metadata.gz: '09c6323f9b5c7a48248e60a0171fcc4a9fac0f942a22a6169cf0dbfaa8a3dc7de9ad8fc71ebe895968483c1727b73d5e131e981644e05be04a086615cc336662'
7
- data.tar.gz: 7bf6bed6db5100bf241fce259a45e69ff1719bcfc546c95f38464adec087a4943233f7448371bbb1dcf992ac0da6638d0a1d17571197129ebfd4d4c29ceaf4b4
6
+ metadata.gz: 7c59ce8f66fdf2871c2998e59adec143ad563e2815f3ebeaf7e8555a5ca39c65062fc0b067067067e1c7541c39db99d3724539eca6e26102abc5fd5f908a2326
7
+ data.tar.gz: 7c9c7a289d356acfbdbda23f0f28f32d0379999f644e7eae447a9f60b6a6ac05fc73c01af0118f05158802a53eabe449ec03ba5cbdad0de3b2b7148b2b91aa22
data/README.md CHANGED
@@ -13,10 +13,10 @@ Stoplight is traffic control for code. It's an implementation of the circuit bre
13
13
  the documentation of the previous version 4.x, you can find it [here](https://github.com/bolshakov/stoplight/tree/v4.1.1).
14
14
 
15
15
  Stoplight helps your application gracefully handle failures in external dependencies
16
- (like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
16
+ (like flaky databases, unreliable APIs, or spotty web services). By wrapping these unreliable
17
17
  calls, Stoplight prevents cascading failures from affecting your entire application.
18
18
 
19
- **The best part?** Stoplight works with zero configuration out of the box, while offering deep customization when you
19
+ **The best part?** Stoplight works with zero configuration out of the box, while offering deep customization when you
20
20
  need it.
21
21
 
22
22
  ## Installation
@@ -41,10 +41,10 @@ Stoplight operates like a traffic light with three states:
41
41
 
42
42
  ```mermaid
43
43
  stateDiagram
44
- Green --> Red: Failures reach threshold
44
+ Green --> Red: Errors reach threshold
45
45
  Red --> Yellow: After cool_off_time
46
- Yellow --> Green: Successful attempt
47
- Yellow --> Red: Failed attempt
46
+ Yellow --> Green: Successful recovery
47
+ Yellow --> Red: Failed recovery
48
48
  Green --> Green: Success
49
49
 
50
50
  classDef greenState fill:#28a745,stroke:#1e7e34,stroke-width:2px,color:#fff
@@ -60,11 +60,15 @@ stateDiagram
60
60
  - **Red**: Failure state. Fast-fails without running the code. (Circuit open)
61
61
  - **Yellow**: Recovery state. Allows a test execution to see if the problem is resolved. (Circuit half-open)
62
62
 
63
- Stoplight's behavior is controlled by three primary parameters:
63
+ Stoplight's behavior is controlled by two main parameters:
64
64
 
65
- 1. **Threshold** (default: `3`): Number of failures required to transition from green to red.
66
- 2. **Cool Off Time** (default: `60` seconds): Time to wait in the red state before transitioning to yellow.
67
- 3. **Window Size** (default: `nil`): Time window in which failures are counted toward the threshold. By default, all failures are counted.
65
+ 1. **Window Size** (default: `nil`): Time window in which errors are counted toward the threshold. By default, all errors are counted.
66
+ 2. **Threshold** (default: `3`): Number of errors required to transition from green to red.
67
+
68
+ Additionally, two other parameters control how Stoplight behaves after it turns red:
69
+
70
+ 1. **Cool Off Time** (default: `60` seconds): Time to wait in the red state before transitioning to yellow.
71
+ 2. **Recovery Threshold** (default: `1`): Number of successful attempts required to transition from yellow back to green.
68
72
 
69
73
  ## Basic Usage
70
74
 
@@ -78,7 +82,7 @@ light = Stoplight("Payment Service")
78
82
  result = light.run { payment_gateway.process(order) }
79
83
  ```
80
84
 
81
- When everything works, the light stays green and your code runs normally. If the code fails repeatedly, the
85
+ When everything works, the light stays green and your code runs normally. If the code fails repeatedly, the
82
86
  light turns red and raises a `Stoplight::Error::RedLight` exception to prevent further calls.
83
87
 
84
88
  ```ruby
@@ -112,7 +116,7 @@ light.color #=> "green"
112
116
 
113
117
  ### Using Fallbacks
114
118
 
115
- Provide fallbacks to gracefully handle failures:
119
+ Provide fallbacks to gracefully handle errors:
116
120
 
117
121
  ```ruby
118
122
  fallback = ->(error) { error ? "Failed: #{error.message}" : "Service unavailable" }
@@ -121,25 +125,30 @@ light = Stoplight('example-fallback')
121
125
  result = light.run(fallback) { external_service.call }
122
126
  ```
123
127
 
124
- If the light is green but the call fails, the fallback receives the `error`. If the light is red, the fallback
128
+ If the light is green but the call fails, the fallback receives the `error`. If the light is red, the fallback
125
129
  receives `nil`. In both cases, the return value of the fallback becomes the return value of the `run` method.
126
130
 
127
131
  ## Admin Panel
128
132
 
129
133
  Stoplight goes with a built-in Admin Panel that can track all active Lights and manually lock them in the desired state (`Green` or `Red`). Locking lights in certain states might be helpful in scenarios like E2E testing.
130
134
 
131
- To add Admin Panel to your Rails project, add this configuration to your `config/routes.rb` file.
135
+ To add Admin Panel protected by basic authentication to your Rails project, add this configuration to your `config/routes.rb` file.
132
136
 
133
137
  ```ruby
134
138
  Rails.application.routes.draw do
135
139
  # ...
136
140
 
141
+ Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
142
+ username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
143
+ end
137
144
  mount Stoplight::Admin => '/stoplights'
138
145
 
139
146
  # ...
140
147
  end
141
148
  ```
142
149
 
150
+ Then set up `STOPLIGHT_ADMIN_USERNAME` and `STOPLIGHT_ADMIN_PASSWORD` env variables to access your Admin panel.
151
+
143
152
  **IMPORTANT:** Stoplight Admin Panel requires you to have `sinatra` and `sinatra-contrib` gems installed. You can either add them to your Gemfile:
144
153
 
145
154
  ```ruby
@@ -158,17 +167,16 @@ gem install sinatra-contrib
158
167
  It is possible to run the Admin Panel separately from your application using the `stoplight-admin:<release-version>` docker image.
159
168
 
160
169
  ```shell
161
- docker run --net=host stoplight-admin:v5
170
+ docker run --net=host bolshakov/stoplight-admin
162
171
  ```
163
172
 
164
173
  **IMPORTANT:** Standalone Admin Panel should use the same Redis your application uses. To achieve this, set the `REDIS_URL` ENV variable via `-e REDIS_URL=<url-to-your-redis-servier>.` E.g.:
165
174
 
166
175
  ```shell
167
- docker run -e REDIS_URL=redis://localhost:6378 --net=host stoplight-admin:v5
176
+ docker run -e REDIS_URL=redis://localhost:6378 --net=host bolshakov/stoplight-admin
168
177
  ```
169
178
 
170
-
171
- ## Configuration
179
+ ## Configuration
172
180
 
173
181
  ### Global Configuration
174
182
 
@@ -177,9 +185,11 @@ Stoplight allows you to set default values for all lights in your application:
177
185
  ```ruby
178
186
  Stoplight.configure do |config|
179
187
  # Set default behavior for all stoplights
180
- config.threshold = 5
188
+ config.traffic_control = :error_rate
189
+ config.window_size = 300
190
+ config.threshold = 0.5
181
191
  config.cool_off_time = 30
182
- config.window_size = 60
192
+ config.recovery_threshold = 5
183
193
 
184
194
  # Set up default data store and notifiers
185
195
  config.data_store = Stoplight::DataStore::Redis.new(redis)
@@ -204,10 +214,11 @@ You can also provide settings during creation:
204
214
  ```ruby
205
215
  data_store = Stoplight::DataStore::Redis.new(Redis.new)
206
216
 
207
- light = Stoplight("Payment Service",
208
- threshold: 5, # 5 failures before turning red
217
+ light = Stoplight("Payment Service",
218
+ window_size: 300, # Only count errors in the last five minutes
219
+ threshold: 5, # 5 errors before turning red
209
220
  cool_off_time: 60, # Wait 60 seconds before attempting recovery
210
- window_size: 300, # Only count failures in the last five minutes
221
+ recovery_threshold: 1, # 1 successful attempt to turn green again
211
222
  data_store: data_store, # Use Redis for persistence
212
223
  tracked_errors: [TimeoutError], # Only count TimeoutError
213
224
  skipped_errors: [ValidationError] # Ignore ValidationError
@@ -228,7 +239,7 @@ users_api = base_api.with(
228
239
  )
229
240
  ```
230
241
 
231
- The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
242
+ The `#with` method creates a new stoplight instance without modifying the original, making it ideal for creating
232
243
  specialized stoplights from a common configuration.
233
244
 
234
245
  ## Error Handling
@@ -254,6 +265,117 @@ When both methods are used, `skipped_errors` takes precedence over `tracked_erro
254
265
 
255
266
  ## Advanced Configuration
256
267
 
268
+ ### Traffic Control Strategies
269
+
270
+ You've seen how Stoplight transitions from green to red when errors reach the threshold. But **how exactly does it
271
+ decide when that threshold is reached?** That's where traffic control strategies come in.
272
+
273
+ Stoplight offers two built-in strategies for counting errors:
274
+
275
+ #### Consecutive Errors (Default)
276
+
277
+ Stops traffic when a specified number of consecutive errors occur. Works with or without time sliding windows.
278
+
279
+ ```ruby
280
+ light = Stoplight(
281
+ "Payment API",
282
+ traffic_control: :consecutive_errors,
283
+ threshold: 5,
284
+ )
285
+ ```
286
+
287
+ Counts consecutive errors regardless of when they occurred. Once 5 consecutive errors happen, the stoplight
288
+ turns red and stops traffic.
289
+
290
+ ```ruby
291
+ light = Stoplight(
292
+ "Payment API",
293
+ traffic_control: :consecutive_errors,
294
+ threshold: 5,
295
+ window_size: 300,
296
+ )
297
+ ```
298
+
299
+ Counts consecutive errors within a 5-minute sliding window. Both conditions must be met: 5 consecutive errors
300
+ AND at least 5 total errors within the window.
301
+
302
+ _This is Stoplight's default strategy when no `traffic_control` is specified._ You can omit `traffic_control` parameter
303
+ in the above examples:
304
+
305
+ ```ruby
306
+ light = Stoplight(
307
+ "Payment API",
308
+ threshold: 5,
309
+ )
310
+ ```
311
+
312
+ #### Error Rate
313
+
314
+ Stops traffic when the error rate exceeds a percentage within a sliding time window. Requires `window_size` to be
315
+ configured:
316
+
317
+
318
+ ```ruby
319
+ light = Stoplight(
320
+ "Payment API",
321
+ traffic_control: :error_rate,
322
+ window_size: 300,
323
+ threshold: 0.5,
324
+ )
325
+ ```
326
+
327
+ Monitors error rate over a 5-minute sliding window. The stoplight turns red when error rate exceeds 50%.
328
+
329
+ ```ruby
330
+ light = Stoplight(
331
+ "Payment API",
332
+ traffic_control: {
333
+ error_rate: { min_requests: 20 },
334
+ },
335
+ window_size: 300,
336
+ threshold: 0.5,
337
+ )
338
+ ```
339
+
340
+ Only evaluates error rate after at least 20 requests within the window. Default `min_requests` is 10.
341
+
342
+
343
+ #### When to use:
344
+
345
+ * **Consecutive Errors**: Low-medium traffic, simple behavior, occasional spikes expected
346
+ * **Error Rate**: High traffic, percentage-based SLAs, variable traffic patterns
347
+
348
+ ### Traffic Recovery Strategies
349
+
350
+ In the yellow state, Stoplight behaves differently from normal (green) operation. Instead of
351
+ blocking all traffic, it allows a limited number of real requests to pass through to
352
+ the underlying service to determine if it has recovered. These aren't synthetic probes -
353
+ they're actual user requests that will execute normally if the service is healthy.
354
+
355
+ After collecting the necessary data from these requests, Stoplight decides whether to
356
+ return to green or red state.
357
+
358
+ Traffic Recovery strategies control how Stoplight evaluates these requests during
359
+ the recovery phase.
360
+
361
+ #### Consecutive Successes (Default)
362
+
363
+ Returns to green after a specified number of consecutive successful recovery attempts. This is the default behavior.
364
+
365
+ ```ruby
366
+ light = Stoplight(
367
+ "Payment API",
368
+ traffic_recovery: :consecutive_successes,
369
+ recovery_threshold: 3,
370
+ )
371
+ ```
372
+
373
+ This configuration requires 3 consecutive successful recovery probes before resuming normal traffic. If any probe
374
+ fails during recovery, the stoplight immediately returns to red and waits for another cool-off period before trying again.
375
+
376
+ **Default behavior**: If no `recovery_threshold` is specified, Stoplight uses a conservative default of 1, meaning a
377
+ single successful recovery probe will resume traffic flow.
378
+
257
379
  ### Data Store
258
380
 
259
381
  Stoplight uses an in-memory data store out of the box:
@@ -279,7 +401,7 @@ end
279
401
 
280
402
  #### Connection Pooling with Redis
281
403
 
282
- For high-traffic applications or when you want to control a number of opened connections to Redis:
404
+ For high-traffic applications or when you want to control a number of opened connections to Redis:
283
405
 
284
406
  ```ruby
285
407
  require "connection_pool"
@@ -324,7 +446,7 @@ the [notifier interface documentation] for detailed instructions. Pull requests
324
446
 
325
447
  ### Error Notifiers
326
448
 
327
- Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
449
+ Stoplight is built for resilience. If the Redis data store fails, Stoplight automatically falls back to the in-memory
328
450
  data store. To get notified about such errors, you can configure an error notifier:
329
451
 
330
452
  ```ruby
@@ -335,7 +457,7 @@ end
335
457
 
336
458
  ### Locking
337
459
 
338
- Sometimes you need to override Stoplight's automatic behavior. Locking allows you to manually control the state of
460
+ Sometimes you need to override Stoplight's automatic behavior. Locking allows you to manually control the state of
339
461
  a stoplight, which is useful for:
340
462
 
341
463
  * **Maintenance periods**: Lock to red when a service is known to be unavailable
@@ -384,6 +506,12 @@ Stoplight.configure do |config|
384
506
  end
385
507
  ```
386
508
 
509
+ You can generate initializer with Redis, Admin Panel route and add needed gems to your Gemfile:
510
+
511
+ ```sh
512
+ rails generate stoplight:install --with-admin-panel
513
+ ```
514
+
387
515
  ## Testing
388
516
 
389
517
  Tips for working with Stoplight in test environments:
@@ -413,9 +541,15 @@ stoplight = Stoplight("test-#{rand}")
413
541
  ## Maintenance Policy
414
542
 
415
543
  Stoplight supports the latest three minor versions of Ruby, which currently are: `3.2.x`, `3.3.x`, and `3.4.x`. Changing
416
- the minimum supported Ruby version is not considered a breaking change. We support the current stable Redis
544
+ the minimum supported Ruby version is not considered a breaking change. We support the current stable Redis
417
545
  version (`7.4.x`) and the latest release of the previous major version (`6.2.x`)
418
546
 
547
+ ## Development
548
+
549
+ After checking out the repo, run `bundle install` to install dependencies. Run tests with `bundle exec rspec` and check
550
+ code style with `bundle exec standardrb`. We follow a git flow branching strategy - see our [Git Flow wiki page] for
551
+ details on branch naming, releases, and contribution workflow.
552
+
419
553
  ## Credits
420
554
 
421
555
  Stoplight was originally created by [camdez][] and [tfausak][]. It is currently maintained by [bolshakov][] and
@@ -444,3 +578,4 @@ Fowler’s [CircuitBreaker][] article.
444
578
  [complete list of contributors]: https://github.com/bolshakov/stoplight/graphs/contributors
445
579
  [CircuitBreaker]: http://martinfowler.com/bliki/CircuitBreaker.html
446
580
  [Redis]: https://redis.io/
581
+ [Git Flow wiki page]: https://github.com/bolshakov/stoplight/wiki/Git-Flow
@@ -0,0 +1,16 @@
1
+ Description:
2
+ Generates stoplight initializer to setup Stoplight Redis configuration
3
+ and optionally sets up Admin panel
4
+
5
+ Examples:
6
+ rails generate stoplight:install
7
+
8
+ This will generate "config/initializers/stoplight.rb" initializer with basic config
9
+ Then you should adjust Redis config there.
10
+
11
+ rails generate stoplight:install --with-admin-panel
12
+
13
+ This generated all needed requirements to set up Stoplight Admin Panel:
14
+ * It generates initializer "config/initializers/stoplight.rb" with Redis configuration
15
+ * It injects your "config/routes.rb" with route with basic authentication to Stoplight Admin panel
16
+ * It injects your Gemfile with needed dependencies ('redis', 'sinatra' and 'sinatra-contrib')
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "rails/generators"
5
+ rescue LoadError
6
+ raise <<~WARN
7
+ Currently generators are only available for Rails applications
8
+ WARN
9
+ end
10
+
11
+ module Stoplight
12
+ module Generators
13
+ class InstallGenerator < ::Rails::Generators::Base # :nodoc:
14
+ source_root File.expand_path("templates", __dir__)
15
+
16
+ class_option :with_admin_panel, type: :boolean, optional: true,
17
+ desc: "Define whether to set up admin panel"
18
+
19
+ ROUTES_PATH = "config/routes.rb"
20
+ STOPLIGHT_CONFIG_TEMPLATE = "stoplight.rb.erb"
21
+ INITIALIZERS_PATH = "config/initializers"
22
+ AFTER_INSTALL_NOTIFICATION = <<~TEXT
23
+ \nThank you for using stoplight!
24
+ Now to finish configuration:
25
+ * Run `bundle` from the project root to install new gems
26
+ * Go to 'config/initializers/stoplight.rb' to set up connection to Redis.\n
27
+ TEXT
28
+
29
+ STOPLIGHT_ADMIN_ROUTE = <<-RUBY
30
+ mount Stoplight::Admin => '/stoplights'
31
+ RUBY
32
+
33
+ STOPLIGHT_AUTH = <<-RUBY
34
+ Stoplight::Admin.use(Rack::Auth::Basic) do |username, password|
35
+ username == ENV["STOPLIGHT_ADMIN_USERNAME"] && password == ENV["STOPLIGHT_ADMIN_PASSWORD"]
36
+ end
37
+ RUBY
38
+
39
+ def generate_redis_gem
40
+ if options[:with_admin_panel]
41
+ conf = "\ngem 'redis'"
42
+ inject_into_file "Gemfile", conf
43
+ end
44
+ end
45
+
46
+ def generate_sinatra_deps
47
+ if options[:with_admin_panel]
48
+ conf = <<~RUBY
49
+ gem 'sinatra', require: false
50
+ gem 'sinatra-contrib', require: false
51
+ RUBY
52
+
53
+ inject_into_file "Gemfile", "\n#{conf}"
54
+ end
55
+ end
56
+
57
+ def generate_initializer
58
+ initializer_template = STOPLIGHT_CONFIG_TEMPLATE
59
+ copy_file initializer_template, "#{INITIALIZERS_PATH}/stoplight.rb"
60
+ end
61
+
62
+ def generate_admin_panel
63
+ if options[:with_admin_panel]
64
+ route_config = "#{STOPLIGHT_AUTH}#{STOPLIGHT_ADMIN_ROUTE}\n"
65
+
66
+ inject_into_file ROUTES_PATH, route_config, after: ".application.routes.draw do\n"
67
+ end
68
+ end
69
+
70
+ def redis_configuration_notification
71
+ print AFTER_INSTALL_NOTIFICATION
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "redis"
4
+
5
+ # Set up your project-specific Redis setup here
6
+ # One of the simplest configurations would be configuring Redis via url
7
+ # Example:
8
+ # redis = Redis.new(url: ENV["REDIS_URL"])
9
+ # REDIS_URL usually looks like this
10
+ # "redis://<username>:<password>@<server ip address>:<port>/<redis db>"
11
+ # Example:
12
+ # "redis://admin:p4ssw0rd@10.0.1.1:6380/15"
13
+ redis = Redis.new
14
+ data_store = Stoplight::DataStore::Redis.new(redis)
15
+
16
+ Stoplight.configure do |config|
17
+ config.data_store = data_store
18
+ end
@@ -25,7 +25,7 @@ module Stoplight
25
25
  helpers Helpers
26
26
 
27
27
  set :protection, except: %i[json_csrf]
28
- set :data_store, proc { Stoplight.config_provider.data_store }
28
+ set :data_store, proc { Stoplight.default_config.data_store }
29
29
  set :views, File.join(__dir__, "admin", "views")
30
30
 
31
31
  get "/" do
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Config
5
+ # The +CompatibilityResult+ class represents the result of a compatibility check
6
+ # for a strategy. It provides methods to determine if the strategy is compatible
7
+ # and to retrieve error messages when it is not.
8
+ class CompatibilityResult
9
+ class << self
10
+ # Creates a new +CompatibilityResult+ instance representing a compatible strategy.
11
+ #
12
+ # @return [CompatibilityResult] An instance with no errors.
13
+ def compatible
14
+ new(errors: [])
15
+ end
16
+
17
+ # Creates a new +CompatibilityResult+ instance representing an incompatible strategy.
18
+ #
19
+ # @param errors [Array<String>] List of error messages indicating incompatibility.
20
+ # @return [CompatibilityResult] An instance with the provided errors.
21
+ def incompatible(*errors)
22
+ new(errors:)
23
+ end
24
+ end
25
+
26
+ # Initializes a new `CompatibilityResult` instance.
27
+ # @param errors [Array<String>] List of error messages if the strategy is not compatible.
28
+ def initialize(errors: [])
29
+ @errors = errors.freeze
30
+ end
31
+
32
+ # Checks if the strategy is compatible.
33
+ # @return [Boolean] `true` if there are no errors, `false` otherwise.
34
+ def compatible?
35
+ @errors.empty?
36
+ end
37
+
38
+ def incompatible? = !compatible?
39
+
40
+ # Retrieves the list of error messages.
41
+ # @return [Array<String>] The list of error messages.
42
+ attr_reader :errors
43
+
44
+ # Retrieves a concatenated error message string.
45
+ # @return [String, nil] A string containing all error messages joined by "; ",
46
+ # or `nil` if the strategy is compatible.
47
+ def error_messages
48
+ unless compatible?
49
+ @errors.join("; ")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Config
5
+ # This is a DSL for configuring Stoplight settings. It is responsible for
6
+ # transforming the provided settings into a format that can be used by Stoplight.
7
+ #
8
+ # @api private
9
+ class DSL
10
+ def transform(settings)
11
+ if settings.has_key?(:data_store)
12
+ settings[:data_store] = build_data_store(settings[:data_store])
13
+ end
14
+
15
+ if settings.has_key?(:notifiers)
16
+ settings[:notifiers] = build_notifiers(settings[:notifiers])
17
+ end
18
+
19
+ if settings.has_key?(:tracked_errors)
20
+ settings[:tracked_errors] = build_tracked_errors(settings[:tracked_errors])
21
+ end
22
+
23
+ if settings.has_key?(:skipped_errors)
24
+ settings[:skipped_errors] = build_skipped_errors(settings[:skipped_errors])
25
+ end
26
+
27
+ if settings.has_key?(:cool_off_time)
28
+ settings[:cool_off_time] = build_cool_off_time(settings[:cool_off_time])
29
+ end
30
+
31
+ if settings.has_key?(:traffic_control)
32
+ settings[:traffic_control] = build_traffic_control(settings[:traffic_control])
33
+ end
34
+
35
+ if settings.has_key?(:traffic_recovery)
36
+ settings[:traffic_recovery] = build_traffic_recovery(settings[:traffic_recovery])
37
+ end
38
+ settings
39
+ end
40
+
41
+ private
42
+
43
+ def build_data_store(data_store)
44
+ DataStore::FailSafe.wrap(data_store)
45
+ end
46
+
47
+ def build_notifiers(notifiers)
48
+ notifiers.map { |notifier| Notifier::FailSafe.wrap(notifier) }
49
+ end
50
+
51
+ def build_tracked_errors(tracked_error)
52
+ Array(tracked_error)
53
+ end
54
+
55
+ def build_skipped_errors(skipped_errors)
56
+ Array(skipped_errors)
57
+ end
58
+
59
+ def build_cool_off_time(cool_off_time)
60
+ cool_off_time.to_i
61
+ end
62
+
63
+ def build_traffic_control(traffic_control)
64
+ case traffic_control
65
+ in Stoplight::TrafficControl::Base
66
+ traffic_control
67
+ in :consecutive_errors
68
+ Stoplight::TrafficControl::ConsecutiveErrors.new
69
+ in :error_rate
70
+ Stoplight::TrafficControl::ErrorRate.new
71
+ in {error_rate: error_rate_settings}
72
+ Stoplight::TrafficControl::ErrorRate.new(**error_rate_settings)
73
+ else
74
+ raise Error::ConfigurationError, <<~ERROR
75
+ unsupported traffic_control strategy provided (`#{traffic_control}`). Supported options:
76
+ * Stoplight::TrafficControl::ConsecutiveErrors
77
+ * Stoplight::TrafficControl::ErrorRate
78
+ ERROR
79
+ end
80
+ end
81
+
82
+ def build_traffic_recovery(traffic_recovery)
83
+ case traffic_recovery
84
+ in Stoplight::TrafficRecovery::Base
85
+ traffic_recovery
86
+ in :consecutive_successes
87
+ Stoplight::TrafficRecovery::ConsecutiveSuccesses.new
88
+ else
89
+ raise Error::ConfigurationError, <<~ERROR
90
+ unsupported traffic_recovery strategy provided (`#{traffic_recovery}`). Supported options:
91
+ * Stoplight::TrafficRecovery::ConsecutiveSuccesses
92
+ ERROR
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -4,26 +4,18 @@ module Stoplight
4
4
  module Config
5
5
  # Provides default settings for the Stoplight library.
6
6
  # @api private
7
- class LibraryDefaultConfig
8
- DEFAULT_SETTINGS = {
9
- cool_off_time: Stoplight::Default::COOL_OFF_TIME,
10
- data_store: Stoplight::Default::DATA_STORE,
11
- error_notifier: Stoplight::Default::ERROR_NOTIFIER,
12
- notifiers: Stoplight::Default::NOTIFIERS,
13
- threshold: Stoplight::Default::THRESHOLD,
14
- window_size: Stoplight::Default::WINDOW_SIZE,
15
- tracked_errors: Stoplight::Default::TRACKED_ERRORS,
16
- skipped_errors: Stoplight::Default::SKIPPED_ERRORS,
17
- traffic_control: Stoplight::Default::TRAFFIC_CONTROL,
18
- traffic_recovery: Stoplight::Default::TRAFFIC_RECOVERY
19
- }.freeze
20
- private_constant :DEFAULT_SETTINGS
21
-
22
- # Returns library default settings.
23
- # @return [Hash]
24
- def to_h
25
- DEFAULT_SETTINGS
26
- end
27
- end
7
+ LibraryDefaultConfig = Light::Config.empty.with(
8
+ cool_off_time: Stoplight::Default::COOL_OFF_TIME,
9
+ data_store: Stoplight::Default::DATA_STORE,
10
+ error_notifier: Stoplight::Default::ERROR_NOTIFIER,
11
+ notifiers: Stoplight::Default::NOTIFIERS,
12
+ threshold: Stoplight::Default::THRESHOLD,
13
+ recovery_threshold: Stoplight::Default::RECOVERY_THRESHOLD,
14
+ window_size: Stoplight::Default::WINDOW_SIZE,
15
+ tracked_errors: Stoplight::Default::TRACKED_ERRORS,
16
+ skipped_errors: Stoplight::Default::SKIPPED_ERRORS,
17
+ traffic_control: Stoplight::Default::TRAFFIC_CONTROL,
18
+ traffic_recovery: Stoplight::Default::TRAFFIC_RECOVERY
19
+ )
28
20
  end
29
21
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Config
5
+ SystemConfig = LibraryDefaultConfig
6
+ end
7
+ end