stoplight 5.3.8 → 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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -1
  3. data/lib/generators/stoplight/install/templates/stoplight.rb.erb +2 -0
  4. data/lib/stoplight/admin/views/layout.erb +3 -3
  5. data/lib/stoplight/admin.rb +4 -4
  6. data/lib/stoplight/domain/color.rb +11 -0
  7. data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
  8. data/lib/stoplight/domain/config.rb +55 -0
  9. data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +17 -15
  10. data/lib/stoplight/domain/error.rb +42 -0
  11. data/lib/stoplight/domain/failure.rb +42 -0
  12. data/lib/stoplight/domain/light/configuration_builder_interface.rb +130 -0
  13. data/lib/stoplight/domain/light.rb +198 -0
  14. data/lib/stoplight/domain/light_factory.rb +75 -0
  15. data/lib/stoplight/domain/metadata.rb +65 -0
  16. data/lib/stoplight/domain/state.rb +11 -0
  17. data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
  18. data/lib/stoplight/domain/strategies/green_run_strategy.rb +69 -0
  19. data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
  20. data/lib/stoplight/domain/strategies/run_strategy.rb +27 -0
  21. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +98 -0
  22. data/lib/stoplight/domain/tracker/base.rb +41 -0
  23. data/lib/stoplight/domain/tracker/recovery_probe.rb +72 -0
  24. data/lib/stoplight/domain/tracker/request.rb +67 -0
  25. data/lib/stoplight/domain/traffic_control/base.rb +74 -0
  26. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +57 -0
  27. data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
  28. data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
  29. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +70 -0
  30. data/lib/stoplight/domain/traffic_recovery.rb +13 -0
  31. data/lib/stoplight/infrastructure/data_store/memory/sliding_window.rb +79 -0
  32. data/lib/stoplight/infrastructure/data_store/memory.rb +307 -0
  33. data/lib/stoplight/infrastructure/data_store/redis/lua.rb +25 -0
  34. data/lib/stoplight/infrastructure/data_store/redis.rb +478 -0
  35. data/lib/stoplight/infrastructure/dependency_injection/container.rb +249 -0
  36. data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +13 -0
  37. data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
  38. data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
  39. data/lib/stoplight/infrastructure/notifier/logger.rb +21 -0
  40. data/lib/stoplight/rspec/generic_notifier.rb +1 -1
  41. data/lib/stoplight/version.rb +1 -1
  42. data/lib/stoplight/wiring/container.rb +80 -0
  43. data/lib/stoplight/wiring/default.rb +28 -0
  44. data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
  45. data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
  46. data/lib/stoplight/wiring/fail_safe_data_store.rb +123 -0
  47. data/lib/stoplight/{notifier/fail_safe.rb → wiring/fail_safe_notifier.rb} +22 -13
  48. data/lib/stoplight/wiring/light/default_config.rb +18 -0
  49. data/lib/stoplight/wiring/light/system_config.rb +11 -0
  50. data/lib/stoplight/wiring/light_factory.rb +188 -0
  51. data/lib/stoplight/wiring/public_api.rb +28 -0
  52. data/lib/stoplight/wiring/system_container.rb +9 -0
  53. data/lib/stoplight/wiring/system_light_factory.rb +17 -0
  54. data/lib/stoplight.rb +38 -28
  55. metadata +53 -42
  56. data/lib/stoplight/color.rb +0 -9
  57. data/lib/stoplight/config/dsl.rb +0 -97
  58. data/lib/stoplight/config/library_default_config.rb +0 -21
  59. data/lib/stoplight/config/system_config.rb +0 -7
  60. data/lib/stoplight/data_store/fail_safe.rb +0 -113
  61. data/lib/stoplight/data_store/memory.rb +0 -311
  62. data/lib/stoplight/data_store/redis/lua.rb +0 -23
  63. data/lib/stoplight/data_store/redis.rb +0 -449
  64. data/lib/stoplight/data_store.rb +0 -6
  65. data/lib/stoplight/default.rb +0 -30
  66. data/lib/stoplight/error.rb +0 -10
  67. data/lib/stoplight/failure.rb +0 -71
  68. data/lib/stoplight/light/config.rb +0 -111
  69. data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
  70. data/lib/stoplight/light/green_run_strategy.rb +0 -54
  71. data/lib/stoplight/light/red_run_strategy.rb +0 -27
  72. data/lib/stoplight/light/run_strategy.rb +0 -32
  73. data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
  74. data/lib/stoplight/light.rb +0 -191
  75. data/lib/stoplight/metadata.rb +0 -99
  76. data/lib/stoplight/notifier/generic.rb +0 -79
  77. data/lib/stoplight/notifier/io.rb +0 -21
  78. data/lib/stoplight/notifier/logger.rb +0 -19
  79. data/lib/stoplight/state.rb +0 -9
  80. data/lib/stoplight/traffic_control/base.rb +0 -70
  81. data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
  82. data/lib/stoplight/traffic_control/error_rate.rb +0 -49
  83. data/lib/stoplight/traffic_recovery/base.rb +0 -75
  84. data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
  85. data/lib/stoplight/traffic_recovery.rb +0 -11
  86. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/get_metadata.lua +0 -0
  87. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_failure.lua +0 -0
  88. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_success.lua +0 -0
  89. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_green.lua +0 -0
  90. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_red.lua +0 -0
  91. /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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stoplight
4
- VERSION = Gem::Version.new("5.3.8")
4
+ VERSION = Gem::Version.new("5.5.0")
5
5
  end
@@ -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 Config
7
- # Represents user-defined default configuration for Stoplight.
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] data_store
48
- # @return [Stoplight::DataStore::Base] The default data store instance.
49
- attr_writer :data_store
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, Symbol, Hash] The traffic control strategy.
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
- traffic_control: @traffic_control
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