stoplight 5.5.0 → 5.7.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/stoplight/admin/actions/remove.rb +23 -0
  4. data/lib/stoplight/admin/dependencies.rb +6 -1
  5. data/lib/stoplight/admin/helpers.rb +10 -5
  6. data/lib/stoplight/admin/lights_repository.rb +26 -14
  7. data/lib/stoplight/admin/views/_card.erb +13 -1
  8. data/lib/stoplight/admin.rb +9 -0
  9. data/lib/stoplight/common/deprecations.rb +11 -0
  10. data/lib/stoplight/domain/config.rb +5 -1
  11. data/lib/stoplight/domain/data_store.rb +58 -6
  12. data/lib/stoplight/domain/failure.rb +2 -0
  13. data/lib/stoplight/domain/light/configuration_builder_interface.rb +120 -16
  14. data/lib/stoplight/domain/light.rb +34 -24
  15. data/lib/stoplight/domain/metrics.rb +64 -0
  16. data/lib/stoplight/domain/recovery_lock_token.rb +15 -0
  17. data/lib/stoplight/domain/{metadata.rb → state_snapshot.rb} +29 -37
  18. data/lib/stoplight/domain/storage/metrics.rb +42 -0
  19. data/lib/stoplight/domain/storage/recovery_lock.rb +56 -0
  20. data/lib/stoplight/domain/storage/state.rb +87 -0
  21. data/lib/stoplight/domain/strategies/green_run_strategy.rb +2 -2
  22. data/lib/stoplight/domain/strategies/red_run_strategy.rb +3 -3
  23. data/lib/stoplight/domain/strategies/run_strategy.rb +2 -7
  24. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +63 -36
  25. data/lib/stoplight/domain/tracker/base.rb +0 -29
  26. data/lib/stoplight/domain/tracker/recovery_probe.rb +26 -22
  27. data/lib/stoplight/domain/tracker/request.rb +26 -21
  28. data/lib/stoplight/domain/traffic_control/base.rb +5 -5
  29. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +3 -7
  30. data/lib/stoplight/domain/traffic_control/error_rate.rb +3 -3
  31. data/lib/stoplight/domain/traffic_recovery/base.rb +5 -5
  32. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +4 -8
  33. data/lib/stoplight/domain/traffic_recovery.rb +0 -1
  34. data/lib/stoplight/infrastructure/data_store/fail_safe.rb +164 -0
  35. data/lib/stoplight/infrastructure/data_store/memory/metrics.rb +27 -0
  36. data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_store.rb +54 -0
  37. data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_token.rb +20 -0
  38. data/lib/stoplight/infrastructure/data_store/memory/state.rb +21 -0
  39. data/lib/stoplight/infrastructure/data_store/memory.rb +163 -132
  40. data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/get_metrics.lua +26 -0
  41. data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_failure.lua +27 -0
  42. data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_success.lua +23 -0
  43. data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/release_lock.lua +6 -0
  44. data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_store.rb +73 -0
  45. data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_token.rb +35 -0
  46. data/lib/stoplight/infrastructure/data_store/redis/scripting.rb +71 -0
  47. data/lib/stoplight/infrastructure/data_store/redis.rb +211 -165
  48. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +62 -0
  49. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +48 -0
  50. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +36 -0
  51. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +55 -0
  52. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +55 -0
  53. data/lib/stoplight/version.rb +1 -1
  54. data/lib/stoplight/wiring/data_store/base.rb +11 -0
  55. data/lib/stoplight/wiring/data_store/memory.rb +10 -0
  56. data/lib/stoplight/wiring/data_store/redis.rb +25 -0
  57. data/lib/stoplight/wiring/default.rb +1 -1
  58. data/lib/stoplight/wiring/default_configuration.rb +1 -1
  59. data/lib/stoplight/wiring/default_factory_builder.rb +1 -1
  60. data/lib/stoplight/wiring/light_builder.rb +185 -0
  61. data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +55 -0
  62. data/lib/stoplight/wiring/light_factory/config_normalizer.rb +71 -0
  63. data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +72 -0
  64. data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +26 -0
  65. data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +21 -0
  66. data/lib/stoplight/wiring/light_factory.rb +45 -132
  67. data/lib/stoplight/wiring/notifier_factory.rb +26 -0
  68. data/lib/stoplight/wiring/public_api.rb +3 -2
  69. data/lib/stoplight.rb +18 -3
  70. metadata +55 -16
  71. data/lib/stoplight/infrastructure/data_store/redis/get_metadata.lua +0 -38
  72. data/lib/stoplight/infrastructure/data_store/redis/lua.rb +0 -25
  73. data/lib/stoplight/infrastructure/dependency_injection/container.rb +0 -249
  74. data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +0 -13
  75. data/lib/stoplight/wiring/container.rb +0 -80
  76. data/lib/stoplight/wiring/fail_safe_data_store.rb +0 -123
  77. data/lib/stoplight/wiring/fail_safe_notifier.rb +0 -79
  78. data/lib/stoplight/wiring/system_container.rb +0 -9
  79. data/lib/stoplight/wiring/system_light_factory.rb +0 -17
  80. /data/lib/stoplight/infrastructure/data_store/redis/{record_failure.lua → lua_scripts/record_failure.lua} +0 -0
  81. /data/lib/stoplight/infrastructure/data_store/redis/{record_success.lua → lua_scripts/record_success.lua} +0 -0
  82. /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_green.lua → lua_scripts/transition_to_green.lua} +0 -0
  83. /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_red.lua → lua_scripts/transition_to_red.lua} +0 -0
  84. /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_yellow.lua → lua_scripts/transition_to_yellow.lua} +0 -0
@@ -1,249 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Infrastructure
5
- module DependencyInjection
6
- # Generic dependency injection container for managing object dependencies.
7
- #
8
- # This is a low-level infrastructure component that provides the mechanism for
9
- # dependency resolution. It has no knowledge of Stoplight's domain concepts and
10
- # could theoretically be used in any Ruby application.
11
- #
12
- # The container supports three resolution strategies:
13
- #
14
- # 1. **Direct dependencies** - Pre-configured values stored in the container
15
- # 2. **Lazy initialization** - Dependencies that are transformed on first access
16
- # 3. **Factories** - Dependencies computed dynamically from other dependencies
17
- #
18
- # @example Basic usage with direct dependencies
19
- # container = Container.new
20
- # container.register(:logger, Logger.new)
21
- # container.resolve(:logger) #=> #<Logger...>
22
- #
23
- # @example Using lazy initialization
24
- # container.register(:redis, "redis://localhost:6379") do |url|
25
- # Redis.new(url)
26
- # end
27
- # container.resolve(:redis) #=> #<Redis @connection_string="redis://localhost:6379">
28
- #
29
- # @example Using factories for computed dependencies
30
- # container.register(:config, my_config)
31
- # container.factory(:service) do
32
- # MyService.new(config: resolve(:config))
33
- # end
34
- # container.resolve(:service) #=> #<MyService...>
35
- #
36
- # @example Building a container with DSL
37
- # container = Container.define do
38
- # register(:port, 3000)
39
- # register(:host, "localhost")
40
- #
41
- # factory(:server) do
42
- # Server.new(host: resolve(:host), port: resolve(:port))
43
- # end
44
- # end
45
- #
46
- # @api private
47
- class Container
48
- # @!attribute [r] dependencies
49
- # Stores registered dependency values
50
- # @return [Hash{Symbol => Object}]
51
- protected attr_reader :dependencies
52
-
53
- # @!attribute [r] factories
54
- # Stores factory blocks for computed dependencies
55
- # @return [Hash{Symbol => Proc}]
56
- protected attr_reader :factories
57
-
58
- # @!attribute [r] initializers
59
- # Stores optional transformation blocks for dependencies
60
- # @return [Hash{Symbol => Proc}]
61
- protected attr_reader :initializers
62
-
63
- class << self
64
- # Define a container using a DSL block.
65
- #
66
- # This is a convenience method for creating and configuring a container
67
- # in a single expression.
68
- #
69
- # @yield Block evaluated in the context of the new container
70
- # @return [Stoplight::Infrastructure::DependencyInjection::Container] Configured container instance
71
- #
72
- # @example
73
- # container = Container.define do
74
- # register(:redis_url, "redis://localhost:6379")
75
- # factory(:redis) { Redis.new(resolve(:redis_url)) }
76
- # end
77
- def define(&definition)
78
- new.define(&definition)
79
- end
80
- end
81
-
82
- # Creates a new dependency injection container.
83
- #
84
- # @param dependencies [Hash{Symbol => Object}]
85
- # @param factories [Hash{Symbol => Proc}]
86
- # @param initializers [Hash{Symbol => Proc}]
87
- def initialize(dependencies: {}, factories: {}, initializers: {})
88
- @dependencies = dependencies
89
- @initializers = initializers
90
- @factories = factories
91
- end
92
-
93
- # Evaluate a configuration block in the context of this container.
94
- #
95
- # @yield Block evaluated in the context of the container
96
- # @return [Stoplight::Infrastructure::DependencyInjection::Container] self for method chaining
97
- #
98
- # @example
99
- # container = Container.new
100
- # container.define do
101
- # register(:port, 8080)
102
- # factory(:server) { Server.new(port: resolve(:port)) }
103
- # end
104
-
105
- def define(&definition)
106
- instance_eval(&definition)
107
- freeze
108
- self
109
- end
110
-
111
- # Registers a dependency value with optional lazy initialization.
112
- #
113
- # The initializer block is called every time the dependency is resolved,
114
- # receiving the registered value as an argument. This allows for delayed
115
- # object construction or transformation of simple values into complex objects.
116
- #
117
- # @param name [Symbol] The dependency key
118
- # @param value [Object] The dependency value (may be transformed by initializer)
119
- # @yield [value] Optional transformation block
120
- # @yieldparam value [Object] The registered value
121
- # @yieldreturn [Object] The transformed value to return when resolving
122
- # @return [Stoplight::Infrastructure::DependencyInjection::Container] self for method chaining
123
- #
124
- # @example Register a simple dependency
125
- # container.register(:port, 3000)
126
- # container.resolve(:port) #=> 3000
127
- #
128
- # @example Register with lazy initialization
129
- # container.register(:redis, "redis://localhost:6379") do |url|
130
- # Redis.new(url)
131
- # end
132
- # container.resolve(:redis) #=> #<Redis...>
133
- #
134
- # @example Initialization is applied on every resolution
135
- # container.register(:timestamp, Time.now, &:to_i)
136
- # container.resolve(:timestamp) #=> 1234567890
137
- #
138
- # # Update the value
139
- # container.register(:timestamp, Time.now + 60)
140
- # container.resolve(:timestamp) #=> 1234567950 (new timestamp)
141
- #
142
- def register(name, value, &initializer)
143
- dependencies[name] = value
144
- initializers[name] = initializer if block_given?
145
- self
146
- end
147
-
148
- # Resolves a dependency by name.
149
- #
150
- # Resolution order:
151
- # 1. Direct dependency with initializer (value is transformed)
152
- # 2. Direct dependency without initializer (value returned as-is)
153
- # 3. Factory (block evaluated in container context)
154
- # 4. Raises {Stoplight::Infrastructure::DependencyInjection::UnresolvedDependencyError}
155
- #
156
- # @param name [Symbol] The dependency key
157
- # @return [Object] The resolved dependency value
158
- # @raise [Stoplight::Infrastructure::DependencyInjection::UnresolvedDependencyError] if dependency is not registered
159
- #
160
- def resolve(name)
161
- if dependencies.key?(name)
162
- value = dependencies[name]
163
- if initializers.key?(name)
164
- initializer = initializers[name]
165
- instance_exec(value, &initializer)
166
- else
167
- value
168
- end
169
- elsif factories.key?(name)
170
- factory = factories[name]
171
- instance_eval(&factory)
172
- else
173
- raise UnresolvedDependencyError, name
174
- end
175
- end
176
-
177
- # Registers a factory for computing dependencies dynamically.
178
- #
179
- # Factories are evaluated lazily when the dependency is resolved.
180
- # The factory block is evaluated in the container's context, giving
181
- # it access to the +#resolve+ method for accessing other dependencies.
182
- #
183
- # Unlike +#register+ with an initializer, factories are not cached -
184
- # they are executed every time the dependency is resolved.
185
- #
186
- # @param key [Symbol] The dependency key
187
- # @yield Factory block evaluated in container context
188
- # @yieldreturn [Object] The computed dependency value
189
- # @return [Stoplight::Infrastructure::DependencyInjection::Container] self for method chaining
190
- #
191
- def factory(key, &factory)
192
- factories[key] = factory
193
- self
194
- end
195
-
196
- # Returns all registered dependency keys.
197
- #
198
- # This includes both direct dependencies and factory definitions.
199
- #
200
- # @return [Array<Symbol>] All registered dependency keys
201
- #
202
- # @example
203
- # container.register(:port, 3000)
204
- # container.factory(:server) { Server.new }
205
- # container.keys #=> [:port, :server]
206
- #
207
- def keys
208
- factories.keys | dependencies.keys
209
- end
210
-
211
- # Creates a new container with merged dependencies.
212
- #
213
- # This is an immutable operation - the original container is not modified.
214
- # Factories and initializers are copied to the new container.
215
- #
216
- # @param new_dependencies [Hash{Symbol => Object}] Dependencies to merge/override
217
- # @return [Stoplight::Infrastructure::DependencyInjection::Container] New container with merged dependencies
218
- #
219
- # @example Creating specialized containers
220
- # base = Container.define do
221
- # register(:host, "localhost")
222
- # register(:port, 3000)
223
- # end
224
- #
225
- # production = base.with(host: "prod.example.com", port: 80)
226
- # staging = base.with(host: "staging.example.com", port: 8080)
227
- #
228
- # base.resolve(:port) #=> 3000
229
- # production.resolve(:port) #=> 80
230
- # staging.resolve(:port) #=> 8080
231
-
232
- def with(**new_dependencies)
233
- self.class.new(
234
- dependencies: {**dependencies, **new_dependencies},
235
- factories:,
236
- initializers:
237
- )
238
- end
239
-
240
- def ==(other)
241
- other.is_a?(self.class) &&
242
- other.dependencies == dependencies &&
243
- other.factories == factories &&
244
- other.initializers == initializers
245
- end
246
- end
247
- end
248
- end
249
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Infrastructure
5
- module DependencyInjection
6
- class UnresolvedDependencyError < Domain::Error::Base
7
- def initialize(key)
8
- super("Unable to resolve dependency: `#{key}`")
9
- end
10
- end
11
- end
12
- end
13
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- # This container implements an instance of +Stoplight::Infrastructure::DependencyInjection::Container+
6
- # with Stoplight-specific wiring knowledge. It defines how to construct and connect
7
- # all the components needed for a circuit breaker to function.
8
- #
9
- # ## Default Configuration
10
- #
11
- # The container is pre-configured with sensible defaults:
12
- # - Data Store - in-memory storage
13
- # - STDERR notifier
14
- # - No-op error notifier
15
- # - Consecutive failure detection
16
- # - Consecutive success recovery
17
- #
18
- # @see Infrastructure::DependencyInjection::Container Generic DI container
19
- # @see Stoplight::Wiring::LightFactory Factory that uses this container
20
- # @api private
21
- Container = Infrastructure::DependencyInjection::Container.define do
22
- register(:config, Light::DefaultConfig)
23
- register(:error_notifier, Default::ERROR_NOTIFIER)
24
- register(:traffic_control, Default::TRAFFIC_CONTROL)
25
- register(:traffic_recovery, Default::TRAFFIC_RECOVERY)
26
-
27
- register(:data_store, Default::DATA_STORE) do |data_store|
28
- FailSafeDataStore.wrap(
29
- data_store:,
30
- error_notifier: resolve(:error_notifier)
31
- )
32
- end
33
-
34
- register(:notifiers, Default::NOTIFIERS) do |notifiers|
35
- error_notifier = resolve(:error_notifier)
36
- notifiers.map { |notifier| Wiring::FailSafeNotifier.wrap(notifier:, error_notifier:) }
37
- end
38
-
39
- factory(:green_run_strategy) do
40
- Domain::Strategies::GreenRunStrategy.new(
41
- config: resolve(:config),
42
- request_tracker: resolve(:request_tracker)
43
- )
44
- end
45
-
46
- factory(:yellow_run_strategy) do
47
- Domain::Strategies::YellowRunStrategy.new(
48
- config: resolve(:config),
49
- data_store: resolve(:data_store),
50
- notifiers: resolve(:notifiers),
51
- request_tracker: resolve(:recovery_probe_tracker)
52
- )
53
- end
54
-
55
- factory(:red_run_strategy) do
56
- Domain::Strategies::RedRunStrategy.new(
57
- config: resolve(:config)
58
- )
59
- end
60
-
61
- factory(:request_tracker) do
62
- Domain::Tracker::Request.new(
63
- data_store: resolve(:data_store),
64
- traffic_control: resolve(:traffic_control),
65
- notifiers: resolve(:notifiers),
66
- config: resolve(:config)
67
- )
68
- end
69
-
70
- factory(:recovery_probe_tracker) do
71
- Domain::Tracker::RecoveryProbe.new(
72
- data_store: resolve(:data_store),
73
- traffic_recovery: resolve(:traffic_recovery),
74
- notifiers: resolve(:notifiers),
75
- config: resolve(:config)
76
- )
77
- end
78
- end
79
- end
80
- end
@@ -1,123 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "securerandom"
4
-
5
- module Stoplight
6
- module Wiring
7
- # A wrapper around a data store that provides fail-safe mechanisms using a
8
- # circuit breaker. It ensures that operations on the data store can gracefully
9
- # handle failures by falling back to default values when necessary.
10
- #
11
- # @api private
12
- class FailSafeDataStore < Domain::DataStore
13
- class << self
14
- # Wraps a data store with fail-safe mechanisms.
15
- #
16
- # @param data_store [Stoplight::DataStore::Base] The data store to wrap.
17
- # @param error_notifier [Proc] called when wrapped data store fails
18
- # @return [Stoplight::DataStore::Base, FailSafe] The original data store if it is already
19
- # a +Memory+ or +FailSafe+ instance, otherwise a new +FailSafe+ instance.
20
- def wrap(data_store:, error_notifier:)
21
- case data_store
22
- in Infrastructure::DataStore::Memory
23
- data_store
24
- in self if data_store.error_notifier == error_notifier
25
- data_store
26
- in self
27
- new(data_store: data_store.data_store, error_notifier:)
28
- else
29
- new(data_store:, error_notifier:)
30
- end
31
- end
32
- end
33
-
34
- # @!attribute data_store
35
- # @return [Stoplight::DataStore::Base] The underlying primary data store being used
36
- attr_reader :data_store
37
-
38
- # @!attribute error_notifier
39
- # @return [Proc]
40
- attr_reader :error_notifier
41
-
42
- # @!attribute failover_data_store
43
- # @return [Stoplight::DataStore::Base] The fallback data store used when the primary fails.
44
- private attr_reader :failover_data_store
45
-
46
- # @!attribute circuit_breaker
47
- # @return [Stoplight::Light] The circuit breaker used to handle data store failures.
48
- private attr_reader :circuit_breaker
49
-
50
- # @param data_store [Stoplight::Domain::DataStore]
51
- # @param error_notifier [Proc]
52
- def initialize(data_store:, error_notifier:, failover_data_store: Wiring::Default::DATA_STORE)
53
- @data_store = data_store
54
- @error_notifier = error_notifier
55
- @failover_data_store = failover_data_store
56
- @circuit_breaker = Stoplight.system_light("data_store:fail_safe:#{data_store.class.name}")
57
- end
58
-
59
- def names
60
- with_fallback(:names) do
61
- data_store.names
62
- end
63
- end
64
-
65
- def get_metadata(config, *args, **kwargs)
66
- with_fallback(:get_metadata, config, *args, **kwargs) do
67
- data_store.get_metadata(config, *args, **kwargs)
68
- end
69
- end
70
-
71
- def record_failure(config, *args, **kwargs)
72
- with_fallback(:record_failure, config, *args, **kwargs) do
73
- data_store.record_failure(config, *args, **kwargs)
74
- end
75
- end
76
-
77
- def record_success(config, *args, **kwargs)
78
- with_fallback(:record_success, config, *args, **kwargs) do
79
- data_store.record_success(config, *args, **kwargs)
80
- end
81
- end
82
-
83
- def record_recovery_probe_success(config, *args, **kwargs)
84
- with_fallback(:record_recovery_probe_success, config, *args, **kwargs) do
85
- data_store.record_recovery_probe_success(config, *args, **kwargs)
86
- end
87
- end
88
-
89
- def record_recovery_probe_failure(config, *args, **kwargs)
90
- with_fallback(:record_recovery_probe_failure, config, *args, **kwargs) do
91
- data_store.record_recovery_probe_failure(config, *args, **kwargs)
92
- end
93
- end
94
-
95
- def set_state(config, *args, **kwargs)
96
- with_fallback(:set_state, config, *args, **kwargs) do
97
- data_store.set_state(config, *args, **kwargs)
98
- end
99
- end
100
-
101
- def transition_to_color(config, *args, **kwargs)
102
- with_fallback(:transition_to_color, config, *args, **kwargs) do
103
- data_store.transition_to_color(config, *args, **kwargs)
104
- end
105
- end
106
-
107
- def ==(other)
108
- other.is_a?(self.class) && other.data_store == data_store && other.error_notifier == error_notifier
109
- end
110
-
111
- # @param method_name [Symbol] protected method name
112
- private def with_fallback(method_name, *args, **kwargs, &code)
113
- fallback = proc do |error|
114
- config = args.first
115
- error_notifier.call(error) if config && error
116
- @failover_data_store.public_send(method_name, *args, **kwargs)
117
- end
118
-
119
- circuit_breaker.run(fallback, &code)
120
- end
121
- end
122
- end
123
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- # A wrapper around a notifier that provides fail-safe mechanisms using a
6
- # circuit breaker. It ensures that a notification can gracefully
7
- # handle failures.
8
- #
9
- # @api private
10
- class FailSafeNotifier < Domain::StateTransitionNotifier
11
- # @!attribute [r] notifier
12
- # @return [Stoplight::Domain::StateTransitionNotifier] The underlying notifier being wrapped.
13
- attr_reader :notifier
14
-
15
- # @!attribute [r] error_notifier
16
- # @return [Stoplight::Domain::StateTransitionNotifier] The underlying notifier being wrapped.
17
- attr_reader :error_notifier
18
-
19
- class << self
20
- # Wraps a notifier with fail-safe mechanisms.
21
- #
22
- # @param notifier [Stoplight::Domain::StateTransitionNotifier] The notifier to wrap.
23
- # @param error_notifier [Proc] called when wrapped data store fails
24
- # @return [Stoplight::Notifier::FailSafe] The original notifier if it is already
25
- # a +FailSafe+ instance, otherwise a new +FailSafe+ instance.
26
- def wrap(notifier:, error_notifier:)
27
- case notifier
28
- in self if notifier.error_notifier == error_notifier
29
- notifier
30
- in self
31
- new(notifier: notifier.notifier, error_notifier:)
32
- else
33
- new(notifier:, error_notifier:)
34
- end
35
- end
36
- end
37
-
38
- # Initializes a new instance of the +FailSafe+ class.
39
- #
40
- # @param notifier [Stoplight::Domain::StateTransitionNotifier] The notifier to wrap.
41
- # @param error_notifier [Proc] called when wrapped data store fails
42
- def initialize(notifier:, error_notifier:)
43
- @notifier = notifier
44
- @error_notifier = error_notifier
45
- end
46
-
47
- # Sends a notification using the wrapped notifier with fail-safe mechanisms.
48
- #
49
- # @param config [Stoplight::Domain::Config] The light configuration.
50
- # @param from_color [String] The initial color of the light.
51
- # @param to_color [String] The target color of the light.
52
- # @param error [Exception, nil] An optional error to include in the notification.
53
- # @return [void]
54
- def notify(config, from_color, to_color, error = nil)
55
- fallback = proc do |exception|
56
- error_notifier.call(exception) if exception
57
- nil
58
- end
59
-
60
- circuit_breaker.run(fallback) do
61
- notifier.notify(config, from_color, to_color, error)
62
- end
63
- end
64
-
65
- # @return [Boolean]
66
- def ==(other)
67
- other.is_a?(self.class) && notifier == other.notifier
68
- end
69
-
70
- # @return [Stoplight::Light] The circuit breaker used to handle failures.
71
- private def circuit_breaker
72
- @circuit_breaker ||= Stoplight.system_light(
73
- "stoplight:notifier:fail_safe:#{notifier.class.name}",
74
- notifiers: []
75
- )
76
- end
77
- end
78
- end
79
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- SystemContainer = Container.with(
6
- traffic_recovery: Domain::TrafficRecovery::ConsecutiveSuccesses.new
7
- )
8
- end
9
- end
@@ -1,17 +0,0 @@
1
- # frozon_string_literal: true
2
-
3
- module Stoplight
4
- module Wiring
5
- # Factory for internal system lights used by the Stoplight itself.
6
- #
7
- # System lights are isolated from user configuration to prevent
8
- # user settings from breaking the library's own circuit breakers.
9
- # For example, the FailSafe data store wrapper uses a system light
10
- # to protect against data store failures.
11
- #
12
- # @api private
13
- SystemLightFactory = Wiring::LightFactory.new(
14
- Wiring::SystemContainer.with(config: Light::SystemConfig)
15
- )
16
- end
17
- end