stoplight 5.4.0 → 5.5.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/stoplight/admin/views/layout.erb +3 -3
- data/lib/stoplight/admin.rb +4 -4
- data/lib/stoplight/domain/color.rb +11 -0
- data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
- data/lib/stoplight/domain/config.rb +55 -0
- data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +17 -15
- data/lib/stoplight/domain/error.rb +42 -0
- data/lib/stoplight/domain/failure.rb +42 -0
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +130 -0
- data/lib/stoplight/domain/light.rb +198 -0
- data/lib/stoplight/domain/light_factory.rb +75 -0
- data/lib/stoplight/domain/metadata.rb +65 -0
- data/lib/stoplight/domain/state.rb +11 -0
- data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +69 -0
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
- data/lib/stoplight/domain/strategies/run_strategy.rb +27 -0
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +98 -0
- data/lib/stoplight/domain/tracker/base.rb +41 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +72 -0
- data/lib/stoplight/domain/tracker/request.rb +67 -0
- data/lib/stoplight/domain/traffic_control/base.rb +74 -0
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +57 -0
- data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
- data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +70 -0
- data/lib/stoplight/domain/traffic_recovery.rb +13 -0
- data/lib/stoplight/infrastructure/data_store/memory/sliding_window.rb +79 -0
- data/lib/stoplight/infrastructure/data_store/memory.rb +307 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua.rb +25 -0
- data/lib/stoplight/infrastructure/data_store/redis.rb +478 -0
- data/lib/stoplight/infrastructure/dependency_injection/container.rb +249 -0
- data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +13 -0
- data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
- data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
- data/lib/stoplight/infrastructure/notifier/logger.rb +21 -0
- data/lib/stoplight/rspec/generic_notifier.rb +1 -1
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/container.rb +80 -0
- data/lib/stoplight/wiring/default.rb +28 -0
- data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
- data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
- data/lib/stoplight/{data_store/fail_safe.rb → wiring/fail_safe_data_store.rb} +22 -11
- data/lib/stoplight/{notifier/fail_safe.rb → wiring/fail_safe_notifier.rb} +22 -13
- data/lib/stoplight/wiring/light/default_config.rb +18 -0
- data/lib/stoplight/wiring/light/system_config.rb +11 -0
- data/lib/stoplight/wiring/light_factory.rb +188 -0
- data/lib/stoplight/wiring/public_api.rb +28 -0
- data/lib/stoplight/wiring/system_container.rb +9 -0
- data/lib/stoplight/wiring/system_light_factory.rb +17 -0
- data/lib/stoplight.rb +38 -28
- metadata +53 -43
- data/lib/stoplight/color.rb +0 -9
- data/lib/stoplight/config/dsl.rb +0 -97
- data/lib/stoplight/config/library_default_config.rb +0 -21
- data/lib/stoplight/config/system_config.rb +0 -10
- data/lib/stoplight/data_store/memory/sliding_window.rb +0 -77
- data/lib/stoplight/data_store/memory.rb +0 -285
- data/lib/stoplight/data_store/redis/lua.rb +0 -23
- data/lib/stoplight/data_store/redis.rb +0 -446
- data/lib/stoplight/data_store.rb +0 -6
- data/lib/stoplight/default.rb +0 -30
- data/lib/stoplight/error.rb +0 -39
- data/lib/stoplight/failure.rb +0 -71
- data/lib/stoplight/light/config.rb +0 -112
- data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
- data/lib/stoplight/light/green_run_strategy.rb +0 -54
- data/lib/stoplight/light/red_run_strategy.rb +0 -31
- data/lib/stoplight/light/run_strategy.rb +0 -32
- data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
- data/lib/stoplight/light.rb +0 -191
- data/lib/stoplight/metadata.rb +0 -99
- data/lib/stoplight/notifier/generic.rb +0 -79
- data/lib/stoplight/notifier/io.rb +0 -21
- data/lib/stoplight/notifier/logger.rb +0 -19
- data/lib/stoplight/state.rb +0 -9
- data/lib/stoplight/traffic_control/base.rb +0 -70
- data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
- data/lib/stoplight/traffic_control/error_rate.rb +0 -49
- data/lib/stoplight/traffic_recovery/base.rb +0 -75
- data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
- data/lib/stoplight/traffic_recovery.rb +0 -11
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/get_metadata.lua +0 -0
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_failure.lua +0 -0
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_success.lua +0 -0
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_green.lua +0 -0
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_red.lua +0 -0
- /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_yellow.lua +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Notifier
|
|
6
|
+
# The Generic module provides a reusable implementation for notifiers in Stoplight.
|
|
7
|
+
# It includes a formatter for generating notification messages and defines the `notify` method.
|
|
8
|
+
#
|
|
9
|
+
# @example Custom Notifier Implementation and Usage
|
|
10
|
+
# # Custom notifier that writes notifications to a file
|
|
11
|
+
# class FileNotifier < Stoplight::Domain::StateTransitionNotifier
|
|
12
|
+
# include Stoplight::Notifier::Generic
|
|
13
|
+
#
|
|
14
|
+
# def initialize(file_path)
|
|
15
|
+
# @file = File.open(file_path, 'a')
|
|
16
|
+
# super(@file)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# private
|
|
20
|
+
#
|
|
21
|
+
# # Writes the notification message to the file
|
|
22
|
+
# def put(message)
|
|
23
|
+
# @file.puts(message)
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# # Usage example
|
|
28
|
+
# # Create a custom notifier that writes to 'stoplight.log'
|
|
29
|
+
# notifier = FileNotifier.new('stoplight.log')
|
|
30
|
+
#
|
|
31
|
+
# # Configure Stoplight to use the custom notifier
|
|
32
|
+
# Stoplight.configure do |config|
|
|
33
|
+
# config.notifiers += [notifier]
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# # Create a stoplight and trigger a state change
|
|
37
|
+
# light = Stoplight('example-light')
|
|
38
|
+
# light.run { raise 'Simulated failure' } rescue nil
|
|
39
|
+
# light.run { raise 'Simulated failure' } rescue nil
|
|
40
|
+
# light.run { raise 'Simulated failure' } rescue nil
|
|
41
|
+
#
|
|
42
|
+
module Generic # rubocop:disable Style/Documentation
|
|
43
|
+
# @!attribute [r] formatter
|
|
44
|
+
# @return [Proc] The formatter used to generate notification messages.
|
|
45
|
+
# @see Stoplight::Default::FORMATTER
|
|
46
|
+
attr_reader :formatter
|
|
47
|
+
|
|
48
|
+
DEFAULT_FORMATTER = lambda do |light, from_color, to_color, error|
|
|
49
|
+
words = ["Switching", light.name, "from", from_color, "to", to_color]
|
|
50
|
+
words += ["because", error.class, error.message] if error
|
|
51
|
+
words.join(" ")
|
|
52
|
+
end
|
|
53
|
+
public_constant :DEFAULT_FORMATTER
|
|
54
|
+
|
|
55
|
+
# @param object [Object] The object used by the notifier (e.g., a logger or external service).
|
|
56
|
+
# @param formatter [Proc, nil] A custom formatter for generating notification messages.
|
|
57
|
+
# If no formatter is provided, the default formatter is used.
|
|
58
|
+
def initialize(object, formatter = nil)
|
|
59
|
+
@object = object
|
|
60
|
+
@formatter = formatter || DEFAULT_FORMATTER
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Sends a notification when a Stoplight changes state.
|
|
64
|
+
#
|
|
65
|
+
# @param light [Light] The Stoplight instance triggering the notification.
|
|
66
|
+
# @param from_color [String] The previous state color of the Stoplight.
|
|
67
|
+
# @param to_color [String] The new state color of the Stoplight.
|
|
68
|
+
# @param error [Exception, nil] The error (if any) that caused the state change.
|
|
69
|
+
# @return [String] The formatted notification message.
|
|
70
|
+
def notify(light, from_color, to_color, error)
|
|
71
|
+
message = formatter.call(light, from_color, to_color, error)
|
|
72
|
+
put(message)
|
|
73
|
+
message
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Processes the notification message.
|
|
79
|
+
#
|
|
80
|
+
# @param message [String] The notification message to be processed.
|
|
81
|
+
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
82
|
+
# :nocov:
|
|
83
|
+
def put(message)
|
|
84
|
+
raise NotImplementedError
|
|
85
|
+
end
|
|
86
|
+
# :nocov:
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Notifier
|
|
6
|
+
# @see Base
|
|
7
|
+
class IO < Domain::StateTransitionNotifier
|
|
8
|
+
include Generic
|
|
9
|
+
|
|
10
|
+
# @return [::IO]
|
|
11
|
+
def io
|
|
12
|
+
@object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def put(message)
|
|
18
|
+
io.puts(message)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Notifier
|
|
6
|
+
# @see Base
|
|
7
|
+
class Logger < Domain::StateTransitionNotifier
|
|
8
|
+
include Generic
|
|
9
|
+
|
|
10
|
+
# @return [::Logger]
|
|
11
|
+
def logger
|
|
12
|
+
@object
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def put(message)
|
|
16
|
+
logger.warn(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -9,7 +9,7 @@ RSpec.shared_examples "a generic notifier" do
|
|
|
9
9
|
it "is initially the default" do
|
|
10
10
|
formatter = nil
|
|
11
11
|
expect(described_class.new(nil, formatter).formatter)
|
|
12
|
-
.to eql(Stoplight::Default::FORMATTER)
|
|
12
|
+
.to eql(Stoplight::Wiring::Default::FORMATTER)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it "reads the formatter" do
|
data/lib/stoplight/version.rb
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
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
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
module Default
|
|
6
|
+
COOL_OFF_TIME = 60.0
|
|
7
|
+
|
|
8
|
+
DATA_STORE = Infrastructure::DataStore::Memory.new
|
|
9
|
+
|
|
10
|
+
ERROR_NOTIFIER = ->(error) { warn error }
|
|
11
|
+
|
|
12
|
+
FORMATTER = Infrastructure::Notifier::Generic::DEFAULT_FORMATTER
|
|
13
|
+
|
|
14
|
+
NOTIFIERS = [Infrastructure::Notifier::IO.new($stderr)].freeze
|
|
15
|
+
|
|
16
|
+
THRESHOLD = 3
|
|
17
|
+
RECOVERY_THRESHOLD = 1
|
|
18
|
+
|
|
19
|
+
WINDOW_SIZE = nil
|
|
20
|
+
|
|
21
|
+
TRACKED_ERRORS = [StandardError].freeze
|
|
22
|
+
SKIPPED_ERRORS = [].freeze
|
|
23
|
+
|
|
24
|
+
TRAFFIC_CONTROL = Domain::TrafficControl::ConsecutiveErrors.new
|
|
25
|
+
TRAFFIC_RECOVERY = Domain::TrafficRecovery::ConsecutiveSuccesses.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "forwardable"
|
|
4
|
-
|
|
5
3
|
module Stoplight
|
|
6
|
-
module
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
# This class allows users to define default settings for various Stoplight
|
|
10
|
-
# parameters, such as cool-off time, data store, error notifier, and more.
|
|
11
|
-
# TODO: add evaluation/recovery strategy support
|
|
12
|
-
class UserDefaultConfig
|
|
13
|
-
extend Forwardable
|
|
14
|
-
|
|
4
|
+
module Wiring
|
|
5
|
+
# User-facing configuration interface
|
|
6
|
+
class DefaultConfiguration
|
|
15
7
|
# @!attribute [w] cool_off_time
|
|
16
8
|
# @return [Integer, nil] The default cool-off time in seconds.
|
|
17
9
|
attr_writer :cool_off_time
|
|
18
10
|
|
|
19
|
-
# @!attribute [w] error_notifier
|
|
20
|
-
# @return [Proc, nil] The default error notifier (callable object).
|
|
21
|
-
attr_writer :error_notifier
|
|
22
|
-
|
|
23
|
-
# @!attribute [rw] notifiers
|
|
24
|
-
# @return [Array<Stoplight::Notifier::Base>] The default list of notifiers.
|
|
25
|
-
attr_accessor :notifiers
|
|
26
|
-
|
|
27
11
|
# @!attribute [w] threshold
|
|
28
12
|
# @return [Integer, Float, nil] The default failure threshold to trip the circuit breaker.
|
|
29
13
|
attr_writer :threshold
|
|
@@ -44,14 +28,26 @@ module Stoplight
|
|
|
44
28
|
# @return [Array<Class>, nil] The default list of errors to skip.
|
|
45
29
|
attr_writer :skipped_errors
|
|
46
30
|
|
|
47
|
-
# @!attribute [w]
|
|
48
|
-
# @return [
|
|
49
|
-
attr_writer :
|
|
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::Domain::DataStore] The default data store instance.
|
|
41
|
+
attr_accessor :data_store
|
|
50
42
|
|
|
51
43
|
# @!attribute [w] traffic_control
|
|
52
|
-
# @return [Stoplight::TrafficControl::Base
|
|
44
|
+
# @return [Stoplight::Domain::TrafficControl::Base] The traffic control strategy.
|
|
53
45
|
attr_writer :traffic_control
|
|
54
46
|
|
|
47
|
+
# @!attribute [w] traffic_recovery
|
|
48
|
+
# @return [Stoplight::Domain::TrafficRecovery::Base] The traffic recovery strategy.
|
|
49
|
+
attr_writer :traffic_recovery
|
|
50
|
+
|
|
55
51
|
def initialize
|
|
56
52
|
# This allows users appending notifiers to the default list,
|
|
57
53
|
# while still allowing them to override the default list.
|
|
@@ -65,21 +61,18 @@ module Stoplight
|
|
|
65
61
|
def to_h
|
|
66
62
|
{
|
|
67
63
|
cool_off_time: @cool_off_time,
|
|
68
|
-
data_store: @data_store,
|
|
69
|
-
error_notifier: @error_notifier,
|
|
70
|
-
notifiers: @notifiers,
|
|
71
64
|
threshold: @threshold,
|
|
72
65
|
recovery_threshold: @recovery_threshold,
|
|
73
66
|
window_size: @window_size,
|
|
74
67
|
tracked_errors: @tracked_errors,
|
|
75
68
|
skipped_errors: @skipped_errors,
|
|
76
|
-
|
|
69
|
+
data_store: @data_store,
|
|
70
|
+
error_notifier: @error_notifier,
|
|
71
|
+
notifiers: @notifiers,
|
|
72
|
+
traffic_control: @traffic_control,
|
|
73
|
+
traffic_recovery: @traffic_recovery
|
|
77
74
|
}.compact
|
|
78
75
|
end
|
|
79
|
-
|
|
80
|
-
# @return [Boolean] True if the configuration hash is not empty, false otherwise.
|
|
81
|
-
# @api private
|
|
82
|
-
def_delegator :to_h, :any?
|
|
83
76
|
end
|
|
84
77
|
end
|
|
85
78
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
# Builds the default LightFactory from user-provided configuration which is
|
|
6
|
+
# used as the basis for all circuit breakers.
|
|
7
|
+
#
|
|
8
|
+
class DefaultFactoryBuilder
|
|
9
|
+
# @!attribute [r] configuration
|
|
10
|
+
# @return [Stoplight::Wiring::DefaultConfiguration]
|
|
11
|
+
#
|
|
12
|
+
attr_reader :configuration
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@configuration = DefaultConfiguration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Stoplight::Wiring::LightFactory]
|
|
19
|
+
# @api private the method is used internally by Stoplight
|
|
20
|
+
def build
|
|
21
|
+
LightFactory.new(Wiring::Container).with(**configuration.to_h)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|