stoplight 5.7.0 → 5.8.2

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} +47 -55
  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
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module Error
6
- Base = Class.new(StandardError)
7
- ConfigurationError = Class.new(Base)
8
- IncorrectColor = Class.new(Base)
9
-
10
- class RedLight < Base
11
- # @!attribute light_name
12
- # @return [String] The light's name
13
- attr_reader :light_name
14
-
15
- # @!attribute cool_off_time
16
- # @return [Numeric] Cool-off period in seconds
17
- attr_reader :cool_off_time
18
-
19
- # @!attribute retry_after
20
- # @return [Time] Absolute Time after which a recovery attempt can occur
21
- attr_reader :retry_after
22
-
23
- # Initializes a new RedLight error.
24
- #
25
- # @param light_name [String] The light's name
26
- #
27
- # @option cool_off_time [Numeric] Cool-off period in seconds
28
- #
29
- # @option retry_after [Time] Absolute Time after which a recovery attempt can occur
30
- #
31
- # @return [Stoplight::Error::RedLight]
32
- def initialize(light_name, cool_off_time:, retry_after:)
33
- @light_name = light_name
34
- @cool_off_time = cool_off_time
35
- @retry_after = retry_after
36
-
37
- super(light_name)
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- # Request metrics over a given window.
6
- #
7
- # @!attribute successes
8
- # A number of successes withing requested window. Zero for non-windowed metrics
9
- # @return [Integer]
10
- #
11
- # @!attribute errors
12
- # A number of errors withing requested window. Zero for non-windowed metrics
13
- # @return [Integer]
14
- #
15
- # @!attribute consecutive_errors
16
- # A number of consecutive errors
17
- # @return [Integer]
18
- #
19
- # @!attribute consecutive_successes
20
- # A number of consecutive successes
21
- # @return [Integer]
22
- #
23
- # @!attribute last_error
24
- # @return [Stoplight::Domain::Failure, nil]
25
- #
26
- # @!attribute last_success_at
27
- # @return [Time, nil]
28
- #
29
- # @api private
30
- Metrics = Data.define(
31
- :successes,
32
- :errors,
33
- :consecutive_errors,
34
- :consecutive_successes,
35
- :last_error,
36
- :last_success_at
37
- ) do
38
- # Calculates the error rate based on the number of successes and errors.
39
- #
40
- # @return [Float]
41
- def error_rate
42
- return unless requests # we effectively check if this is windowed metrics
43
-
44
- if (successes + errors).zero?
45
- 0.0
46
- else
47
- errors.fdiv(successes + errors)
48
- end
49
- end
50
-
51
- # @return [Integer]
52
- def requests
53
- if successes && errors # we effectively check if this is windowed metrics
54
- successes + errors
55
- end
56
- end
57
-
58
- # @return [Time, nil]
59
- def last_error_at
60
- last_error&.time
61
- end
62
- end
63
- end
64
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- # Token representing an acquired recovery lock.
6
- #
7
- # Returned by +DataStore#acquire_recovery_lock+ and passed to
8
- # +DataStore#release_recovery_lock+ to identify which lock to release.
9
- #
10
- # The actual locking mechanism lives in DataStore implementations,
11
- # not in these tokens.
12
- class RecoveryLockToken
13
- end
14
- end
15
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module State
6
- UNLOCKED = "unlocked"
7
- LOCKED_GREEN = "locked_green"
8
- LOCKED_RED = "locked_red"
9
- end
10
- end
11
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- # Base class for creating custom notifiers in Stoplight.
6
- # This is an abstract class that defines the interface for notifiers.
7
- #
8
- # @abstract Subclasses must implement the `notify` method to define custom notification logic.
9
- # :nocov:
10
- class StateTransitionNotifier # ColorTransition?????
11
- # Sends a notification when a Stoplight changes state.
12
- #
13
- # @param config [Stoplight::Domain::Config] The Stoplight instance triggering the notification.
14
- # @param from_color [String] The previous state color of the Stoplight.
15
- # @param to_color [String] The new state color of the Stoplight.
16
- # @param error [Exception, nil] The error (if any) that caused the state change.
17
- # @return [String] The result of the notification process.
18
- #
19
- def notify(config, from_color, to_color, error)
20
- raise NotImplementedError
21
- end
22
- end
23
- # :nocov:
24
- end
25
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module Storage
6
- # Encapsulates metrics storage for circuit breaker execution tracking.
7
- #
8
- # This abstraction isolates metrics collection and retrieval from the
9
- # broader data store concerns, enabling:
10
- # - Purpose-built implementations optimized for time-series data
11
- # - Independent scaling and optimization of metrics vs. state storage
12
- # - Clearer separation between "what happened" (metrics) and "what to do" (state)
13
- #
14
- # Lifecycle: A Metrics instance is scoped to a single circuit breaker
15
- # configuration. Each circuit gets its own metrics store instance,
16
- # allowing different circuits to use different storage strategies.
17
- #
18
- # @abstract
19
- class Metrics
20
- # Retrieves a snapshot of current metrics for decision-making.
21
- #
22
- # @return [Stoplight::Domain::Metrics]
23
- def metrics_snapshot = raise NotImplementedError
24
-
25
- # Records a successful circuit breaker execution
26
- #
27
- # @return [void]
28
- def record_success = raise NotImplementedError
29
-
30
- # Records a failed circuit breaker execution
31
- #
32
- # @param error [StandardError]
33
- # @return [void]
34
- def record_failure(error) = raise NotImplementedError
35
-
36
- # Clears all metrics for this circuit
37
- # @return [void]
38
- def clear = raise NotImplementedError
39
- end
40
- end
41
- end
42
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module Storage
6
- # Encapsulates recovery lock management for coordinating recovery probes.
7
- #
8
- # When a circuit enters YELLOW state (half-open), it begins sending
9
- # "recovery probes" - test requests to check if the protected service
10
- # has recovered. In distributed deployments with multiple instances,
11
- # recovery locks ensure only ONE instance sends probes at a time.
12
- #
13
- # Without coordination, all instances would simultaneously:
14
- # 1. Detect the circuit is YELLOW
15
- # 2. Send recovery probes to the struggling service
16
- # 3. Potentially overwhelm it with "test" traffic
17
- #
18
- # Lock Lifecycle:
19
- #
20
- # Instance A: acquire_lock -> probe -> release_lock
21
- # Instance B: acquire_lock -> nil (already held) -> skip probe
22
- # Instance C: acquire_lock -> nil (already held) -> skip probe
23
- #
24
- # Lock Semantics:
25
- # - Returns +nil+ if lock is already held. Never blocks waiting for lock availability
26
- # - Locks must automatically expire when persisted storage is used
27
- # - Failed releases are acceptable (timeout provides safety)
28
- #
29
- # @abstract
30
- # @see Stoplight::Domain::Strategies::YellowRunStrategy
31
- class RecoveryLock
32
- # Attempts to acquire recovery lock for exclusive probe execution.
33
- #
34
- # This method tries to acquire a lock that serializes recovery probe
35
- # execution across multiple instances. If the lock is already held by
36
- # another instance, returns +nil+ immediately without blocking.
37
- #
38
- # @return [Stoplight::Domain::RecoveryLockToken, nil]
39
- # - +RecoveryLockToken+: Lock acquired, caller should send probe
40
- # - +nil+: Lock unavailable, another instance is probing
41
- #
42
- def acquire_lock = raise NotImplementedError
43
-
44
- # Releases a previously acquired lock.
45
- #
46
- # This method releases the lock token returned by +#acquire_lock+,
47
- # allowing other instances to acquire it. Release should be called
48
- # in an ensure block to guarantee cleanup even if probe fails.
49
- #
50
- # @param lock [Stoplight::Domain::RecoveryLockToken] The token returned by +#acquire_lock+
51
- # @return [void]
52
- def release_lock(lock) = raise NotImplementedError
53
- end
54
- end
55
- end
56
- end
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module Storage
6
- # Encapsulates circuit breaker state storage.
7
- #
8
- # State management handles the current operational mode of a circuit breaker:
9
- # - Color (GREEN/YELLOW/RED) - whether the circuit is open or closed
10
- # - Lock state (LOCKED_GREEN/LOCKED_RED/UNLOCKED) - manual overrides
11
- # - State transitions - tracking color changes for notifications #
12
- #
13
- # State requires stronger consistency than metrics because:
14
- # - Multiple instances must agree on circuit color
15
- # - Race conditions during transitions must be handled
16
- # - Lock states must be immediately visible across instances
17
- #
18
- # @abstract
19
- # @see Stoplight::Domain::Storage::Metrics
20
- class State
21
- # Retrieves current state snapshot for decision-making.
22
- #
23
- # The snapshot is an immutable view of the circuit's current state,
24
- # including its color and lock status. This method is called on every
25
- # circuit breaker invocation to determine whether to allow traffic.
26
- #
27
- # This is called on every request, so implementations should be fast.
28
- #
29
- # @return [Stoplight::Domain::StateSnapshot]
30
- def state_snapshot = raise NotImplementedError
31
-
32
- # Sets the lock state of the circuit.
33
- #
34
- # Locks allow manual override of circuit behavior:
35
- # - LOCKED_GREEN: Force circuit closed (allow all traffic)
36
- # - LOCKED_RED: Force circuit open (block all traffic)
37
- # - UNLOCKED: Follow normal circuit breaker rules
38
- #
39
- # Lock states take precedence over color states. A locked circuit
40
- # ignores failure thresholds and stays in the locked state until
41
- # explicitly unlocked.
42
- #
43
- # Use Cases:
44
- # - Emergency traffic control during incidents
45
- # - Maintenance windows (lock RED to prevent traffic)
46
- # - Gradual rollout (lock GREEN during testing)
47
- #
48
- # @param state [String] The new state to set.
49
- # @return [String] The state that was set.
50
- def set_state(state) = raise NotImplementedError
51
-
52
- # Transitions the Stoplight to the specified color.
53
- #
54
- # This method performs a color transition operation that works across distributed instances
55
- # of the light. It ensures that in a multi-instance environment, only one instance
56
- # is considered the "first" to perform the transition (and therefore responsible for
57
- # triggering notifications).
58
- #
59
- # @param color [String] The target color/state to transition to.
60
- # Should be one of Stoplight::Color::GREEN, Stoplight::Color::YELLOW, or Stoplight::Color::RED.
61
- #
62
- # @return [Boolean] Returns +true+ if this instance was the first to perform this specific transition
63
- # (and should therefore trigger notifications). Returns +false+ if another instance already
64
- # initiated this transition.
65
- #
66
- # @note In distributed environments with multiple instances, race conditions can occur when instances
67
- # attempt conflicting transitions simultaneously (e.g., one instance tries to transition from
68
- # YELLOW to GREEN while another tries YELLOW to RED). The implementation handles this, but
69
- # be aware that the last operation may determine the final color of the light.
70
- #
71
- def transition_to_color(color) = raise NotImplementedError
72
-
73
- # Clears all state data for this circuit.
74
- #
75
- # This removes the circuit from storage entirely, resetting it to
76
- # default (unlocked, green) state. The next invocation will start
77
- # with fresh state.
78
- #
79
- # @note This does NOT clear metrics. If you want to fully
80
- # reset a circuit, clear both state and metrics stores.
81
- #
82
- # @return [void]
83
- def clear = raise NotImplementedError
84
- end
85
- end
86
- end
87
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module Strategies
6
- # Represents an abstract strategy for running a light's operations.
7
- # Every new strategy should be a child of this class.
8
- #
9
- # @api private
10
- # @abstract
11
- class RunStrategy
12
- # @param fallback [Proc, nil] A fallback proc to execute in case of an error.
13
- # @param state_snapshot [Stoplight::Domain::StateSnapshot]
14
- # :nocov:
15
- def execute(fallback, state_snapshot:, &code)
16
- raise NotImplementedError, "Subclasses must implement the execute method"
17
- end
18
- # :nocov:
19
- end
20
- end
21
- end
22
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module TrafficControl
6
- # Strategies for determining when a Stoplight should change color to red.
7
- #
8
- # These strategies evaluate the current state and metrics of a Stoplight to decide
9
- # if traffic should be stopped (i.e., if the light should turn RED).
10
- #
11
- # @example Creating a custom strategy
12
- # class ErrorRateStrategy < Stoplight::Domain::TrafficControl::Base
13
- # def check_compatibility(config)
14
- # if config.window_size.nil?
15
- # incompatible("`window_size` should be set")
16
- # else
17
- # compatible
18
- # end
19
- # end
20
- #
21
- # def stop_traffic?(config, metrics)
22
- # total = metrics.successes + metrics.failures
23
- # return false if total < 10 # Minimum sample size
24
- #
25
- # error_rate = metrics.failures.fdiv(total)
26
- # error_rate >= 0.5 # Stop traffic when error rate reaches 50%
27
- # end
28
- # end
29
- #
30
- # @abstract
31
- # @api private
32
- class Base
33
- # Checks if the strategy is compatible with the given Stoplight configuration.
34
- #
35
- # @param config [Stoplight::Domain::Config]
36
- # @return [Stoplight::Domain::CompatibilityResult]
37
- # :nocov:
38
- def check_compatibility(config)
39
- raise NotImplementedError
40
- end
41
- # :nocov:
42
-
43
- # Determines whether traffic should be stopped based on the Stoplight's
44
- # current state and metrics.
45
- #
46
- # @param config [Stoplight::Domain::Config]
47
- # @param metrics [Stoplight::Domain::Metrics]
48
- # @return [Boolean] true if traffic should be stopped (rec), false otherwise (green)
49
- # :nocov:
50
- def stop_traffic?(config, metrics)
51
- raise NotImplementedError
52
- end
53
- # :nocov:
54
-
55
- # @param other [any]
56
- # @return [Boolean]
57
- def ==(other)
58
- other.is_a?(self.class)
59
- end
60
-
61
- # Returns a compatibility result indicating the strategy is compatible.
62
- #
63
- # @return [Stoplight::Domain::CompatibilityResult] A compatible result.
64
- private def compatible = CompatibilityResult.compatible
65
-
66
- # Returns a compatibility result indicating the strategy is incompatible.
67
- #
68
- # @param errors [Array<String>] The list of error messages describing incompatibility.
69
- # @return [Stoplight::Domain::CompatibilityResult] An incompatible result.
70
- private def incompatible(*errors) = CompatibilityResult.incompatible(*errors)
71
- end
72
- end
73
- end
74
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Domain
5
- module TrafficRecovery
6
- # Strategies for determining how to recover traffic flow through the Stoplight.
7
- # These strategies evaluate recovery metrics to decide which color the Stoplight should
8
- # transition to during the recovery process.
9
- #
10
- # @example Creating a custom traffic recovery strategy
11
- # class GradualRecovery < Stoplight::Domain::TrafficRecovery::Base
12
- # def initialize(min_success_rate: 0.8, min_samples: 100)
13
- # @min_success_rate = min_success_rate
14
- # @min_samples = min_samples
15
- # end
16
- #
17
- # def determine_color(config, metrics)
18
- # total_probes = metrics.recovery_probe_successes + metrics.recovery_probe_errors
19
- #
20
- # if total_probes < @min_samples
21
- # return Color::YELLOW # Keep recovering, not enough samples
22
- # end
23
- #
24
- # success_rate = metrics.recovery_probe_successes.fdiv(total_probes)
25
- # if success_rate >= @min_success_rate
26
- # Color::GREEN # Recovery successful
27
- # elsif success_rate <= 0.2
28
- # Color::RED # Recovery failed, too many errors
29
- # else
30
- # Color::YELLOW # Continue recovery
31
- # end
32
- # end
33
- # end
34
- #
35
- # @abstract
36
- # @api private
37
- class Base
38
- # Checks if the strategy is compatible with the given Stoplight configuration.
39
- #
40
- # @param config [Stoplight::Domain::Config]
41
- # @return [Stoplight::Domain::CompatibilityResult]
42
- # :nocov:
43
- def check_compatibility(config)
44
- raise NotImplementedError
45
- end
46
- # :nocov:
47
-
48
- # Determines the appropriate recovery state based on the Stoplight's
49
- # current metrics and recovery progress.
50
- #
51
- # @param config [Stoplight::Domain::Config]
52
- # @param metrics [Stoplight::Domain::Metrics]
53
- # @return [TrafficRecovery::Decision]
54
- # :nocov:
55
- def determine_color(config, metrics)
56
- raise NotImplementedError
57
- end
58
- # :nocov:
59
-
60
- # @param other [any]
61
- # @return [Boolean]
62
- def ==(other)
63
- other.is_a?(self.class)
64
- end
65
-
66
- # Returns a compatibility result indicating the strategy is compatible.
67
- #
68
- # @return [Stoplight::Domain::CompatibilityResult] A compatible result.
69
- private def compatible = CompatibilityResult.compatible
70
-
71
- # Returns a compatibility result indicating the strategy is incompatible.
72
- #
73
- # @param errors [Array<String>] The list of error messages describing incompatibility.
74
- # @return [Stoplight::Domain::CompatibilityResult] An incompatible result.
75
- private def incompatible(*errors) = CompatibilityResult.incompatible(*errors)
76
- end
77
- end
78
- end
79
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- module DataStore
6
- # @abstract
7
- class Base
8
- end
9
- end
10
- end
11
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- module DataStore
6
- class Redis < Base
7
- # @!attribute redis
8
- # @return [::Redis, ConnectionPool<::Redis>]
9
- attr_reader :redis
10
-
11
- # @!attribute warn_on_clock_skew
12
- # @return [Boolean]
13
- attr_reader :warn_on_clock_skew
14
-
15
- # @param redis [::Redis, ConnectionPool<::Redis>]
16
- # @param warn_on_clock_skew [Boolean] (true) Whether to warn about clock skew between Redis and
17
- # the application server
18
- def initialize(redis, warn_on_clock_skew: true)
19
- @warn_on_clock_skew = warn_on_clock_skew
20
- @redis = redis
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- # Builds the default LightFactory from user-provided configuration which is
6
- # used as the basis for all circuit breakers.
7
- #
8
- class DefaultFactoryBuilder
9
- # @!attribute [r] configuration
10
- # @return [Stoplight::Wiring::DefaultConfiguration]
11
- #
12
- attr_reader :configuration
13
-
14
- def initialize
15
- @configuration = DefaultConfiguration.new
16
- end
17
-
18
- # @return [Stoplight::Wiring::LightFactory]
19
- # @api private the method is used internally by Stoplight
20
- def build
21
- LightFactory.new(configuration.to_h)
22
- end
23
- end
24
- end
25
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- module Light
6
- # Provides default settings for the Stoplight library.
7
- # @api private
8
- DefaultConfig = Domain::Config.empty.with(
9
- cool_off_time: Default::COOL_OFF_TIME,
10
- threshold: Default::THRESHOLD,
11
- recovery_threshold: Default::RECOVERY_THRESHOLD,
12
- window_size: Default::WINDOW_SIZE,
13
- tracked_errors: Default::TRACKED_ERRORS,
14
- skipped_errors: Default::SKIPPED_ERRORS
15
- )
16
- end
17
- end
18
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- module Light
6
- SystemConfig = DefaultConfig.with(
7
- recovery_threshold: 3
8
- )
9
- end
10
- end
11
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- class LightFactory
6
- # Validates that traffic control and recovery strategies are
7
- # compatible with the provided configuration.
8
- #
9
- # Different strategies have different configuration requirements:
10
- # - ErrorRate requires window_size and threshold ∈ [0,1]
11
- # - ConsecutiveErrors requires threshold > 0
12
- # - ConsecutiveSuccesses requires recovery_threshold > 0
13
- #
14
- # @raise [Stoplight::Error::ConfigurationError] if incompatible
15
- class CompatibilityValidator
16
- private attr_reader :dependencies
17
- private attr_reader :config
18
-
19
- class << self
20
- def call(config, dependencies) = new(config, dependencies).call
21
- end
22
-
23
- def initialize(config, dependencies)
24
- @config = config
25
- @dependencies = dependencies
26
- end
27
-
28
- def call
29
- validate_traffic_control!
30
- validate_traffic_recovery!
31
- end
32
-
33
- private def validate_traffic_control!
34
- traffic_control = dependencies.fetch(:traffic_control)
35
- traffic_control.check_compatibility(config).then do |compatibility_result|
36
- if compatibility_result.incompatible?
37
- raise Domain::Error::ConfigurationError,
38
- "#{traffic_control.class.name} incompatible with config: #{compatibility_result.error_messages}"
39
- end
40
- end
41
- end
42
-
43
- def validate_traffic_recovery!
44
- traffic_recovery = dependencies.fetch(:traffic_recovery)
45
- traffic_recovery.check_compatibility(config).then do |compatibility_result|
46
- if compatibility_result.incompatible?
47
- raise Domain::Error::ConfigurationError,
48
- "#{traffic_recovery.class.name} incompatible with config: #{compatibility_result.error_messages}"
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end