stoplight 5.6.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 (238) 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 +26 -5
  7. data/lib/stoplight/admin/lights_repository/light.rb +22 -6
  8. data/lib/stoplight/admin/lights_repository.rb +20 -16
  9. data/lib/stoplight/admin/views/_card.erb +8 -5
  10. data/lib/stoplight/admin.rb +2 -1
  11. data/lib/stoplight/color.rb +9 -0
  12. data/lib/stoplight/common/deprecations.rb +11 -0
  13. data/lib/stoplight/data_store.rb +28 -0
  14. data/lib/stoplight/domain/compatibility_result.rb +7 -7
  15. data/lib/stoplight/domain/config.rb +38 -35
  16. data/lib/stoplight/domain/error_tracking_policy.rb +27 -0
  17. data/lib/stoplight/domain/failure.rb +1 -1
  18. data/lib/stoplight/domain/light/configuration_builder_interface.rb +122 -16
  19. data/lib/stoplight/domain/light.rb +44 -64
  20. data/lib/stoplight/domain/light_info.rb +7 -0
  21. data/lib/stoplight/domain/metrics_snapshot.rb +58 -0
  22. data/lib/stoplight/domain/state_snapshot.rb +29 -23
  23. data/lib/stoplight/domain/storage/recovery_lock_token.rb +15 -0
  24. data/lib/stoplight/domain/strategies/green_run_strategy.rb +18 -26
  25. data/lib/stoplight/domain/strategies/red_run_strategy.rb +9 -12
  26. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +74 -58
  27. data/lib/stoplight/domain/tracker/recovery_probe.rb +27 -43
  28. data/lib/stoplight/domain/tracker/request.rb +24 -39
  29. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +8 -11
  30. data/lib/stoplight/domain/traffic_control/error_rate.rb +19 -15
  31. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +8 -18
  32. data/lib/stoplight/domain/traffic_recovery.rb +3 -5
  33. data/lib/stoplight/error.rb +46 -0
  34. data/lib/stoplight/infrastructure/fail_safe/data_store.rb +152 -0
  35. data/lib/stoplight/infrastructure/fail_safe/storage/metrics.rb +65 -0
  36. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock.rb +69 -0
  37. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rb +19 -0
  38. data/lib/stoplight/infrastructure/fail_safe/storage/state.rb +62 -0
  39. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/metrics.rb +2 -2
  40. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +52 -0
  41. data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +17 -0
  42. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/sliding_window.rb +21 -26
  43. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/state.rb +3 -3
  44. data/lib/stoplight/infrastructure/{data_store/memory.rb → memory/data_store.rb} +90 -57
  45. data/lib/stoplight/infrastructure/memory/storage/recovery_lock.rb +35 -0
  46. data/lib/stoplight/infrastructure/memory/storage/recovery_metrics.rb +16 -0
  47. data/lib/stoplight/infrastructure/memory/storage/state.rb +155 -0
  48. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +103 -0
  49. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +101 -0
  50. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +50 -0
  51. data/lib/stoplight/infrastructure/notifier/generic.rb +4 -14
  52. data/lib/stoplight/infrastructure/notifier/io.rb +1 -2
  53. data/lib/stoplight/infrastructure/notifier/logger.rb +1 -2
  54. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +27 -0
  55. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +23 -0
  56. data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +6 -0
  57. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +60 -0
  58. data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +28 -0
  59. data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +73 -0
  60. data/lib/stoplight/infrastructure/{data_store/redis.rb → redis/data_store.rb} +173 -210
  61. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +51 -0
  62. data/lib/stoplight/infrastructure/redis/storage/metrics.rb +40 -0
  63. data/lib/stoplight/infrastructure/redis/storage/recovery_lock/release_lock.lua +6 -0
  64. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +64 -0
  65. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +20 -0
  66. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +18 -0
  67. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +10 -0
  68. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +10 -0
  69. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +9 -0
  70. data/lib/stoplight/infrastructure/redis/storage/state.rb +141 -0
  71. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +28 -0
  72. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +26 -0
  73. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +123 -0
  74. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +26 -0
  75. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +36 -0
  76. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +35 -0
  77. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +174 -0
  78. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +41 -0
  79. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +33 -0
  80. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +47 -0
  81. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +44 -0
  82. data/lib/stoplight/infrastructure/system_clock.rb +16 -0
  83. data/lib/stoplight/notifier.rb +11 -0
  84. data/lib/stoplight/state.rb +9 -0
  85. data/lib/stoplight/types.rb +29 -0
  86. data/lib/stoplight/undefined.rb +16 -0
  87. data/lib/stoplight/version.rb +1 -1
  88. data/lib/stoplight/wiring/config_compatibility_validator.rb +54 -0
  89. data/lib/stoplight/wiring/configuration_dsl.rb +101 -0
  90. data/lib/stoplight/wiring/data_store_backend.rb +26 -0
  91. data/lib/stoplight/wiring/default.rb +2 -2
  92. data/lib/stoplight/wiring/default_config.rb +21 -0
  93. data/lib/stoplight/wiring/default_configuration.rb +70 -53
  94. data/lib/stoplight/wiring/light_builder.rb +198 -0
  95. data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +26 -0
  96. data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +21 -0
  97. data/lib/stoplight/wiring/light_factory.rb +74 -135
  98. data/lib/stoplight/wiring/memory/backend.rb +57 -0
  99. data/lib/stoplight/wiring/notifier_factory.rb +26 -0
  100. data/lib/stoplight/wiring/redis/backend.rb +116 -0
  101. data/lib/stoplight/wiring/storage_set.rb +12 -0
  102. data/lib/stoplight/wiring/storage_set_builder.rb +51 -0
  103. data/lib/stoplight/wiring/system/light_builder.rb +47 -0
  104. data/lib/stoplight/wiring/system/light_factory.rb +64 -0
  105. data/lib/stoplight/wiring/system.rb +129 -0
  106. data/lib/stoplight.rb +209 -23
  107. data/sig/_private/generators/stoplight/install/install_generator.rbs +22 -0
  108. data/sig/_private/stoplight/common/deprecations.rbs +9 -0
  109. data/sig/_private/stoplight/data_store.rbs +6 -0
  110. data/sig/_private/stoplight/domain/compatibility_result.rbs +18 -0
  111. data/sig/_private/stoplight/domain/config.rbs +65 -0
  112. data/sig/_private/stoplight/domain/error_tracking_policy.rbs +14 -0
  113. data/sig/_private/stoplight/domain/failure.rbs +16 -0
  114. data/sig/_private/stoplight/domain/light.rbs +25 -0
  115. data/sig/_private/stoplight/domain/light_info.rbs +19 -0
  116. data/sig/_private/stoplight/domain/metrics_snapshot.rbs +38 -0
  117. data/sig/_private/stoplight/domain/ports/clock.rbs +18 -0
  118. data/sig/_private/stoplight/domain/ports/data_store.rbs +76 -0
  119. data/{lib/stoplight/domain/light_factory.rb → sig/_private/stoplight/domain/ports/light_factory.rbs} +33 -28
  120. data/sig/_private/stoplight/domain/ports/metrics_store.rbs +29 -0
  121. data/sig/_private/stoplight/domain/ports/recovery_lock_store.rbs +52 -0
  122. data/sig/_private/stoplight/domain/ports/recovery_lock_token.rbs +6 -0
  123. data/sig/_private/stoplight/domain/ports/run_strategy.rbs +14 -0
  124. data/sig/_private/stoplight/domain/ports/state_store.rbs +79 -0
  125. data/sig/_private/stoplight/domain/ports/traffic_control.rbs +41 -0
  126. data/sig/_private/stoplight/domain/ports/traffic_recovery.rbs +47 -0
  127. data/sig/_private/stoplight/domain/state_snapshot.rbs +32 -0
  128. data/sig/_private/stoplight/domain/storage/recovery_lock_token.rbs +11 -0
  129. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +17 -0
  130. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +17 -0
  131. data/sig/_private/stoplight/domain/strategies/yellow_run_strategy.rbs +42 -0
  132. data/sig/_private/stoplight/domain/tracker/base.rbs +8 -0
  133. data/sig/_private/stoplight/domain/tracker/recovery_probe.rbs +25 -0
  134. data/sig/_private/stoplight/domain/tracker/request.rbs +26 -0
  135. data/sig/_private/stoplight/domain/traffic_control/consecutive_errors.rbs +9 -0
  136. data/sig/_private/stoplight/domain/traffic_control/error_rate.rbs +13 -0
  137. data/sig/_private/stoplight/domain/traffic_recovery/consecutive_successes.rbs +9 -0
  138. data/sig/_private/stoplight/domain/traffic_recovery.rbs +9 -0
  139. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +26 -0
  140. data/sig/_private/stoplight/infrastructure/fail_safe/storage/metrics.rbs +25 -0
  141. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock.rbs +29 -0
  142. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rbs +19 -0
  143. data/sig/_private/stoplight/infrastructure/fail_safe/storage/state.rbs +25 -0
  144. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +25 -0
  145. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +19 -0
  146. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +17 -0
  147. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +27 -0
  148. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +17 -0
  149. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +30 -0
  150. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_lock.rbs +15 -0
  151. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_metrics.rbs +10 -0
  152. data/sig/_private/stoplight/infrastructure/memory/storage/state.rbs +28 -0
  153. data/sig/_private/stoplight/infrastructure/memory/storage/unbounded_metrics.rbs +25 -0
  154. data/sig/_private/stoplight/infrastructure/memory/storage/window_metrics.rbs +26 -0
  155. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +17 -0
  156. data/sig/_private/stoplight/infrastructure/notifier/generic.rbs +18 -0
  157. data/sig/_private/stoplight/infrastructure/notifier/io.rbs +14 -0
  158. data/sig/_private/stoplight/infrastructure/notifier/logger.rbs +14 -0
  159. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +24 -0
  160. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +21 -0
  161. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +34 -0
  162. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +67 -0
  163. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +19 -0
  164. data/sig/_private/stoplight/infrastructure/redis/storage/metrics.rbs +17 -0
  165. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_lock.rbs +26 -0
  166. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_metrics.rbs +10 -0
  167. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +13 -0
  168. data/sig/_private/stoplight/infrastructure/redis/storage/state.rbs +32 -0
  169. data/sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs +21 -0
  170. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +34 -0
  171. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +17 -0
  172. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +13 -0
  173. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +14 -0
  174. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +14 -0
  175. data/sig/_private/stoplight/infrastructure/system_clock.rbs +7 -0
  176. data/sig/_private/stoplight/system/light_builder.rbs +23 -0
  177. data/sig/_private/stoplight/system/light_factory.rbs +17 -0
  178. data/sig/_private/stoplight/types.rbs +6 -0
  179. data/sig/_private/stoplight/wiring/config_compatibility_validator.rbs +19 -0
  180. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +43 -0
  181. data/sig/_private/stoplight/wiring/data_store_backend.rbs +11 -0
  182. data/sig/_private/stoplight/wiring/default.rbs +26 -0
  183. data/sig/_private/stoplight/wiring/default_config.rbs +7 -0
  184. data/sig/_private/stoplight/wiring/default_configuration.rbs +29 -0
  185. data/sig/_private/stoplight/wiring/light_builder.rbs +48 -0
  186. data/sig/_private/stoplight/wiring/light_factory/traffic_control_dsl.rbs +7 -0
  187. data/sig/_private/stoplight/wiring/light_factory/traffic_recovery_dsl.rbs +7 -0
  188. data/sig/_private/stoplight/wiring/light_factory.rbs +16 -0
  189. data/sig/_private/stoplight/wiring/memory/backend.rbs +26 -0
  190. data/sig/_private/stoplight/wiring/notifier_factory.rbs +10 -0
  191. data/sig/_private/stoplight/wiring/redis/backend.rbs +38 -0
  192. data/sig/_private/stoplight/wiring/storage_set.rbs +38 -0
  193. data/sig/_private/stoplight/wiring/storage_set_builder.rbs +15 -0
  194. data/sig/_private/stoplight/wiring/system.rbs +15 -0
  195. data/sig/_private/stoplight.rbs +48 -0
  196. data/sig/stoplight/color.rbs +7 -0
  197. data/sig/stoplight/data_store.rbs +19 -0
  198. data/sig/stoplight/error.rbs +20 -0
  199. data/sig/stoplight/notifier.rbs +11 -0
  200. data/sig/stoplight/ports/configuration.rbs +19 -0
  201. data/sig/stoplight/ports/exception_matcher.rbs +8 -0
  202. data/sig/stoplight/ports/light.rbs +12 -0
  203. data/sig/stoplight/ports/light_info.rbs +5 -0
  204. data/sig/stoplight/ports/state_transition_notifier.rbs +15 -0
  205. data/sig/stoplight/ports/system.rbs +21 -0
  206. data/sig/stoplight/state.rbs +7 -0
  207. data/sig/stoplight/undefined.rbs +9 -0
  208. data/sig/stoplight/version.rbs +3 -0
  209. data/sig/stoplight.rbs +66 -0
  210. metadata +199 -36
  211. data/lib/stoplight/domain/color.rb +0 -11
  212. data/lib/stoplight/domain/data_store.rb +0 -130
  213. data/lib/stoplight/domain/error.rb +0 -42
  214. data/lib/stoplight/domain/metrics.rb +0 -85
  215. data/lib/stoplight/domain/state.rb +0 -11
  216. data/lib/stoplight/domain/state_transition_notifier.rb +0 -25
  217. data/lib/stoplight/domain/strategies/run_strategy.rb +0 -27
  218. data/lib/stoplight/domain/tracker/base.rb +0 -41
  219. data/lib/stoplight/domain/traffic_control/base.rb +0 -74
  220. data/lib/stoplight/domain/traffic_recovery/base.rb +0 -80
  221. data/lib/stoplight/infrastructure/data_store/redis/lua.rb +0 -25
  222. data/lib/stoplight/infrastructure/dependency_injection/container.rb +0 -249
  223. data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +0 -13
  224. data/lib/stoplight/wiring/container.rb +0 -80
  225. data/lib/stoplight/wiring/default_factory_builder.rb +0 -25
  226. data/lib/stoplight/wiring/fail_safe_data_store.rb +0 -147
  227. data/lib/stoplight/wiring/fail_safe_notifier.rb +0 -79
  228. data/lib/stoplight/wiring/light/default_config.rb +0 -18
  229. data/lib/stoplight/wiring/light/system_config.rb +0 -11
  230. data/lib/stoplight/wiring/public_api.rb +0 -28
  231. data/lib/stoplight/wiring/system_container.rb +0 -9
  232. data/lib/stoplight/wiring/system_light_factory.rb +0 -17
  233. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/get_metrics.lua +0 -0
  234. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/record_failure.lua +0 -0
  235. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/record_success.lua +0 -0
  236. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/transition_to_green.lua +0 -0
  237. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/transition_to_red.lua +0 -0
  238. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store/lua_scripts}/transition_to_yellow.lua +0 -0
data/lib/stoplight.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/map"
3
4
  require "zeitwerk"
4
5
 
6
+ # steep:ignore:start
5
7
  loader = Zeitwerk::Loader.for_gem
6
8
  loader.inflector.inflect("io" => "IO")
7
9
  loader.do_not_eager_load(
@@ -12,19 +14,21 @@ loader.do_not_eager_load(
12
14
  loader.ignore("#{__dir__}/generators")
13
15
  loader.ignore("#{__dir__}/stoplight/rspec.rb", "#{__dir__}/stoplight/rspec")
14
16
  loader.setup
17
+ # steep:ignore:end
15
18
 
16
19
  module Stoplight # rubocop:disable Style/Documentation
17
- include Wiring::PublicApi
20
+ T = Types
18
21
 
19
22
  CONFIG_MUTEX = Mutex.new
20
23
  private_constant :CONFIG_MUTEX
24
+ @systems = Concurrent::Map.new
21
25
 
22
26
  class << self
23
27
  # Configures the Stoplight library.
24
28
  #
25
29
  # This method allows you to set up the library's configuration using a block.
26
30
  # It raises an error if called more than once.
27
- #
31
+ # @param trust_me_im_an_engineer [Boolean]
28
32
  # @yield [config] Provides a configuration object to the block.
29
33
  # @yieldparam config [Stoplight::Wiring::DefaultConfiguration] The configuration object.
30
34
  # @return [void]
@@ -52,32 +56,175 @@ module Stoplight # rubocop:disable Style/Documentation
52
56
  #
53
57
  def configure(trust_me_im_an_engineer: false)
54
58
  warn_if_reconfiguring(trust_me_im_an_engineer) do
55
- factory_builder = Wiring::DefaultFactoryBuilder.new
56
- yield factory_builder.configuration if block_given?
59
+ configuration = Wiring::DefaultConfiguration.new
60
+ yield configuration if block_given?
57
61
 
58
- @default_configuration = factory_builder.configuration
59
- @default_light_factory = factory_builder.build
62
+ default_config = configuration.to_config!
63
+ @default_config = default_config
64
+ @default_light_factory = Wiring::LightFactory.new(config: default_config)
60
65
  end
61
66
  end
62
67
 
63
- # Creates a Light for internal use.
64
- #
65
- # @param name [String]
66
- # @param settings [Hash]
67
- # @return [Stoplight::Light]
68
68
  # @api private
69
- def system_light(name, **settings)
70
- Wiring::SystemLightFactory.build_with(name: "__stoplight__#{name}", **settings)
69
+ def system_light(
70
+ name,
71
+ cool_off_time: T.undefined,
72
+ threshold: T.undefined,
73
+ recovery_threshold: T.undefined,
74
+ window_size: T.undefined,
75
+ tracked_errors: T.undefined,
76
+ skipped_errors: T.undefined,
77
+ data_store: T.undefined,
78
+ error_notifier: T.undefined,
79
+ notifiers: T.undefined,
80
+ traffic_control: T.undefined,
81
+ traffic_recovery: T.undefined
82
+ )
83
+ Wiring::LightFactory.new(config: Wiring::DefaultConfig).build_with(
84
+ name: "__stoplight__#{name}",
85
+ cool_off_time:,
86
+ threshold:,
87
+ recovery_threshold:,
88
+ window_size:,
89
+ tracked_errors:,
90
+ skipped_errors:,
91
+ data_store:,
92
+ error_notifier:,
93
+ notifiers:,
94
+ traffic_control:,
95
+ traffic_recovery:
96
+ )
71
97
  end
72
98
 
73
99
  # Create a Light with the user default configuration.
74
100
  #
75
- # @param name [String]
76
- # @param settings [Hash]
77
101
  # @return [Stoplight::Light]
78
102
  # @api private
79
- def light(name, **settings)
80
- __stoplight__default_light_factory.build_with(name:, **settings)
103
+ def light(
104
+ name,
105
+ cool_off_time: T.undefined,
106
+ threshold: T.undefined,
107
+ recovery_threshold: T.undefined,
108
+ window_size: T.undefined,
109
+ tracked_errors: T.undefined,
110
+ skipped_errors: T.undefined,
111
+ data_store: T.undefined,
112
+ error_notifier: T.undefined,
113
+ notifiers: T.undefined,
114
+ traffic_control: T.undefined,
115
+ traffic_recovery: T.undefined
116
+ )
117
+ __stoplight__default_light_factory.build_with(
118
+ name:,
119
+ cool_off_time:,
120
+ threshold:,
121
+ recovery_threshold:,
122
+ window_size:,
123
+ tracked_errors:,
124
+ skipped_errors:,
125
+ data_store:,
126
+ error_notifier:,
127
+ notifiers:,
128
+ traffic_control:,
129
+ traffic_recovery:
130
+ )
131
+ end
132
+
133
+ # Creates a new named system with the given configuration.
134
+ #
135
+ # Systems are composition roots that own infrastructure (data store, notifiers)
136
+ # and enforce configuration consistency for all lights created within them.
137
+ #
138
+ # @param name [String] Unique identifier for the system
139
+ # @param settings [Hash] Configuration options that override global defaults.
140
+ # @see Stoplight() documentation
141
+ #
142
+ # @return [Stoplight::Wiring::System] A new system instance.
143
+ #
144
+ # @raise [ArgumentError] If a system with the given name already exists.
145
+ #
146
+ # @note Systems are not cached for reuse. Assign the returned system to a constant
147
+ # for repeated access. Calling this method twice with the same name raises an error.
148
+ #
149
+ # @example Creating a system for payment services
150
+ # Payments = Stoplight.__stoplight__system(:payments, threshold: 3, cool_off_time: 30)
151
+ # Payments.light("stripe").run { process_payment }
152
+ #
153
+ # @example Isolated system with dedicated data store
154
+ # Analytics = Stoplight.__stoplight__system(:analytics, data_store: analytics_redis)
155
+ #
156
+ # @api private
157
+ def __stoplight__system(
158
+ name,
159
+ cool_off_time: T.undefined,
160
+ threshold: T.undefined,
161
+ recovery_threshold: T.undefined,
162
+ window_size: T.undefined,
163
+ tracked_errors: T.undefined,
164
+ skipped_errors: T.undefined,
165
+ data_store: T.undefined,
166
+ error_notifier: T.undefined,
167
+ notifiers: T.undefined,
168
+ traffic_control: T.undefined,
169
+ traffic_recovery: T.undefined
170
+ )
171
+ ensure_configured
172
+
173
+ systems.compute(name.to_s) do |existing_system|
174
+ if existing_system
175
+ raise ArgumentError, "system `#{name}` is already in use"
176
+ else
177
+ Wiring::System.new(
178
+ config: Wiring::ConfigurationDsl.new(
179
+ name: name.to_s,
180
+ cool_off_time:,
181
+ threshold:,
182
+ recovery_threshold:,
183
+ window_size:,
184
+ tracked_errors:,
185
+ skipped_errors:,
186
+ traffic_control:,
187
+ traffic_recovery:,
188
+ data_store:,
189
+ error_notifier:,
190
+ notifiers:
191
+ ).configure!(default_config)
192
+ )
193
+ end
194
+ end
195
+ end
196
+
197
+ private attr_reader :systems
198
+ private def default_config = T.must(@default_config)
199
+
200
+ # Resets Stoplight to an unconfigured state.
201
+ #
202
+ # Clears all registered systems, default configuration, and the default light factory.
203
+ # After calling this method, the next call to +Stoplight()+ or +configure+ will
204
+ # initialize fresh state.
205
+ #
206
+ # @return [void]
207
+ #
208
+ # @note This method is intended for test suite setup/teardown. Do not use in production
209
+ # code, as it orphans any existing light or system references, leading to split-brain
210
+ # scenarios where different lights with the same name use different data stores.
211
+ #
212
+ # @note This method does not clean up state in external data stores (e.g., Redis keys).
213
+ # It only resets in-process configuration.
214
+ #
215
+ # @example RSpec test setup
216
+ # RSpec.configure do |config|
217
+ # config.before do
218
+ # Stoplight.__stoplight__reset!
219
+ # end
220
+ # end
221
+ #
222
+ # @api private
223
+ def __stoplight__reset!
224
+ @systems = Concurrent::Map.new
225
+ @default_config = nil
226
+ @default_light_factory = nil
227
+ @configured = nil
81
228
  end
82
229
 
83
230
  # Retrieves the current default dependencies.
@@ -86,12 +233,12 @@ module Stoplight # rubocop:disable Style/Documentation
86
233
  # @api private
87
234
  def __stoplight__default_light_factory
88
235
  ensure_configured
89
- @default_light_factory
236
+ T.must(@default_light_factory)
90
237
  end
91
238
 
92
239
  def __stoplight__default_configuration
93
240
  ensure_configured
94
- @default_configuration
241
+ T.must(@default_config)
95
242
  end
96
243
 
97
244
  # @api private
@@ -115,13 +262,12 @@ module Stoplight # rubocop:disable Style/Documentation
115
262
  end
116
263
  end
117
264
 
118
- # Creates a new Stoplight circuit breaker with the given name and settings.
265
+ # Creates a new Stoplight circuit brNeaker with the given name and settings.
119
266
  #
120
267
  # @param name [String] The name of the circuit breaker.
121
268
  # @param settings [Hash] Optional settings to configure the circuit breaker.
122
269
  # @option settings [Numeric] :cool_off_time The time to wait before resetting the circuit breaker.
123
270
  # @option settings [Stoplight::DataStore::Base] :data_store The data store to use for storing state.
124
- # @option settings [Proc] :error_notifier A proc to handle error notifications.
125
271
  # @option settings [Array<Stoplight::Notifier::Base>] :notifiers A list of notifiers to use.
126
272
  # @option settings [Numeric] :threshold The failure threshold to trip the circuit breaker.
127
273
  # @option settings [Numeric] :window_size The size of the rolling window for failure tracking.
@@ -160,6 +306,46 @@ end
160
306
  # # When 66.6% error rate reached withing a sliding 5 minute window, the circuit breaker will trip.
161
307
  # light = Stoplight("Payment API", traffic_control: :error_rate, threshold: 0.666, window_size: 300)
162
308
  #
163
- def Stoplight(name, **settings) # rubocop:disable Naming/MethodName
164
- Stoplight.light(name, **settings)
309
+ def Stoplight(
310
+ name,
311
+ cool_off_time: Stoplight::T.undefined,
312
+ threshold: Stoplight::T.undefined,
313
+ recovery_threshold: Stoplight::T.undefined,
314
+ window_size: Stoplight::T.undefined,
315
+ tracked_errors: Stoplight::T.undefined,
316
+ skipped_errors: Stoplight::T.undefined,
317
+ data_store: Stoplight::T.undefined,
318
+ error_notifier: Stoplight::T.undefined,
319
+ notifiers: Stoplight::T.undefined,
320
+ traffic_control: Stoplight::T.undefined,
321
+ traffic_recovery: Stoplight::T.undefined
322
+ ) # rubocop:disable Naming/MethodName
323
+ Stoplight::Common::Deprecations.deprecate(<<~MSG) if error_notifier != Stoplight::T.undefined
324
+ Passing "error_notifier" to Stoplight('#{name}') is deprecated and will be removed in v6.0.0.
325
+
326
+ IMPORTANT: The `error_notifier` is NOT called for exceptions in your protected code.
327
+ It only reports internal Stoplight failures (e.g., Redis connection errors).
328
+
329
+ To fix: Move `error_notifier` to global configuration:
330
+
331
+ Stoplight.configure do |config|
332
+ config.error_notifier = ->(error) { Logger.warn(error) }
333
+ end
334
+
335
+ See: https://github.com/bolshakov/stoplight#error-notifiers
336
+ MSG
337
+ Stoplight.light(
338
+ name,
339
+ cool_off_time:,
340
+ threshold:,
341
+ recovery_threshold:,
342
+ window_size:,
343
+ tracked_errors:,
344
+ skipped_errors:,
345
+ data_store:,
346
+ error_notifier:,
347
+ notifiers:,
348
+ traffic_control:,
349
+ traffic_recovery:
350
+ )
165
351
  end
@@ -0,0 +1,22 @@
1
+ module Stoplight
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ ROUTES_PATH: String
5
+ STOPLIGHT_CONFIG_TEMPLATE: String
6
+ INITIALIZERS_PATH: String
7
+ AFTER_INSTALL_NOTIFICATION: String
8
+ STOPLIGHT_ADMIN_ROUTE: String
9
+ STOPLIGHT_AUTH: String
10
+
11
+ def options: -> Hash[Symbol, untyped]
12
+ def generate_redis_gem: -> void
13
+ def generate_sinatra_deps: -> void
14
+ def generate_initializer: -> void
15
+ def generate_admin_panel: -> void
16
+ def redis_configuration_notification: -> void
17
+
18
+ def copy_file: (String, String) -> void
19
+ def inject_into_file: (String path, String what, ?after: String) -> void
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Common
5
+ module Deprecations
6
+ def deprecate: (String) -> void
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Stoplight
2
+ module DataStore
3
+ class Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Domain
5
+ class CompatibilityResult
6
+ def self.compatible: -> CompatibilityResult
7
+ def self.incompatible: (*String) -> CompatibilityResult
8
+
9
+ attr_reader errors: Array[String]
10
+
11
+ def initialize: (?errors: Array[String]) -> void
12
+
13
+ def compatible?: -> bool
14
+ def incompatible?: -> bool
15
+ def error_messages: -> String?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,65 @@
1
+ module Stoplight
2
+ module Domain
3
+ class Config < Data
4
+ attr_reader threshold: percentage | Integer
5
+ attr_reader window_size: duration?
6
+ attr_reader tracked_errors: Array[_ExceptionMatcher]
7
+ attr_reader skipped_errors: Array[_ExceptionMatcher]
8
+ attr_reader name: String
9
+ attr_reader cool_off_time: duration
10
+ attr_reader recovery_threshold: Integer
11
+ attr_reader traffic_control: _TrafficControl
12
+ attr_reader traffic_recovery: _TrafficRecovery
13
+ attr_reader error_notifier: error_notifier
14
+ attr_reader notifiers: Array[state_transition_notifier]
15
+ attr_reader data_store: data_store
16
+
17
+ def self.new: (
18
+ name: String,
19
+ cool_off_time: duration,
20
+ threshold: percentage | Integer,
21
+ recovery_threshold: Integer,
22
+ window_size: duration?,
23
+ tracked_errors: Array[_ExceptionMatcher],
24
+ skipped_errors: Array[_ExceptionMatcher],
25
+ traffic_control: _TrafficControl,
26
+ traffic_recovery: _TrafficRecovery,
27
+ error_notifier: error_notifier,
28
+ notifiers: Array[state_transition_notifier],
29
+ data_store: data_store
30
+ ) -> instance
31
+
32
+ def initialize: (
33
+ name: String,
34
+ cool_off_time: duration,
35
+ threshold: percentage | Integer,
36
+ recovery_threshold: Integer,
37
+ window_size: duration?,
38
+ tracked_errors: Array[_ExceptionMatcher],
39
+ skipped_errors: Array[_ExceptionMatcher],
40
+ traffic_control: _TrafficControl,
41
+ traffic_recovery: _TrafficRecovery,
42
+ error_notifier: error_notifier,
43
+ notifiers: Array[state_transition_notifier],
44
+ data_store: data_store
45
+ ) -> void
46
+
47
+ def cool_off_time_in_milliseconds: () -> Integer
48
+
49
+ def with: (
50
+ ?name: optional[String],
51
+ ?cool_off_time: optional[duration],
52
+ ?threshold: optional[percentage | Integer],
53
+ ?recovery_threshold: optional[Integer],
54
+ ?window_size: optional[duration?],
55
+ ?tracked_errors: optional[Array[_ExceptionMatcher]],
56
+ ?skipped_errors: optional[Array[_ExceptionMatcher]],
57
+ ?traffic_control: optional[_TrafficControl],
58
+ ?traffic_recovery: optional[_TrafficRecovery],
59
+ ?error_notifier: optional[error_notifier],
60
+ ?notifiers: optional[Array[state_transition_notifier]],
61
+ ?data_store: optional[data_store],
62
+ ) -> Config | () -> Config
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,14 @@
1
+ module Stoplight
2
+ module Domain
3
+ class ErrorTrackingPolicy
4
+ @tracked: Array[_ExceptionMatcher]
5
+ @skipped: Array[_ExceptionMatcher]
6
+
7
+ def initialize: (tracked: Array[_ExceptionMatcher], skipped: Array[_ExceptionMatcher]) -> void
8
+ def track?: (StandardError) -> bool
9
+
10
+ def skipped?: (StandardError) -> bool
11
+ def tracked?: (StandardError) -> bool
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Stoplight
2
+ module Domain
3
+ class Failure
4
+ attr_reader error_class: String
5
+ attr_reader error_message: String
6
+ attr_reader time: Time
7
+ alias occurred_at time
8
+
9
+ def self.from_error: (StandardError error, time: Time) -> Failure
10
+
11
+ def initialize: (String error_class, String error_message, Time time) -> void
12
+
13
+ def ==: (untyped other) -> bool
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Stoplight
2
+ module Domain
3
+ class Light
4
+ include _Light
5
+ attr_reader green_run_strategy: Strategies::GreenRunStrategy
6
+ attr_reader yellow_run_strategy: Strategies::YellowRunStrategy
7
+ attr_reader red_run_strategy: Strategies::RedRunStrategy
8
+ attr_reader state_store: Domain::_StateStore
9
+ attr_reader factory: _LightFactory
10
+ @name: String
11
+
12
+ def initialize: (
13
+ String name,
14
+ green_run_strategy: Strategies::GreenRunStrategy,
15
+ yellow_run_strategy: Strategies::YellowRunStrategy,
16
+ red_run_strategy: Strategies::RedRunStrategy,
17
+ factory: _LightFactory,
18
+ state_store: Domain::_StateStore
19
+ ) -> void
20
+
21
+ private def state_strategy_factory: (color) -> _RunStrategy
22
+ private def state_snapshot: -> StateSnapshot
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Stoplight
2
+ module Domain
3
+ class LightInfo < Data
4
+ include _LightInfo
5
+
6
+ def self.new: (
7
+ name: String,
8
+ ) -> instance
9
+
10
+ def initialize: (
11
+ name: String,
12
+ ) -> void
13
+
14
+ def with: (
15
+ ?name: String,
16
+ ) -> LightInfo
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ module Stoplight
2
+ module Domain
3
+ class MetricsSnapshot < Data
4
+ attr_reader successes: Integer?
5
+ attr_reader errors: Integer?
6
+ attr_reader consecutive_errors: Integer
7
+ attr_reader consecutive_successes: Integer
8
+ attr_reader last_error: Failure?
9
+ attr_reader last_success_at: Time?
10
+
11
+ def self.new: (
12
+ successes: Integer?,
13
+ errors: Integer?,
14
+ consecutive_errors: Integer,
15
+ consecutive_successes: Integer,
16
+ last_error: Failure?,
17
+ last_success_at: Time?
18
+ ) -> instance
19
+
20
+ def initialize: (
21
+ successes: Integer?,
22
+ errors: Integer?,
23
+ consecutive_errors: Integer,
24
+ consecutive_successes: Integer,
25
+ last_error: Failure?,
26
+ last_success_at: Time?
27
+ ) -> void
28
+
29
+ def errors!: -> Integer
30
+ def successes!: -> Integer
31
+ def error_rate: () -> Float?
32
+
33
+ def requests: () -> Integer?
34
+
35
+ def last_error_at: () -> Time?
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ module Stoplight
2
+ module Domain
3
+ # Wall-clock interface for time-dependent operations.
4
+ #
5
+ # Provides a centralized definition of how Stoplight obtains and
6
+ # interprets time. All time-dependent components should use this
7
+ # interface rather than calling Time methods directly, ensuring
8
+ # consistent time handling across the library.
9
+ #
10
+ interface _Clock
11
+ # Returns the current time.
12
+ def current_time: () -> Time
13
+
14
+ # Converts a Unix timestamp to a Time object.
15
+ def at: (timestamp) -> Time
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Domain
5
+ interface _DataStore
6
+ # Retrieves the names of all lights stored in the data store.
7
+ #
8
+ # @return An array of light names.
9
+ def names: -> Array[String]
10
+
11
+ # Retrieves metrics for a specific light configuration.
12
+ def get_metrics: (Config) -> Domain::MetricsSnapshot
13
+
14
+ # Retrieves recovery metrics for a specific light configuration.
15
+ def get_recovery_metrics: (Config) -> Domain::MetricsSnapshot
16
+
17
+ # Retrieves State Snapshot for a specific light configuration.
18
+ def get_state_snapshot: (Config) -> StateSnapshot
19
+
20
+ # Clears windowed metrics (successes/errors) to prevent
21
+ # stale failures from before recovery from affecting post-recovery decisions.
22
+ # Consecutive counts are intentionally preserved as they track current streaks.
23
+ def clear_metrics: (Config) -> void
24
+
25
+ def clear_recovery_metrics: (Config) -> void
26
+
27
+ # Records a failure for a specific light configuration.
28
+ def record_failure: (Config, StandardError exception) -> void
29
+
30
+ # Records a success for a specific light configuration.
31
+ def record_success: (Config) -> void
32
+
33
+ # Records a failed recovery probe for a specific light configuration.
34
+ def record_recovery_probe_failure: (Config, StandardError exception) -> void
35
+
36
+ # Records a successful recovery probe for a specific light configuration.
37
+ def record_recovery_probe_success: (Config) -> void
38
+
39
+ # Sets the state of a specific light configuration.
40
+ def set_state: (Config, state state) -> state
41
+
42
+ # Acquires recovery lock for serializing probe execution.
43
+ def acquire_recovery_lock: (Config) -> _RecoveryLockToken?
44
+
45
+ # Releases previously acquired lock.
46
+ def release_recovery_lock: (_RecoveryLockToken lock) -> void
47
+
48
+ # Transitions the Stoplight to the specified color.
49
+ #
50
+ # This method performs a color transition operation that works across distributed instances
51
+ # of the light. It ensures that in a multi-instance environment, only one instance
52
+ # is considered the "first" to perform the transition (and therefore responsible for
53
+ # triggering notifications).
54
+ #
55
+ # @param config
56
+ # @param color The target color/state to transition to.
57
+ # Should be one of Stoplight::Color::GREEN, Stoplight::Color::YELLOW, or Stoplight::Color::RED.
58
+ #
59
+ # @return [Boolean] Returns +true+ if this instance was the first to perform this specific transition
60
+ # (and should therefore trigger notifications). Returns +false+ if another instance already
61
+ # initiated this transition.
62
+ #
63
+ # @note In distributed environments with multiple instances, race conditions can occur when instances
64
+ # attempt conflicting transitions simultaneously (e.g., one instance tries to transition from
65
+ # YELLOW to GREEN while another tries YELLOW to RED). The implementation handles this, but
66
+ # be aware that the last operation may determine the final color of the light.
67
+ #
68
+ def transition_to_color: (Config, color color) -> bool
69
+
70
+ # Deletes metadata (and related persistent state) for the given light.
71
+ #
72
+ # Implementations may choose to only remove metadata; metrics may expire via TTL.
73
+ def delete_light: (Config) -> void
74
+ end
75
+ end
76
+ end