stoplight 5.7.0 → 5.8.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.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/UPGRADING.md +303 -0
  4. data/lib/generators/stoplight/install/install_generator.rb +6 -1
  5. data/lib/stoplight/admin/dependencies.rb +1 -1
  6. data/lib/stoplight/admin/helpers.rb +20 -4
  7. data/lib/stoplight/admin/lights_repository/light.rb +22 -6
  8. data/lib/stoplight/admin/lights_repository.rb +6 -5
  9. data/lib/stoplight/admin/views/_card.erb +8 -5
  10. data/lib/stoplight/color.rb +9 -0
  11. data/lib/stoplight/data_store.rb +28 -0
  12. data/lib/stoplight/domain/compatibility_result.rb +7 -7
  13. data/lib/stoplight/domain/config.rb +38 -39
  14. data/lib/stoplight/domain/error_tracking_policy.rb +27 -0
  15. data/lib/stoplight/domain/failure.rb +1 -1
  16. data/lib/stoplight/domain/light/configuration_builder_interface.rb +2 -0
  17. data/lib/stoplight/domain/light.rb +15 -46
  18. data/lib/stoplight/domain/light_info.rb +7 -0
  19. data/lib/stoplight/domain/metrics_snapshot.rb +58 -0
  20. data/lib/stoplight/domain/state_snapshot.rb +29 -23
  21. data/lib/stoplight/domain/storage/recovery_lock_token.rb +15 -0
  22. data/lib/stoplight/domain/strategies/green_run_strategy.rb +18 -26
  23. data/lib/stoplight/domain/strategies/red_run_strategy.rb +9 -12
  24. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +41 -51
  25. data/lib/stoplight/domain/tracker/recovery_probe.rb +16 -33
  26. data/lib/stoplight/domain/tracker/request.rb +12 -31
  27. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +8 -11
  28. data/lib/stoplight/domain/traffic_control/error_rate.rb +19 -15
  29. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +6 -10
  30. data/lib/stoplight/domain/traffic_recovery.rb +3 -4
  31. data/lib/stoplight/error.rb +46 -0
  32. data/lib/stoplight/infrastructure/{data_store/fail_safe.rb → fail_safe/data_store.rb} +39 -51
  33. data/lib/stoplight/infrastructure/fail_safe/storage/metrics.rb +65 -0
  34. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock.rb +69 -0
  35. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rb +19 -0
  36. data/lib/stoplight/infrastructure/fail_safe/storage/state.rb +62 -0
  37. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/metrics.rb +2 -2
  38. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_store.rb +10 -12
  39. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_token.rb +3 -6
  40. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/sliding_window.rb +21 -26
  41. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/state.rb +3 -3
  42. data/lib/stoplight/infrastructure/{data_store/memory.rb → memory/data_store.rb} +36 -32
  43. data/lib/stoplight/infrastructure/memory/storage/recovery_lock.rb +35 -0
  44. data/lib/stoplight/infrastructure/memory/storage/recovery_metrics.rb +16 -0
  45. data/lib/stoplight/infrastructure/memory/storage/state.rb +155 -0
  46. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +103 -0
  47. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +101 -0
  48. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +9 -21
  49. data/lib/stoplight/infrastructure/notifier/generic.rb +4 -14
  50. data/lib/stoplight/infrastructure/notifier/io.rb +1 -2
  51. data/lib/stoplight/infrastructure/notifier/logger.rb +1 -2
  52. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_store.rb +9 -22
  53. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_token.rb +7 -14
  54. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/scripting.rb +22 -20
  55. data/lib/stoplight/infrastructure/{data_store/redis.rb → redis/data_store.rb} +48 -56
  56. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +51 -0
  57. data/lib/stoplight/infrastructure/redis/storage/metrics.rb +40 -0
  58. data/lib/stoplight/infrastructure/redis/storage/recovery_lock/release_lock.lua +6 -0
  59. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +64 -0
  60. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +20 -0
  61. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +18 -0
  62. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +10 -0
  63. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +10 -0
  64. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +9 -0
  65. data/lib/stoplight/infrastructure/redis/storage/state.rb +141 -0
  66. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +28 -0
  67. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +26 -0
  68. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +123 -0
  69. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +26 -0
  70. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +36 -0
  71. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +35 -0
  72. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +174 -0
  73. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +3 -10
  74. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +8 -11
  75. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +6 -14
  76. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +6 -17
  77. data/lib/stoplight/infrastructure/system_clock.rb +16 -0
  78. data/lib/stoplight/notifier.rb +11 -0
  79. data/lib/stoplight/state.rb +9 -0
  80. data/lib/stoplight/types.rb +29 -0
  81. data/lib/stoplight/undefined.rb +16 -0
  82. data/lib/stoplight/version.rb +1 -1
  83. data/lib/stoplight/wiring/config_compatibility_validator.rb +54 -0
  84. data/lib/stoplight/wiring/configuration_dsl.rb +101 -0
  85. data/lib/stoplight/wiring/data_store_backend.rb +26 -0
  86. data/lib/stoplight/wiring/default.rb +1 -1
  87. data/lib/stoplight/wiring/default_config.rb +21 -0
  88. data/lib/stoplight/wiring/default_configuration.rb +70 -53
  89. data/lib/stoplight/wiring/light_builder.rb +76 -63
  90. data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +3 -3
  91. data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +4 -4
  92. data/lib/stoplight/wiring/light_factory.rb +78 -52
  93. data/lib/stoplight/wiring/memory/backend.rb +57 -0
  94. data/lib/stoplight/wiring/redis/backend.rb +116 -0
  95. data/lib/stoplight/wiring/storage_set.rb +12 -0
  96. data/lib/stoplight/wiring/storage_set_builder.rb +51 -0
  97. data/lib/stoplight/wiring/system/light_builder.rb +47 -0
  98. data/lib/stoplight/wiring/system/light_factory.rb +64 -0
  99. data/lib/stoplight/wiring/system.rb +129 -0
  100. data/lib/stoplight.rb +196 -25
  101. data/sig/_private/generators/stoplight/install/install_generator.rbs +22 -0
  102. data/sig/_private/stoplight/common/deprecations.rbs +9 -0
  103. data/sig/_private/stoplight/data_store.rbs +6 -0
  104. data/sig/_private/stoplight/domain/compatibility_result.rbs +18 -0
  105. data/sig/_private/stoplight/domain/config.rbs +65 -0
  106. data/sig/_private/stoplight/domain/error_tracking_policy.rbs +14 -0
  107. data/sig/_private/stoplight/domain/failure.rbs +16 -0
  108. data/sig/_private/stoplight/domain/light.rbs +25 -0
  109. data/sig/_private/stoplight/domain/light_info.rbs +19 -0
  110. data/sig/_private/stoplight/domain/metrics_snapshot.rbs +38 -0
  111. data/sig/_private/stoplight/domain/ports/clock.rbs +18 -0
  112. data/sig/_private/stoplight/domain/ports/data_store.rbs +76 -0
  113. data/{lib/stoplight/domain/light_factory.rb → sig/_private/stoplight/domain/ports/light_factory.rbs} +33 -28
  114. data/sig/_private/stoplight/domain/ports/metrics_store.rbs +29 -0
  115. data/sig/_private/stoplight/domain/ports/recovery_lock_store.rbs +52 -0
  116. data/sig/_private/stoplight/domain/ports/recovery_lock_token.rbs +6 -0
  117. data/sig/_private/stoplight/domain/ports/run_strategy.rbs +14 -0
  118. data/sig/_private/stoplight/domain/ports/state_store.rbs +79 -0
  119. data/sig/_private/stoplight/domain/ports/traffic_control.rbs +41 -0
  120. data/sig/_private/stoplight/domain/ports/traffic_recovery.rbs +47 -0
  121. data/sig/_private/stoplight/domain/state_snapshot.rbs +32 -0
  122. data/sig/_private/stoplight/domain/storage/recovery_lock_token.rbs +11 -0
  123. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +17 -0
  124. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +17 -0
  125. data/sig/_private/stoplight/domain/strategies/yellow_run_strategy.rbs +42 -0
  126. data/{lib/stoplight/domain/tracker/base.rb → sig/_private/stoplight/domain/tracker/base.rbs} +0 -4
  127. data/sig/_private/stoplight/domain/tracker/recovery_probe.rbs +25 -0
  128. data/sig/_private/stoplight/domain/tracker/request.rbs +26 -0
  129. data/sig/_private/stoplight/domain/traffic_control/consecutive_errors.rbs +9 -0
  130. data/sig/_private/stoplight/domain/traffic_control/error_rate.rbs +13 -0
  131. data/sig/_private/stoplight/domain/traffic_recovery/consecutive_successes.rbs +9 -0
  132. data/sig/_private/stoplight/domain/traffic_recovery.rbs +9 -0
  133. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +26 -0
  134. data/sig/_private/stoplight/infrastructure/fail_safe/storage/metrics.rbs +25 -0
  135. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock.rbs +29 -0
  136. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rbs +19 -0
  137. data/sig/_private/stoplight/infrastructure/fail_safe/storage/state.rbs +25 -0
  138. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +25 -0
  139. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +19 -0
  140. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +17 -0
  141. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +27 -0
  142. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +17 -0
  143. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +30 -0
  144. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_lock.rbs +15 -0
  145. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_metrics.rbs +10 -0
  146. data/sig/_private/stoplight/infrastructure/memory/storage/state.rbs +28 -0
  147. data/sig/_private/stoplight/infrastructure/memory/storage/unbounded_metrics.rbs +25 -0
  148. data/sig/_private/stoplight/infrastructure/memory/storage/window_metrics.rbs +26 -0
  149. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +17 -0
  150. data/sig/_private/stoplight/infrastructure/notifier/generic.rbs +18 -0
  151. data/sig/_private/stoplight/infrastructure/notifier/io.rbs +14 -0
  152. data/sig/_private/stoplight/infrastructure/notifier/logger.rbs +14 -0
  153. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +24 -0
  154. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +21 -0
  155. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +34 -0
  156. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +67 -0
  157. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +19 -0
  158. data/sig/_private/stoplight/infrastructure/redis/storage/metrics.rbs +17 -0
  159. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_lock.rbs +26 -0
  160. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_metrics.rbs +10 -0
  161. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +13 -0
  162. data/sig/_private/stoplight/infrastructure/redis/storage/state.rbs +32 -0
  163. data/sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs +21 -0
  164. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +34 -0
  165. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +17 -0
  166. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +13 -0
  167. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +14 -0
  168. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +14 -0
  169. data/sig/_private/stoplight/infrastructure/system_clock.rbs +7 -0
  170. data/sig/_private/stoplight/system/light_builder.rbs +23 -0
  171. data/sig/_private/stoplight/system/light_factory.rbs +17 -0
  172. data/sig/_private/stoplight/types.rbs +6 -0
  173. data/sig/_private/stoplight/wiring/config_compatibility_validator.rbs +19 -0
  174. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +43 -0
  175. data/sig/_private/stoplight/wiring/data_store_backend.rbs +11 -0
  176. data/sig/_private/stoplight/wiring/default.rbs +26 -0
  177. data/{lib/stoplight/wiring/data_store/memory.rb → sig/_private/stoplight/wiring/default_config.rbs} +1 -4
  178. data/sig/_private/stoplight/wiring/default_configuration.rbs +29 -0
  179. data/sig/_private/stoplight/wiring/light_builder.rbs +48 -0
  180. data/sig/_private/stoplight/wiring/light_factory/traffic_control_dsl.rbs +7 -0
  181. data/sig/_private/stoplight/wiring/light_factory/traffic_recovery_dsl.rbs +7 -0
  182. data/sig/_private/stoplight/wiring/light_factory.rbs +16 -0
  183. data/sig/_private/stoplight/wiring/memory/backend.rbs +26 -0
  184. data/sig/_private/stoplight/wiring/notifier_factory.rbs +10 -0
  185. data/sig/_private/stoplight/wiring/redis/backend.rbs +38 -0
  186. data/sig/_private/stoplight/wiring/storage_set.rbs +38 -0
  187. data/sig/_private/stoplight/wiring/storage_set_builder.rbs +15 -0
  188. data/sig/_private/stoplight/wiring/system.rbs +15 -0
  189. data/sig/_private/stoplight.rbs +48 -0
  190. data/sig/stoplight/color.rbs +7 -0
  191. data/sig/stoplight/data_store.rbs +19 -0
  192. data/sig/stoplight/error.rbs +20 -0
  193. data/sig/stoplight/notifier.rbs +11 -0
  194. data/sig/stoplight/ports/configuration.rbs +19 -0
  195. data/sig/stoplight/ports/exception_matcher.rbs +8 -0
  196. data/sig/stoplight/ports/light.rbs +12 -0
  197. data/sig/stoplight/ports/light_info.rbs +5 -0
  198. data/sig/stoplight/ports/state_transition_notifier.rbs +15 -0
  199. data/sig/stoplight/ports/system.rbs +21 -0
  200. data/sig/stoplight/state.rbs +7 -0
  201. data/sig/stoplight/undefined.rbs +9 -0
  202. data/sig/stoplight/version.rbs +3 -0
  203. data/sig/stoplight.rbs +66 -0
  204. metadata +175 -47
  205. data/lib/stoplight/domain/color.rb +0 -11
  206. data/lib/stoplight/domain/data_store.rb +0 -146
  207. data/lib/stoplight/domain/error.rb +0 -42
  208. data/lib/stoplight/domain/metrics.rb +0 -64
  209. data/lib/stoplight/domain/recovery_lock_token.rb +0 -15
  210. data/lib/stoplight/domain/state.rb +0 -11
  211. data/lib/stoplight/domain/state_transition_notifier.rb +0 -25
  212. data/lib/stoplight/domain/storage/metrics.rb +0 -42
  213. data/lib/stoplight/domain/storage/recovery_lock.rb +0 -56
  214. data/lib/stoplight/domain/storage/state.rb +0 -87
  215. data/lib/stoplight/domain/strategies/run_strategy.rb +0 -22
  216. data/lib/stoplight/domain/traffic_control/base.rb +0 -74
  217. data/lib/stoplight/domain/traffic_recovery/base.rb +0 -79
  218. data/lib/stoplight/wiring/data_store/base.rb +0 -11
  219. data/lib/stoplight/wiring/data_store/redis.rb +0 -25
  220. data/lib/stoplight/wiring/default_factory_builder.rb +0 -25
  221. data/lib/stoplight/wiring/light/default_config.rb +0 -18
  222. data/lib/stoplight/wiring/light/system_config.rb +0 -11
  223. data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +0 -55
  224. data/lib/stoplight/wiring/light_factory/config_normalizer.rb +0 -71
  225. data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +0 -72
  226. data/lib/stoplight/wiring/public_api.rb +0 -29
  227. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/get_metrics.lua +0 -0
  228. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_failure.lua +0 -0
  229. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_failure.lua +0 -0
  230. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_success.lua +0 -0
  231. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_success.lua +0 -0
  232. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/release_lock.lua +0 -0
  233. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_green.lua +0 -0
  234. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_red.lua +0 -0
  235. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_yellow.lua +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d96e1b5dcce81c642d059b07b2e274b8c7dbd7f246b86ee25ce40567eaba8418
4
- data.tar.gz: e6b04f0ff592f89f345ff3f9ec9e26b86463ff0baeb56ef752cbd00b7f8952b8
3
+ metadata.gz: 186bd97d97bfe0d58398e0f7516ee2679567e8a124395291f63a91902d493ece
4
+ data.tar.gz: 59b9e3fdac592528d0c0cba71c8e5da88481bffe670e0c0129c41910264ca8a3
5
5
  SHA512:
6
- metadata.gz: 01c5c2356f84eaeade94edba7113e700682911558d165b8b60186abbe0cda3d0f453427b8ab6e96f4ab51948e3ec5a109e53e1b51cc15c46c3fd79aad4865768
7
- data.tar.gz: 94633b744ef88219f66289d9aae91bbb35b013bbea82c84f0f253abd15d4e5bd7ea5a123c39dffc36911479b1663cee71f786110be0ee15c3c8f56d035032e76
6
+ metadata.gz: 2c9f21452070dbade7a8320b1f5344ce1610ea6b2254d15f2a709a7b3cbad045f7865f12b2fd4afeb2882c3b8fabc405666b21ef17222e20733bc9c7bbb60802
7
+ data.tar.gz: c5a77e5313cdabfbe47fa536b54df0774a0899962eaf8b6e3a239d1498f50b3724018859ec2c3beb282fbe29731862356ddd41254bcd8f119a1e9ffe07f55344
data/README.md CHANGED
@@ -648,7 +648,7 @@ Fowler’s [CircuitBreaker][] article.
648
648
  [the change log]: CHANGELOG.md
649
649
  [stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry
650
650
  [stoplight-honeybadger]: https://github.com/qoqa/stoplight-honeybadger
651
- [notifier interface documentation]: https://github.com/bolshakov/stoplight/blob/main/lib/stoplight/notifier/generic.rb
651
+ [notifier interface documentation]: https://github.com/bolshakov/stoplight/blob/main/lib/stoplight/domain/state_transition_notifier.rb
652
652
  [camdez]: https://github.com/camdez
653
653
  [tfausak]: https://github.com/tfausak
654
654
  [bolshakov]: https://github.com/bolshakov
data/UPGRADING.md ADDED
@@ -0,0 +1,303 @@
1
+ ## Stoplight 5.0
2
+
3
+ Stoplight 5.0 introduces several breaking changes, so you'll need to set aside some time to update your code. The good
4
+ news is that most of the changes are pretty straightforward, and once you're done, you'll have a much cleaner and
5
+ more powerful setup.
6
+
7
+ Here's what you'll want to tackle during your upgrade. Don't worry if this looks like a lot - most of these are simple
8
+ find-and-replace operations:
9
+
10
+ - [] Update global configuration to use the new block syntax
11
+ - [] Replace any remaining `Stoplight() {}` calls with `Stoplight().run {}`
12
+ - [] Convert error handlers to tracked/skipped error lists
13
+ - [] Move fallbacks from configuration to `#run` method calls
14
+ - [] Account for Stoplight state reset after deployment
15
+ - [] Test thoroughly in a staging environment
16
+
17
+ ### Global Configuration Redesign
18
+
19
+ The biggest change you'll see is how global configuration works. We've moved away from individual setter methods to a
20
+ unified configuration block. The old individual setters were causing race conditions in production - imagine
21
+ one part of your app setting the data store while another part was setting notifiers, and depending on timing, you could
22
+ end up with inconsistent configuration states. The new block-based approach ensures all your settings are applied
23
+ atomically, which eliminates these edge cases completely.
24
+
25
+ If you have code that looks like this:
26
+
27
+ ```ruby
28
+ # Old way that won't work anymore
29
+ Stoplight.default_data_store = Stoplight::DataStore::Redis.new(redis)
30
+ Stoplight.default_notifiers += [Stoplight::Notifier::Logger.new(Rails.logger)]
31
+ Stoplight.default_error_notifier = ->(error) { Bugsnag.notify(error) }
32
+ ```
33
+
34
+ You'll need to convert it to the new block syntax:
35
+
36
+ ```ruby
37
+ # New way that's much more reliable
38
+ Stoplight.configure do |config|
39
+ config.data_store = Stoplight::DataStore::Redis.new(redis)
40
+ config.notifiers += [Stoplight::Notifier::Logger.new(Rails.logger)]
41
+ config.error_notifier = ->(error) { Bugsnag.notify(error) }
42
+ end
43
+ ```
44
+
45
+ The new approach ensures all your configuration is applied atomically, which prevents some weird edge cases where
46
+ partial configuration changes could cause unexpected behavior.
47
+
48
+ ### Cleaning Up Old Deprecated Code
49
+
50
+ Remember `Stoplight() {}` interface that got deprecated way back in 4.0? Well, it's finally gone completely. If you
51
+ still have any of these in your codebase, you'll need to convert them to use the run method:
52
+
53
+ ```ruby
54
+ # This won't work anymore
55
+ Stoplight('API Call') { make_api_request }.run
56
+
57
+ # Change it to this
58
+ Stoplight('API Call').run { make_api_request }
59
+ ```
60
+
61
+ Most codebases shouldn't have these anymore since they've been deprecated for a while, but it's worth doing a quick
62
+ grep to make sure.
63
+
64
+ ### Error Handling Gets Much Simpler
65
+
66
+ This is probably the change you'll appreciate most once you're used to it. The old `with_error_handler` callback system
67
+ was confusing and led to a lot of boilerplate code, but more importantly, it was a source of bugs. We kept
68
+ seeing cases where developers would forget to call the handler properly, or accidentally raise errors when they meant
69
+ to track them, or create configuration that leaked between different circuit breakers. The new approach is much more
70
+ straightforward and eliminates these problems entirely - you just tell Stoplight which errors to track and which to ignore.
71
+
72
+ If you have complex error handler logic like this:
73
+
74
+ ```ruby
75
+ # Old complicated way
76
+ light = Stoplight('api-call')
77
+ .with_error_handler do |error, handle|
78
+ if error.is_a?(ActiveRecord::RecordNotFound) || error.is_a?(ActiveRecord::RecordInvalid)
79
+ raise error # Don't track this error
80
+ else
81
+ handle.call(error) # Track this error
82
+ end
83
+ end
84
+ ```
85
+
86
+ You can replace it with this much cleaner approach:
87
+
88
+ ```ruby
89
+ # New simple way
90
+ light = Stoplight('api-call', skipped_errors: [ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid])
91
+ ```
92
+
93
+ The new system is way more explicit about what's happening, and you don't have to worry about accidentally forgetting
94
+ to call the handler or raising the error in the right places.
95
+
96
+ ### Fallbacks Work Differently Now
97
+
98
+ Fallbacks have moved from being configured on the light instance to being passed directly to the run method. This might
99
+ seem like a small change, but it's actually pretty powerful and solves a real problem we've observed in production
100
+ codebases. When fallbacks were configured on the light instance, you'd often end up with the same circuit breaker
101
+ protecting multiple different operations, but each operation would need its own fallback strategy. This led to either
102
+ duplicated light configurations or inappropriate fallbacks being applied to the wrong operations. The new approach
103
+ makes each operation's fallback explicit and prevents configuration contamination between different use cases.
104
+
105
+ Instead of configuring fallbacks upfront like this:
106
+
107
+ ```ruby
108
+ # Old way
109
+ light = Stoplight("Payment Gateway")
110
+ .with_fallback { |error| handle_payment_failure(error) }
111
+ result = light.run { process_payment }
112
+ ```
113
+
114
+ You now pass the fallback directly to the run method:
115
+
116
+ ```ruby
117
+ # New way
118
+ light = Stoplight('Payment Gateway')
119
+ result = light.run(->(error) { handle_payment_failure(error) }) { process_payment }
120
+ ```
121
+
122
+ This makes it much clearer which fallback belongs to which operation, and you can easily have the same circuit breaker
123
+ protect multiple operations with completely different fallback behaviors.
124
+
125
+ ### Redis Data Gets a Fresh Start
126
+
127
+ Here's the one change that doesn't require any code updates but is worth knowing about: Stoplight 5.0 uses completely
128
+ new Redis data structures that aren't compatible with the old version. We didn't make this change lightly - the old
129
+ data structures were becoming a bottleneck for the new features we wanted to build, especially around better
130
+ distributed coordination and more sophisticated error tracking. The new structures use Lua scripting for atomic
131
+ operations, which eliminates race conditions in distributed environments and provides much better performance.
132
+ Unfortunately, there was no practical way to migrate the old data format without significant complexity and potential
133
+ data corruption risks, so we opted for a clean break.
134
+
135
+ For most applications, this isn't a big deal since circuit breakers are designed to adapt quickly to current conditions
136
+ anyway. But if you have circuit breakers that take a long time to fail and you're upgrading during a period when your
137
+ dependencies are already having issues, you might want to plan your deployment timing accordingly.
138
+
139
+ The old Redis data won't be deleted, so if you really need to reference historical information for debugging purposes,
140
+ it'll still be there. But Stoplight will ignore it completely and start fresh.
141
+
142
+ ### Testing Your Migration
143
+
144
+ Once you've made all these changes, definitely test everything thoroughly in a staging environment that mirrors your
145
+ production setup. Pay special attention to how your circuit breakers behave under load and make sure your error
146
+ classification is working the way you expect.
147
+
148
+ The new error handling system is much more explicit, but that also means if you get the configuration wrong, it'll be
149
+ more obvious what's happening (which is actually a good thing).
150
+
151
+ ### Getting Help
152
+
153
+ If you run into any issues during the migration, don't hesitate to post a message to our [Discussions forum]. We've
154
+ tried to make the error messages as clear as possible when something's misconfigured. The new APIs are much more
155
+ consistent and predictable once you get used to them.
156
+
157
+ Overall, while this upgrade does require some work upfront, the end result is a much cleaner and more reliable circuit
158
+ breaker setup that should serve you well going forward.
159
+
160
+ ## Stoplight 4.0
161
+
162
+ ### Notifiers have dropped!
163
+
164
+ With this release, we've officially moved all third-party notifiers out of Stoplight.
165
+ The only notifiers that remain to be in the Stoplight distribution are:
166
+
167
+ * `Stoplight::Notifier::IO`
168
+ * `Stoplight::Notifier::Logger`
169
+
170
+ #### Why was this decision made?
171
+
172
+ We've taken this decision for the following technical reasons:
173
+
174
+ * We wanted to free the maintainers from supporting all the different notifiers, relying more on
175
+ the community to maintain them based on broad interest.
176
+ * Moving notifiers into separate gems allow to solve the dependency issues once and for all.
177
+ The notifiers gems will be able to automatically pull any necessary dependency, without having to
178
+ rely on the developer to do so.
179
+ * With the community-supported notifiers, we can solve the third-party services compatibility issue. Such services
180
+ arise and go and Stoplight should not depend on their lifecycle.
181
+
182
+ #### So, what does this mean for me?
183
+
184
+ Unfortunately, we cannot support all the possible notifiers.
185
+
186
+ * All the notifiers relying on third-party services have been dropped.
187
+ * We implemented the Sentry notifier as an external [stoplight-sentry] gem. You can use it as a reference implementation.
188
+ * We added a [Community-supported notifiers] section and encourage you to contribute by adding your notifiers.
189
+
190
+ #### All right! What should I change in my code immediately after upgrading?
191
+
192
+ * If you just use the default, `Stoplight::Notifier::IO`, or `Stoplight::Notifier::Logger` notifiers, then you
193
+ don't need to do anything!
194
+ * Otherwise, you many need to find a third-party notifier:
195
+
196
+ ```ruby
197
+ # Gemfile
198
+ gem 'sentry'
199
+ gem 'stoplight'
200
+ gem 'stoplight-sentry'
201
+
202
+ # Code
203
+ Stoplight.default_notifiers += [Stoplight::Sentry::Notifier.new(Sentry)]
204
+ ```
205
+ * If you cannot find a notifier gem, you may need to implement your own. Consider checking the
206
+ [How to implement your own notifier?] guide which contains all the information needed to implement a notifier. You
207
+ can use [dropped notifiers] for the inspiration.
208
+
209
+ ### Stoplight() interface has changed
210
+
211
+ We moved block argument from the `Stoplight()` function to the `#run` method.
212
+
213
+ #### Why was this decision made?
214
+
215
+ We aim to make Stoplight's configuration sharable across the code. Due to this change, it's possible to run
216
+ different code blocks with the same Stoplight configuration:
217
+
218
+ ```ruby
219
+ light = Stoplight('http-api').with_cool_off_time(300)
220
+ light.run { call_this }
221
+ light.run { call_that }
222
+ ```
223
+
224
+ Another benefit is that now you can easily inspect the status of the circuit breaker [without passing an empty block]:
225
+
226
+ ```ruby
227
+ light.color
228
+ ```
229
+
230
+ #### So, what does this mean for me?
231
+
232
+ Stoplight 4.0 supports both an old and a new interface. However, the old interface is deprecated. To
233
+ update to Stoplight 5.0, you will need to switch to the new syntax.
234
+
235
+ ```diff
236
+ - Stoplight('example') { 1 / 0 }.run
237
+ + Stoplight('example').run { 1 / 0 }
238
+ ```
239
+
240
+ ### Stoplight::Light becomes private
241
+
242
+ This class has always considered private but some developers preferred to use `Stoplight::Light#new` instead of
243
+ `Stoplight()`. In the next major release the use of `Stoplight::Light#new` will be forbidden.
244
+
245
+ #### Why was this decision made?
246
+
247
+ We want to provide a simple, concise Stoplight interface. Having a single public interface guarantees users
248
+ use it the right way.
249
+
250
+ #### So, what does this mean for me?
251
+
252
+ Any use of `Stoplight::Light` outside of Stoplight itself is deprecated in Stoplight 4.0. To update to the
253
+ next major version (Stoplight 5.0), you will need to change a few things:
254
+
255
+ ```diff
256
+ - Stoplight::Light.default_data_store = data_store
257
+ + Stoplight.default_data_store = data_store
258
+ ```
259
+
260
+ ```diff
261
+ - Stoplight::Light.default_notifiers += [notifier]
262
+ + Stoplight.default_notifiers += [notifier]
263
+ ```
264
+
265
+ ```diff
266
+ - Stoplight::Light.default_error_notifier = ->(*) {}
267
+ + Stoplight.default_error_notifier = ->(*) {}
268
+ ```
269
+
270
+ In case you prefer to check types in your specs, you may need to switch it from checking for `Stoplight::Light` class
271
+ to `Stoplight::CircuitBreaker`. The `Stoplight::CircuitBreaker` abstract module considered the only public interface.
272
+
273
+ Under the hood, we use two slightly different implementations to provide a smooth transition to the new interface
274
+ and to make it possible to pass Stoplight as a dependency.
275
+
276
+ #### All right! What should I change in my code immediately after upgrading?
277
+
278
+ You might encounter a few deprecation warnings, but you do not need to changes anything in your code in this release.
279
+
280
+ ### Change in Redis Data Structures
281
+
282
+ Redis Data store in Stoplight 4.0 uses a new data structure under the hood.
283
+
284
+ #### Why was this decision made?
285
+
286
+ This decision was made to enable the implementation of error counting using a [sliding window] approach. This feature
287
+ allows Stoplight to count only errors that have occurred recently.
288
+
289
+ #### So, what does this mean for me?
290
+
291
+ After upgrading to this version, Stoplight will not be aware of errors that occurred before the update.
292
+
293
+ #### All right! What should I change in my code immediately after upgrading?
294
+
295
+ Nothing. Stoplight will function as usual.
296
+
297
+ [stoplight-sentry]: https://github.com/bolshakov/stoplight-sentry
298
+ [Community-supported notifiers]: https://github.com/bolshakov/stoplight/tree/master#community-supported-notifiers
299
+ [How to implement your own notifier?]: https://github.com/bolshakov/stoplight/blob/master/lib/stoplight/notifier/generic.rb
300
+ [dropped notifiers]: https://github.com/bolshakov/stoplight/tree/v3.0.1/lib/stoplight/notifier
301
+ [without passing an empty block]: https://github.com/bolshakov/stoplight-admin/blob/9c9848eb94410e46b20972548f0863db224cb6da/lib/sinatra/stoplight_admin.rb#L30
302
+ [sliding window]: https://github.com/bolshakov/stoplight#custom-window-size
303
+ [Discussions forum]: https://github.com/bolshakov/stoplight/discussions/categories/q-a
@@ -11,7 +11,12 @@ end
11
11
  module Stoplight
12
12
  module Generators
13
13
  class InstallGenerator < ::Rails::Generators::Base # :nodoc:
14
- source_root File.expand_path("templates", __dir__)
14
+ case (root = __dir__)
15
+ when String
16
+ source_root File.expand_path("templates", root)
17
+ else
18
+ raise "cannot find templates root"
19
+ end
15
20
 
16
21
  class_option :with_admin_panel, type: :boolean, optional: true,
17
22
  desc: "Define whether to set up admin panel"
@@ -8,7 +8,7 @@ module Stoplight
8
8
  attr_reader :data_store
9
9
  private :data_store
10
10
 
11
- # @param data_store [Stoplight::Domain::DataStore]
11
+ # @param data_store [Stoplight::Domain::_DataStore]
12
12
  def initialize(data_store:)
13
13
  @data_store = data_store
14
14
  end
@@ -14,16 +14,32 @@ module Stoplight
14
14
  Dependencies.new(data_store:)
15
15
  end
16
16
 
17
+ def time_ago_in_words(time)
18
+ time_difference = Time.now.utc - time
19
+ if time_difference < 1
20
+ "just now"
21
+ elsif time_difference < 60
22
+ "#{time_difference.to_i}s ago"
23
+ elsif time_difference < 3600
24
+ "#{(time_difference / 60).to_i}m ago"
25
+ elsif time_difference < 86400
26
+ "#{(time_difference / 3600).to_i}h ago"
27
+ else
28
+ "#{(time_difference / 86400).to_i}d ago"
29
+ end
30
+ end
31
+
17
32
  private def data_store
18
33
  if settings.data_store.is_a?(Stoplight::DataStore::Memory)
19
34
  raise "Stoplight Admin requires a persistent data store, but the current data store is Memory. " \
20
35
  "Please configure a different data store in your Stoplight configuration."
21
36
  else
22
37
  Stoplight::Wiring::LightBuilder.new(
23
- {
24
- data_store: settings.data_store,
25
- config: Wiring::Light::DefaultConfig
26
- }
38
+ config: Wiring::DefaultConfig.with(
39
+ name: "noname",
40
+ data_store: settings.data_store
41
+ ),
42
+ factory: nil
27
43
  ).__send__(:data_store)
28
44
  end
29
45
  end
@@ -32,16 +32,22 @@ module Stoplight
32
32
  # @return [<Stoplight::Failure>]
33
33
  attr_reader :failures
34
34
 
35
+ # @!attribute failure_count
36
+ # @return [Integer]
37
+ attr_reader :failure_count
38
+
35
39
  # @param name [String]
36
40
  # @param color [String]
37
41
  # @param state [String]
38
42
  # @param failures [<Stoplight::Failure>]
39
- def initialize(name:, color:, state:, failures:)
43
+ # @param failure_count [Integer, nil]
44
+ def initialize(name:, color:, state:, failures:, failure_count: nil)
40
45
  @id = SecureRandom.uuid
41
46
  @name = name
42
47
  @color = color
43
48
  @state = state
44
49
  @failures = failures
50
+ @failure_count = failure_count
45
51
  end
46
52
 
47
53
  def latest_failure
@@ -73,20 +79,24 @@ module Stoplight
73
79
  [-COLORS.index(color), name]
74
80
  end
75
81
 
82
+ def last_check = latest_failure&.time # TODO: take into account positive checks as well
83
+
76
84
  # @return [String, nil]
77
85
  def last_check_in_words
78
86
  last_error_time = latest_failure&.time
79
87
  return unless last_error_time
80
88
 
81
- time_difference = Time.now - last_error_time
89
+ time_difference = Time.now.utc - last_error_time
82
90
  if time_difference < 1
83
91
  "just now"
84
92
  elsif time_difference < 60
85
93
  "#{time_difference.to_i}s ago"
86
94
  elsif time_difference < 3600
87
95
  "#{(time_difference / 60).to_i}m ago"
88
- else
96
+ elsif time_difference < 86400
89
97
  "#{(time_difference / 3600).to_i}h ago"
98
+ else
99
+ "#{(time_difference / 86400).to_i}d ago"
90
100
  end
91
101
  end
92
102
 
@@ -114,13 +124,19 @@ module Stoplight
114
124
  def description_message
115
125
  case color
116
126
  when RED
117
- if locked? && failures.empty?
127
+ if latest_failure
128
+ "#{latest_failure.error_class}: #{latest_failure.error_message}"
129
+ elsif locked?
118
130
  "Circuit manually locked open"
119
131
  else
120
- "#{latest_failure.error_class}: #{latest_failure.error_message}"
132
+ "Not available"
121
133
  end
122
134
  when Stoplight::Color::YELLOW
123
- "#{latest_failure.error_class}: #{latest_failure.error_message}"
135
+ if latest_failure
136
+ "#{latest_failure.error_class}: #{latest_failure.error_message}"
137
+ else
138
+ "Not available"
139
+ end
124
140
  when GREEN
125
141
  if locked?
126
142
  "Circuit manually locked closed"
@@ -4,11 +4,11 @@ module Stoplight
4
4
  class Admin
5
5
  class LightsRepository
6
6
  # @!attribute data_store
7
- # @return [Stoplight::Domain::DataStore]
7
+ # @return [Stoplight::Domain::_DataStore]
8
8
  attr_reader :data_store
9
9
  private :data_store
10
10
 
11
- # @param data_store [Stoplight::Domain::DataStore]
11
+ # @param data_store [Stoplight::Domain::_DataStore]
12
12
  def initialize(data_store:)
13
13
  @data_store = data_store
14
14
  end
@@ -52,7 +52,7 @@ module Stoplight
52
52
  # @return [void]
53
53
  def unlock(name)
54
54
  config = build_config(name)
55
- data_store.set_state(config, Domain::State::UNLOCKED)
55
+ data_store.set_state(config, State::UNLOCKED)
56
56
  end
57
57
 
58
58
  # @param name [String] removes light metadata by its name
@@ -74,12 +74,13 @@ module Stoplight
74
74
  name: name,
75
75
  color: state_snapshot.color,
76
76
  state: state_snapshot.locked_state,
77
- failures: [metrics.last_error].compact
77
+ failures: [metrics.last_error].compact,
78
+ failure_count: metrics.consecutive_errors
78
79
  )
79
80
  end
80
81
 
81
82
  private def build_config(name)
82
- Wiring::Light::DefaultConfig.with(name:)
83
+ Wiring::DefaultConfig.with(name:)
83
84
  end
84
85
  end
85
86
  end
@@ -107,8 +107,11 @@
107
107
  <p><%= light.description_comment %></p>
108
108
  </div>
109
109
  <% if light.latest_failure %>
110
- <div class="whitespace-nowrap">
111
- <%= Time.at(light.latest_failure.time).strftime("%T") %>
110
+ <div class="whitespace-nowrap text-center">
111
+ <%= light.latest_failure.time.strftime("%F %T") %>
112
+ <span class="text-gray-400 dark:text-gray-500 uppercase tracking-wide">
113
+ UTC
114
+ </span>
112
115
  </div>
113
116
  <% end %>
114
117
  </div>
@@ -117,13 +120,13 @@
117
120
  <!-- Stats Row -->
118
121
  <div class="flex justify-between text-xs text-gray-500 dark:text-gray-400 mt-3">
119
122
  <div>
120
- <span class="font-medium">Failures:</span> <%= light.failures.count %>
123
+ <span class="font-medium">Failures:</span> <%= light.failure_count %>
121
124
  </div>
122
125
 
123
- <% light.last_check_in_words.then do |last_check| %>
126
+ <% light.last_check.then do |last_check| %>
124
127
  <% if last_check %>
125
128
  <div>
126
- <span class="font-medium">Last Check:</span> <%= light.last_check_in_words %>
129
+ <span class="font-medium">Last Check:</span><%= time_ago_in_words(last_check) %>
127
130
  </div>
128
131
  <% end %>
129
132
  <% end %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Color
5
+ GREEN = "green"
6
+ YELLOW = "yellow"
7
+ RED = "red"
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module Stoplight
2
+ module DataStore
3
+ # @api private not for public use
4
+ class Base
5
+ end
6
+
7
+ class Redis < Base
8
+ # @!attribute redis
9
+ # @return [::Redis, ConnectionPool<::Redis>]
10
+ attr_reader :redis
11
+
12
+ # @!attribute warn_on_clock_skew
13
+ # @return [Boolean]
14
+ attr_reader :warn_on_clock_skew
15
+
16
+ # @param redis [::Redis, ConnectionPool<::Redis>]
17
+ # @param warn_on_clock_skew [Boolean] (true) Whether to warn about clock skew between Redis and
18
+ # the application server
19
+ def initialize(redis, warn_on_clock_skew: true)
20
+ @warn_on_clock_skew = warn_on_clock_skew
21
+ @redis = redis
22
+ end
23
+ end
24
+
25
+ class Memory < Base
26
+ end
27
+ end
28
+ end
@@ -9,28 +9,28 @@ module Stoplight
9
9
  class << self
10
10
  # Creates a new +CompatibilityResult+ instance representing a compatible strategy.
11
11
  #
12
- # @return [CompatibilityResult] An instance with no errors.
12
+ # @return An instance with no errors.
13
13
  def compatible
14
14
  new(errors: [])
15
15
  end
16
16
 
17
17
  # Creates a new +CompatibilityResult+ instance representing an incompatible strategy.
18
18
  #
19
- # @param errors [Array<String>] List of error messages indicating incompatibility.
20
- # @return [CompatibilityResult] An instance with the provided errors.
19
+ # @param errors List of error messages indicating incompatibility.
20
+ # @return An instance with the provided errors.
21
21
  def incompatible(*errors)
22
22
  new(errors:)
23
23
  end
24
24
  end
25
25
 
26
26
  # Initializes a new `CompatibilityResult` instance.
27
- # @param errors [Array<String>] List of error messages if the strategy is not compatible.
27
+ # @param errors List of error messages if the strategy is not compatible.
28
28
  def initialize(errors: [])
29
29
  @errors = errors.freeze
30
30
  end
31
31
 
32
32
  # Checks if the strategy is compatible.
33
- # @return [Boolean] `true` if there are no errors, `false` otherwise.
33
+ # @return `true` if there are no errors, `false` otherwise.
34
34
  def compatible?
35
35
  @errors.empty?
36
36
  end
@@ -38,11 +38,11 @@ module Stoplight
38
38
  def incompatible? = !compatible?
39
39
 
40
40
  # Retrieves the list of error messages.
41
- # @return [Array<String>] The list of error messages.
41
+ # @return The list of error messages.
42
42
  attr_reader :errors
43
43
 
44
44
  # Retrieves a concatenated error message string.
45
- # @return [String, nil] A string containing all error messages joined by "; ",
45
+ # @return A string containing all error messages joined by "; ",
46
46
  # or `nil` if the strategy is compatible.
47
47
  def error_messages
48
48
  unless compatible?