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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ # Singleton representing an undefined/not-provided argument.
5
+ #
6
+ # Distinct from nil, which may be a valid configured value.
7
+ # Used with keyword arguments to detect when a parameter
8
+ # wasn't passed vs. explicitly set to nil.
9
+ # @api private
10
+ class Undefined
11
+ include Singleton
12
+
13
+ def inspect = "UNDEFINED"
14
+ alias_method :to_s, :inspect
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stoplight
4
- VERSION = Gem::Version.new("5.7.0")
4
+ VERSION = Gem::Version.new("5.8.2")
5
5
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ # Validates that traffic control and recovery strategies are
6
+ # compatible with the provided configuration.
7
+ #
8
+ # Different strategies have different configuration requirements:
9
+ # - ErrorRate requires window_size and threshold ∈ [0,1]
10
+ # - ConsecutiveErrors requires threshold > 0
11
+ # - ConsecutiveSuccesses requires recovery_threshold > 0
12
+ #
13
+ # @raise [Stoplight::Error::ConfigurationError] if incompatible
14
+ class ConfigCompatibilityValidator
15
+ private attr_reader :config
16
+
17
+ class << self
18
+ def call(config:) = new(config:).call
19
+ end
20
+
21
+ def initialize(config:)
22
+ @config = config
23
+ end
24
+
25
+ def call
26
+ validate_traffic_control!
27
+ validate_traffic_recovery!
28
+ config
29
+ end
30
+
31
+ private def validate_traffic_control!
32
+ traffic_control = config.traffic_control
33
+ traffic_control.check_compatibility(config).then do |compatibility_result|
34
+ if compatibility_result.incompatible?
35
+ raise Error::ConfigurationError,
36
+ "#{traffic_control} incompatible with config: #{compatibility_result.error_messages}",
37
+ caller(8)
38
+ end
39
+ end
40
+ end
41
+
42
+ def validate_traffic_recovery!
43
+ traffic_recovery = config.traffic_recovery
44
+ traffic_recovery.check_compatibility(config).then do |compatibility_result|
45
+ if compatibility_result.incompatible?
46
+ raise Error::ConfigurationError,
47
+ "#{traffic_recovery} incompatible with config: #{compatibility_result.error_messages}",
48
+ caller(8)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ class ConfigurationDsl
6
+ def initialize(
7
+ name: T.undefined,
8
+ cool_off_time: T.undefined,
9
+ threshold: T.undefined,
10
+ recovery_threshold: T.undefined,
11
+ window_size: T.undefined,
12
+ tracked_errors: T.undefined,
13
+ skipped_errors: T.undefined,
14
+ data_store: T.undefined,
15
+ error_notifier: T.undefined,
16
+ notifiers: T.undefined,
17
+ traffic_control: T.undefined,
18
+ traffic_recovery: T.undefined
19
+ )
20
+ @name = name
21
+ @cool_off_time = cool_off_time
22
+ @threshold = threshold
23
+ @recovery_threshold = recovery_threshold
24
+ @window_size = window_size
25
+ @tracked_errors = tracked_errors
26
+ @skipped_errors = skipped_errors
27
+ @traffic_control = traffic_control
28
+ @traffic_recovery = traffic_recovery
29
+ @error_notifier = error_notifier
30
+ @data_store = data_store
31
+ @notifiers = notifiers
32
+ end
33
+
34
+ def configure!(default_config)
35
+ ConfigCompatibilityValidator.call(
36
+ config: default_config.with(
37
+ name:,
38
+ cool_off_time:,
39
+ threshold:,
40
+ recovery_threshold:,
41
+ window_size:,
42
+ tracked_errors:,
43
+ skipped_errors:,
44
+ traffic_control:,
45
+ traffic_recovery:,
46
+ error_notifier:,
47
+ data_store:,
48
+ notifiers:
49
+ )
50
+ )
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :name
56
+ attr_reader :cool_off_time
57
+ attr_reader :threshold
58
+ attr_reader :recovery_threshold
59
+ attr_reader :window_size
60
+ attr_reader :error_notifier
61
+ attr_reader :data_store
62
+ attr_reader :notifiers
63
+
64
+ def tracked_errors
65
+ value = @tracked_errors
66
+ if value.is_a?(Undefined)
67
+ value
68
+ else
69
+ Array(value)
70
+ end
71
+ end
72
+
73
+ def skipped_errors
74
+ value = @skipped_errors
75
+ if value.is_a?(Undefined)
76
+ value
77
+ else
78
+ Array(value)
79
+ end
80
+ end
81
+
82
+ def traffic_control
83
+ value = @traffic_control
84
+ if value.is_a?(Undefined)
85
+ value
86
+ else
87
+ LightFactory::TrafficControlDsl.call(value)
88
+ end
89
+ end
90
+
91
+ def traffic_recovery
92
+ value = @traffic_recovery
93
+ if value.is_a?(Undefined)
94
+ value
95
+ else
96
+ LightFactory::TrafficRecoveryDsl.call(value)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ # Abstract base class defining the storage backend interface.
6
+ #
7
+ # A backend encapsulates all storage construction for a specific data store type
8
+ # (Memory or Redis). Backends handle infrastructure concerns like connection
9
+ # management and failover wrapping, exposing a uniform interface to StorageSetBuilder.
10
+ #
11
+ # Each method returns a memoized storage instance. Backends are designed to be
12
+ # instantiated once per Light and reused.
13
+ #
14
+ # @abstract Subclass and implement all methods
15
+ # @see Memory::Backend
16
+ # @see Redis::Backend
17
+ # @api private
18
+ class DataStoreBackend
19
+ def state_store = raise ArgumentError
20
+ def recovery_lock_store = raise ArgumentError
21
+ def recovery_metrics_store = raise ArgumentError
22
+ def windowed_metrics_store = raise ArgumentError
23
+ def unbounded_metrics_store = raise ArgumentError
24
+ end
25
+ end
26
+ end
@@ -19,7 +19,7 @@ module Stoplight
19
19
  WINDOW_SIZE = nil
20
20
 
21
21
  TRACKED_ERRORS = [StandardError].freeze
22
- SKIPPED_ERRORS = [].freeze
22
+ SKIPPED_ERRORS = [].freeze # steep:ignore
23
23
 
24
24
  TRAFFIC_CONTROL = Domain::TrafficControl::ConsecutiveErrors.new
25
25
  TRAFFIC_RECOVERY = Domain::TrafficRecovery::ConsecutiveSuccesses.new
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ # Provides default settings for the Stoplight library.
6
+ DefaultConfig = Domain::Config.new(
7
+ name: "DEFAULT_CONFIG",
8
+ cool_off_time: Default::COOL_OFF_TIME,
9
+ threshold: Default::THRESHOLD,
10
+ recovery_threshold: Default::RECOVERY_THRESHOLD,
11
+ window_size: Default::WINDOW_SIZE,
12
+ tracked_errors: Default::TRACKED_ERRORS,
13
+ skipped_errors: Default::SKIPPED_ERRORS,
14
+ traffic_control: Default::TRAFFIC_CONTROL,
15
+ traffic_recovery: Default::TRAFFIC_RECOVERY,
16
+ error_notifier: Default::ERROR_NOTIFIER,
17
+ notifiers: Default::NOTIFIERS,
18
+ data_store: Default::DATA_STORE
19
+ )
20
+ end
21
+ end
@@ -2,76 +2,93 @@
2
2
 
3
3
  module Stoplight
4
4
  module Wiring
5
- # User-facing configuration interface
5
+ # User-facing configuration interface for setting global Stoplight defaults.
6
+ #
7
+ # This class serves as the configuration DSL yielded to users when calling
8
+ # +Stoplight.configure+. It provides a clean interface for setting default
9
+ # values while internally tracking whether each setting was explicitly
10
+ # configured or should fall back to library defaults.
11
+ #
12
+ # @example Configuring Stoplight defaults
13
+ # Stoplight.configure do |config|
14
+ # config.data_store = Redis.new
15
+ # config.threshold = 5
16
+ # # window_size not set - will use library default
17
+ # end
18
+ #
19
+ # == Option-Based Configuration Tracking
20
+ #
21
+ # Internally, each setting is wrapped in an Option type (+Some+ or +None+).
22
+ # This design allows the class to distinguish between three states:
23
+ #
24
+ # 1. Not configured: Stored as +None+, getter returns library default
25
+ # 2. Explicitly configured: Stored as +Some(value)+, getter returns that value
26
+ # 3. Explicitly set to nil: Stored as +Some(nil)+, getter returns +nil+
27
+ #
28
+ # This distinction is critical when building {Settings} and {Dependencies}
29
+ # objects, which need to know whether a value was user-specified (and should
30
+ # be enforced) or inherited (and can be overridden per-circuit).
31
+ #
32
+ # == Dual Interface
33
+ #
34
+ # The class exposes two interfaces for each setting:
35
+ #
36
+ # - *Setters* (+attr_writer+): Accept raw values, wrap them in +Some+
37
+ # - *Getters* (custom methods): Unwrap the Option, returning the value
38
+ # or falling back to library defaults from {Default}
39
+ #
40
+ # The {#settings} and {#dependencies} methods preserve the raw Option
41
+ # values, allowing downstream code to detect explicit configuration.
42
+ #
43
+ # @see Settings Value object for circuit behavior configuration
44
+ # @see Dependencies Value object for infrastructure dependencies
45
+ # @see Default Library default constants
46
+ #
6
47
  class DefaultConfiguration
7
- # @!attribute [w] cool_off_time
8
- # @return [Integer, nil] The default cool-off time in seconds.
9
- attr_writer :cool_off_time
48
+ def initialize
49
+ @config = DefaultConfig.with
50
+ @cool_off_time = T.undefined
51
+ @threshold = T.undefined
52
+ @recovery_threshold = T.undefined
53
+ @window_size = T.undefined
54
+ @tracked_errors = T.undefined
55
+ @skipped_errors = T.undefined
56
+ @traffic_control = T.undefined
57
+ @traffic_recovery = T.undefined
58
+ @error_notifier = T.undefined
59
+ @data_store = T.undefined
60
+ @notifiers = T.undefined
61
+ end
10
62
 
11
- # @!attribute [w] threshold
12
- # @return [Integer, Float, nil] The default failure threshold to trip the circuit breaker.
13
- attr_writer :threshold
63
+ def notifiers = @config.notifiers
14
64
 
15
- # @!attribute [w] recovery_threshold
16
- # @return [Integer, nil] The default recovery threshold for the circuit breaker.
65
+ attr_writer :cool_off_time
66
+ attr_writer :threshold
17
67
  attr_writer :recovery_threshold
18
-
19
- # @!attribute [w] window_size
20
- # @return [Integer, nil] The default size of the rolling window for failure tracking.
21
68
  attr_writer :window_size
22
-
23
- # @!attribute [w] tracked_errors
24
- # @return [Array<Class>, nil] The default list of errors to track.
25
69
  attr_writer :tracked_errors
26
-
27
- # @!attribute [w] skipped_errors
28
- # @return [Array<Class>, nil] The default list of errors to skip.
29
70
  attr_writer :skipped_errors
30
-
31
- # @!attribute [w] error_notifier
32
- # @return [Proc, nil] The default error notifier (callable object).
33
- attr_writer :error_notifier
34
-
35
- # @!attribute [rw] notifiers
36
- # @return [Array<Stoplight::Domain::StateTransitionNotifier>] The default list of notifiers.
37
- attr_accessor :notifiers
38
-
39
- # @!attribute [rw] data_store
40
- # @return [Stoplight::Wiring::DataStore::Base] The default data store instance.
41
- attr_accessor :data_store
42
-
43
- # @!attribute [w] traffic_control
44
- # @return [Stoplight::Domain::TrafficControl::Base] The traffic control strategy.
45
71
  attr_writer :traffic_control
46
-
47
- # @!attribute [w] traffic_recovery
48
- # @return [Stoplight::Domain::TrafficRecovery::Base] The traffic recovery strategy.
49
72
  attr_writer :traffic_recovery
73
+ attr_writer :error_notifier
74
+ attr_writer :data_store
75
+ attr_writer :notifiers
50
76
 
51
- def initialize
52
- # This allows users appending notifiers to the default list,
53
- # while still allowing them to override the default list.
54
- @notifiers = Default::NOTIFIERS
55
- end
56
-
57
- # Converts the user-defined configuration to a hash.
58
- #
59
- # @return [Hash] A hash representation of the configuration, excluding nil values.
60
- # @api private
61
- def to_h
62
- {
77
+ # Builds and validates configuration
78
+ def to_config!
79
+ ConfigurationDsl.new(
63
80
  cool_off_time: @cool_off_time,
64
81
  threshold: @threshold,
65
82
  recovery_threshold: @recovery_threshold,
66
83
  window_size: @window_size,
67
84
  tracked_errors: @tracked_errors,
68
85
  skipped_errors: @skipped_errors,
69
- data_store: @data_store,
70
- error_notifier: @error_notifier,
71
- notifiers: @notifiers,
72
86
  traffic_control: @traffic_control,
73
- traffic_recovery: @traffic_recovery
74
- }.compact
87
+ traffic_recovery: @traffic_recovery,
88
+ error_notifier: @error_notifier,
89
+ data_store: @data_store,
90
+ notifiers: @notifiers
91
+ ).configure!(@config)
75
92
  end
76
93
  end
77
94
  end
@@ -34,43 +34,28 @@ module Stoplight
34
34
  MEMORY_REGISTRY = Concurrent::Map.new
35
35
  private_constant :MEMORY_REGISTRY
36
36
 
37
- # @!attribute data_store_config
38
- # @return [Stoplight::DataStore::Bose]
39
- private attr_reader :data_store_config
40
-
41
- # @!attribute error_notifier
42
- # @return [Proc]
43
- private attr_reader :error_notifier
44
-
45
- # @!attribute traffic_recovery
46
- # @return [Stoplight::Domain::TrafficRecovery::Base]
47
- private attr_reader :traffic_recovery
48
-
49
- # @!attribute traffic_control
50
- # @return [Stoplight::Domain::TrafficControl::Base]
51
- private attr_reader :traffic_control
52
-
53
- # @!attribute config
54
- # @return [Stoplight::Domain::Config]
55
- private attr_reader :config
56
-
57
- # @!attribute factory
58
- # @return [Stoplight::Domain::LightFactory]
59
- private attr_reader :factory
60
-
61
- def initialize(settings)
62
- @notifiers = settings[:notifiers]
63
- @data_store_config = settings[:data_store]
64
- @error_notifier = settings[:error_notifier]
65
- @traffic_recovery = settings[:traffic_recovery]
66
- @traffic_control = settings[:traffic_control]
67
- @config = settings[:config]
68
- @factory = settings[:factory]
37
+ def initialize(config:, factory:)
38
+ @clock = Infrastructure::SystemClock.new
39
+ @config = config
40
+ @name = T.must(config.name)
41
+ @cool_off_time = config.cool_off_time
42
+
43
+ @data_store_config = config.data_store
44
+ @error_notifier = config.error_notifier
45
+ @factory = factory
46
+ @notifiers = config.notifiers
47
+ @traffic_recovery = config.traffic_recovery
48
+ @traffic_control = config.traffic_control
49
+
50
+ @error_tracking_policy = Domain::ErrorTrackingPolicy.new(
51
+ tracked: config.tracked_errors,
52
+ skipped: config.skipped_errors
53
+ )
69
54
  end
70
55
 
71
56
  def build
72
57
  Stoplight::Domain::Light.new(
73
- config,
58
+ @name,
74
59
  state_store:,
75
60
  green_run_strategy:,
76
61
  yellow_run_strategy:,
@@ -79,50 +64,60 @@ module Stoplight
79
64
  )
80
65
  end
81
66
 
82
- private def state_store = Stoplight::Infrastructure::Storage::CompatibilityState.new(config:, data_store:)
67
+ private
68
+
69
+ attr_reader :data_store_config
70
+ attr_reader :error_notifier
71
+ attr_reader :factory
72
+ attr_reader :clock
73
+ attr_reader :traffic_control
74
+ attr_reader :traffic_recovery
75
+ attr_reader :config
76
+
77
+ def state_store = Stoplight::Infrastructure::Storage::CompatibilityState.new(config:, data_store:)
83
78
 
84
79
  # @return [<Stoplight::Notifier::Base>]
85
- private def notifiers
80
+ def notifiers
86
81
  Array(@notifiers).map do |notifier|
87
82
  Infrastructure::Notifier::FailSafe.new(notifier:, error_notifier:)
88
83
  end
89
84
  end
90
85
 
91
- private def redis_recovery_lock_store
92
- Infrastructure::DataStore::Redis::RecoveryLockStore.new(
93
- redis: data_store_config.redis,
86
+ def redis_recovery_lock_store
87
+ Infrastructure::Redis::DataStore::RecoveryLockStore.new(
88
+ redis:,
94
89
  lock_timeout: config.cool_off_time_in_milliseconds,
95
90
  scripting:
96
91
  )
97
92
  end
98
93
 
99
- private def scripting = Infrastructure::DataStore::Redis::Scripting.new(redis: data_store_config.redis)
94
+ def scripting = Infrastructure::Redis::DataStore::Scripting.new(redis:)
100
95
 
101
- private def memory_recovery_lock_store
102
- Infrastructure::DataStore::Memory::RecoveryLockStore.new
96
+ def memory_recovery_lock_store
97
+ Infrastructure::Memory::DataStore::RecoveryLockStore.new
103
98
  end
104
99
 
105
- private def failover_data_store
100
+ def failover_data_store
106
101
  create_data_store(FAILOVER_DATA_STORE_CONFIG)
107
102
  end
108
103
 
109
- private def data_store
104
+ def data_store
110
105
  create_data_store(data_store_config)
111
106
  end
112
107
 
113
- private def metrics_store
108
+ def metrics_store
114
109
  Stoplight::Infrastructure::Storage::CompatibilityMetrics.new(config:, data_store:)
115
110
  end
116
111
 
117
- private def recovery_lock_store
112
+ def recovery_lock_store
118
113
  Stoplight::Infrastructure::Storage::CompatibilityRecoveryLock.new(config:, data_store:)
119
114
  end
120
115
 
121
- private def request_tracker
116
+ def request_tracker
122
117
  Domain::Tracker::Request.new(traffic_control:, notifiers:, config:, metrics_store:, state_store:)
123
118
  end
124
119
 
125
- private def recovery_probe_tracker
120
+ def recovery_probe_tracker
126
121
  Domain::Tracker::RecoveryProbe.new(
127
122
  traffic_recovery:,
128
123
  notifiers:,
@@ -132,41 +127,48 @@ module Stoplight
132
127
  )
133
128
  end
134
129
 
135
- private def recovery_metrics_store
130
+ def recovery_metrics_store
136
131
  Stoplight::Infrastructure::Storage::CompatibilityRecoveryMetrics.new(config:, data_store:)
137
132
  end
138
133
 
139
- private def green_run_strategy
140
- Domain::Strategies::GreenRunStrategy.new(config:, request_tracker:)
134
+ def green_run_strategy
135
+ Domain::Strategies::GreenRunStrategy.new(
136
+ error_tracking_policy: @error_tracking_policy,
137
+ request_tracker:
138
+ )
141
139
  end
142
140
 
143
- private def yellow_run_strategy
141
+ def yellow_run_strategy
144
142
  Domain::Strategies::YellowRunStrategy.new(
145
- config:,
143
+ name: @name,
144
+ error_tracking_policy: @error_tracking_policy,
146
145
  notifiers:,
147
146
  request_tracker: recovery_probe_tracker,
148
147
  red_run_strategy:,
149
148
  state_store:,
150
149
  metrics_store:,
151
- recovery_lock_store:
150
+ recovery_lock_store:,
151
+ config: @config
152
152
  )
153
153
  end
154
154
 
155
- private def red_run_strategy
156
- Domain::Strategies::RedRunStrategy.new(config:)
155
+ def red_run_strategy
156
+ Domain::Strategies::RedRunStrategy.new(name: @name, cool_off_time: @cool_off_time)
157
157
  end
158
158
 
159
- private def create_data_store(data_store_config)
159
+ def create_data_store(data_store_config)
160
160
  case data_store_config
161
- in Stoplight::DataStore::Memory
161
+ when Stoplight::DataStore::Memory
162
162
  memory_registry.compute_if_absent(data_store_config.object_id) do
163
- Infrastructure::DataStore::Memory.new(
164
- recovery_lock_store: memory_recovery_lock_store
163
+ Infrastructure::Memory::DataStore.new(
164
+ recovery_lock_store: memory_recovery_lock_store,
165
+ clock:
165
166
  )
166
167
  end
167
- in Stoplight::DataStore::Redis
168
- Infrastructure::DataStore::FailSafe.new(
169
- data_store: Stoplight::Infrastructure::DataStore::Redis.new(
168
+ when Stoplight::DataStore::Redis
169
+ Infrastructure::FailSafe::DataStore.new(
170
+ data_store: Stoplight::Infrastructure::Redis::DataStore.new(
171
+ clock:,
170
172
  redis: data_store_config.redis,
171
173
  warn_on_clock_skew: data_store_config.warn_on_clock_skew,
172
174
  recovery_lock_store: redis_recovery_lock_store,
@@ -176,10 +178,21 @@ module Stoplight
176
178
  failover_data_store:,
177
179
  circuit_breaker: Stoplight.system_light("data_store:fail_safe:redis")
178
180
  )
181
+ else
182
+ raise NoMatchingPatternError, data_store_config
183
+ end
184
+ end
185
+
186
+ def redis
187
+ case data_store_config
188
+ when DataStore::Redis
189
+ data_store_config.redis
190
+ else
191
+ raise TypeError, "Expected Stoplight::DataStore::Redis, got #{data_store_config.class}"
179
192
  end
180
193
  end
181
194
 
182
- private def memory_registry = MEMORY_REGISTRY
195
+ def memory_registry = MEMORY_REGISTRY
183
196
  end
184
197
  end
185
198
  end
@@ -3,9 +3,9 @@
3
3
  module Stoplight
4
4
  module Wiring
5
5
  class LightFactory
6
- TrafficControlDsl = proc do |value|
6
+ TrafficControlDsl = ->(value) {
7
7
  case value
8
- in Domain::TrafficControl::Base
8
+ in _ if value.respond_to?(:stop_traffic?) # TODO: can be removed in 6.0
9
9
  value
10
10
  in :consecutive_errors
11
11
  Domain::TrafficControl::ConsecutiveErrors.new
@@ -20,7 +20,7 @@ module Stoplight
20
20
  * :error_rate
21
21
  ERROR
22
22
  end
23
- end
23
+ }
24
24
  end
25
25
  end
26
26
  end
@@ -3,19 +3,19 @@
3
3
  module Stoplight
4
4
  module Wiring
5
5
  class LightFactory
6
- TrafficRecoveryDsl = proc do |value|
6
+ TrafficRecoveryDsl = ->(value) {
7
7
  case value
8
- in Domain::TrafficRecovery::Base
8
+ in _ if value.respond_to?(:determine_color) # TODO: remove in 6.0
9
9
  value
10
10
  in :consecutive_successes
11
11
  Domain::TrafficRecovery::ConsecutiveSuccesses.new
12
12
  else
13
- raise Domain::Error::ConfigurationError, <<~ERROR
13
+ raise Error::ConfigurationError, <<~ERROR
14
14
  unsupported traffic_recovery strategy provided (`#{value}`). Supported options:
15
15
  * :consecutive_successes
16
16
  ERROR
17
17
  end
18
- end
18
+ }
19
19
  end
20
20
  end
21
21
  end