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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/stoplight/admin/views/layout.erb +3 -3
  4. data/lib/stoplight/admin.rb +4 -4
  5. data/lib/stoplight/domain/color.rb +11 -0
  6. data/lib/stoplight/{config → domain}/compatibility_result.rb +1 -1
  7. data/lib/stoplight/domain/config.rb +55 -0
  8. data/lib/stoplight/{data_store/base.rb → domain/data_store.rb} +17 -15
  9. data/lib/stoplight/domain/error.rb +42 -0
  10. data/lib/stoplight/domain/failure.rb +42 -0
  11. data/lib/stoplight/domain/light/configuration_builder_interface.rb +130 -0
  12. data/lib/stoplight/domain/light.rb +198 -0
  13. data/lib/stoplight/domain/light_factory.rb +75 -0
  14. data/lib/stoplight/domain/metadata.rb +65 -0
  15. data/lib/stoplight/domain/state.rb +11 -0
  16. data/lib/stoplight/{notifier/base.rb → domain/state_transition_notifier.rb} +5 -4
  17. data/lib/stoplight/domain/strategies/green_run_strategy.rb +69 -0
  18. data/lib/stoplight/domain/strategies/red_run_strategy.rb +41 -0
  19. data/lib/stoplight/domain/strategies/run_strategy.rb +27 -0
  20. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +98 -0
  21. data/lib/stoplight/domain/tracker/base.rb +41 -0
  22. data/lib/stoplight/domain/tracker/recovery_probe.rb +72 -0
  23. data/lib/stoplight/domain/tracker/request.rb +67 -0
  24. data/lib/stoplight/domain/traffic_control/base.rb +74 -0
  25. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +57 -0
  26. data/lib/stoplight/domain/traffic_control/error_rate.rb +51 -0
  27. data/lib/stoplight/domain/traffic_recovery/base.rb +79 -0
  28. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +70 -0
  29. data/lib/stoplight/domain/traffic_recovery.rb +13 -0
  30. data/lib/stoplight/infrastructure/data_store/memory/sliding_window.rb +79 -0
  31. data/lib/stoplight/infrastructure/data_store/memory.rb +307 -0
  32. data/lib/stoplight/infrastructure/data_store/redis/lua.rb +25 -0
  33. data/lib/stoplight/infrastructure/data_store/redis.rb +478 -0
  34. data/lib/stoplight/infrastructure/dependency_injection/container.rb +249 -0
  35. data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +13 -0
  36. data/lib/stoplight/infrastructure/notifier/generic.rb +90 -0
  37. data/lib/stoplight/infrastructure/notifier/io.rb +23 -0
  38. data/lib/stoplight/infrastructure/notifier/logger.rb +21 -0
  39. data/lib/stoplight/rspec/generic_notifier.rb +1 -1
  40. data/lib/stoplight/version.rb +1 -1
  41. data/lib/stoplight/wiring/container.rb +80 -0
  42. data/lib/stoplight/wiring/default.rb +28 -0
  43. data/lib/stoplight/{config/user_default_config.rb → wiring/default_configuration.rb} +24 -31
  44. data/lib/stoplight/wiring/default_factory_builder.rb +25 -0
  45. data/lib/stoplight/{data_store/fail_safe.rb → wiring/fail_safe_data_store.rb} +22 -11
  46. data/lib/stoplight/{notifier/fail_safe.rb → wiring/fail_safe_notifier.rb} +22 -13
  47. data/lib/stoplight/wiring/light/default_config.rb +18 -0
  48. data/lib/stoplight/wiring/light/system_config.rb +11 -0
  49. data/lib/stoplight/wiring/light_factory.rb +188 -0
  50. data/lib/stoplight/wiring/public_api.rb +28 -0
  51. data/lib/stoplight/wiring/system_container.rb +9 -0
  52. data/lib/stoplight/wiring/system_light_factory.rb +17 -0
  53. data/lib/stoplight.rb +38 -28
  54. metadata +53 -43
  55. data/lib/stoplight/color.rb +0 -9
  56. data/lib/stoplight/config/dsl.rb +0 -97
  57. data/lib/stoplight/config/library_default_config.rb +0 -21
  58. data/lib/stoplight/config/system_config.rb +0 -10
  59. data/lib/stoplight/data_store/memory/sliding_window.rb +0 -77
  60. data/lib/stoplight/data_store/memory.rb +0 -285
  61. data/lib/stoplight/data_store/redis/lua.rb +0 -23
  62. data/lib/stoplight/data_store/redis.rb +0 -446
  63. data/lib/stoplight/data_store.rb +0 -6
  64. data/lib/stoplight/default.rb +0 -30
  65. data/lib/stoplight/error.rb +0 -39
  66. data/lib/stoplight/failure.rb +0 -71
  67. data/lib/stoplight/light/config.rb +0 -112
  68. data/lib/stoplight/light/configuration_builder_interface.rb +0 -128
  69. data/lib/stoplight/light/green_run_strategy.rb +0 -54
  70. data/lib/stoplight/light/red_run_strategy.rb +0 -31
  71. data/lib/stoplight/light/run_strategy.rb +0 -32
  72. data/lib/stoplight/light/yellow_run_strategy.rb +0 -94
  73. data/lib/stoplight/light.rb +0 -191
  74. data/lib/stoplight/metadata.rb +0 -99
  75. data/lib/stoplight/notifier/generic.rb +0 -79
  76. data/lib/stoplight/notifier/io.rb +0 -21
  77. data/lib/stoplight/notifier/logger.rb +0 -19
  78. data/lib/stoplight/state.rb +0 -9
  79. data/lib/stoplight/traffic_control/base.rb +0 -70
  80. data/lib/stoplight/traffic_control/consecutive_errors.rb +0 -55
  81. data/lib/stoplight/traffic_control/error_rate.rb +0 -49
  82. data/lib/stoplight/traffic_recovery/base.rb +0 -75
  83. data/lib/stoplight/traffic_recovery/consecutive_successes.rb +0 -68
  84. data/lib/stoplight/traffic_recovery.rb +0 -11
  85. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/get_metadata.lua +0 -0
  86. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_failure.lua +0 -0
  87. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/record_success.lua +0 -0
  88. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_green.lua +0 -0
  89. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_red.lua +0 -0
  90. /data/lib/stoplight/{data_store → infrastructure/data_store}/redis/transition_to_yellow.lua +0 -0
@@ -3,32 +3,41 @@
3
3
  require "securerandom"
4
4
 
5
5
  module Stoplight
6
- module DataStore
6
+ module Wiring
7
7
  # A wrapper around a data store that provides fail-safe mechanisms using a
8
8
  # circuit breaker. It ensures that operations on the data store can gracefully
9
9
  # handle failures by falling back to default values when necessary.
10
10
  #
11
11
  # @api private
12
- class FailSafe < Base
12
+ class FailSafeDataStore < Domain::DataStore
13
13
  class << self
14
14
  # Wraps a data store with fail-safe mechanisms.
15
15
  #
16
16
  # @param data_store [Stoplight::DataStore::Base] The data store to wrap.
17
+ # @param error_notifier [Proc] called when wrapped data store fails
17
18
  # @return [Stoplight::DataStore::Base, FailSafe] The original data store if it is already
18
19
  # a +Memory+ or +FailSafe+ instance, otherwise a new +FailSafe+ instance.
19
- def wrap(data_store)
20
+ def wrap(data_store:, error_notifier:)
20
21
  case data_store
21
- when Memory, FailSafe
22
+ in Infrastructure::DataStore::Memory
22
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:)
23
28
  else
24
- new(data_store)
29
+ new(data_store:, error_notifier:)
25
30
  end
26
31
  end
27
32
  end
28
33
 
29
34
  # @!attribute data_store
30
35
  # @return [Stoplight::DataStore::Base] The underlying primary data store being used
31
- protected attr_reader :data_store
36
+ attr_reader :data_store
37
+
38
+ # @!attribute error_notifier
39
+ # @return [Proc]
40
+ attr_reader :error_notifier
32
41
 
33
42
  # @!attribute failover_data_store
34
43
  # @return [Stoplight::DataStore::Base] The fallback data store used when the primary fails.
@@ -38,11 +47,13 @@ module Stoplight
38
47
  # @return [Stoplight::Light] The circuit breaker used to handle data store failures.
39
48
  private attr_reader :circuit_breaker
40
49
 
41
- # @param data_store [Stoplight::DataStore::Base]
42
- def initialize(data_store, failover_data_store: Default::DATA_STORE)
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)
43
53
  @data_store = data_store
54
+ @error_notifier = error_notifier
44
55
  @failover_data_store = failover_data_store
45
- @circuit_breaker = Stoplight.system_light("stoplight:data_store:fail_safe:#{data_store.class.name}")
56
+ @circuit_breaker = Stoplight.system_light("data_store:fail_safe:#{data_store.class.name}")
46
57
  end
47
58
 
48
59
  def names
@@ -94,14 +105,14 @@ module Stoplight
94
105
  end
95
106
 
96
107
  def ==(other)
97
- other.is_a?(self.class) && other.data_store == data_store
108
+ other.is_a?(self.class) && other.data_store == data_store && other.error_notifier == error_notifier
98
109
  end
99
110
 
100
111
  # @param method_name [Symbol] protected method name
101
112
  private def with_fallback(method_name, *args, **kwargs, &code)
102
113
  fallback = proc do |error|
103
114
  config = args.first
104
- config.error_notifier.call(error) if config && error
115
+ error_notifier.call(error) if config && error
105
116
  @failover_data_store.public_send(method_name, *args, **kwargs)
106
117
  end
107
118
 
@@ -1,50 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stoplight
4
- module Notifier
4
+ module Wiring
5
5
  # A wrapper around a notifier that provides fail-safe mechanisms using a
6
6
  # circuit breaker. It ensures that a notification can gracefully
7
7
  # handle failures.
8
8
  #
9
9
  # @api private
10
- class FailSafe < Base
10
+ class FailSafeNotifier < Domain::StateTransitionNotifier
11
11
  # @!attribute [r] notifier
12
- # @return [Stoplight::Notifier::Base] The underlying notifier being wrapped.
13
- protected attr_reader :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
14
18
 
15
19
  class << self
16
20
  # Wraps a notifier with fail-safe mechanisms.
17
21
  #
18
- # @param notifier [Stoplight::Notifier::Base] The notifier to wrap.
22
+ # @param notifier [Stoplight::Domain::StateTransitionNotifier] The notifier to wrap.
23
+ # @param error_notifier [Proc] called when wrapped data store fails
19
24
  # @return [Stoplight::Notifier::FailSafe] The original notifier if it is already
20
25
  # a +FailSafe+ instance, otherwise a new +FailSafe+ instance.
21
- def wrap(notifier)
26
+ def wrap(notifier:, error_notifier:)
22
27
  case notifier
23
- when FailSafe
28
+ in self if notifier.error_notifier == error_notifier
24
29
  notifier
30
+ in self
31
+ new(notifier: notifier.notifier, error_notifier:)
25
32
  else
26
- new(notifier)
33
+ new(notifier:, error_notifier:)
27
34
  end
28
35
  end
29
36
  end
30
37
 
31
38
  # Initializes a new instance of the +FailSafe+ class.
32
39
  #
33
- # @param notifier [Stoplight::Notifier::Base] The notifier to wrap.
34
- def initialize(notifier)
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:)
35
43
  @notifier = notifier
44
+ @error_notifier = error_notifier
36
45
  end
37
46
 
38
47
  # Sends a notification using the wrapped notifier with fail-safe mechanisms.
39
48
  #
40
- # @param config [Stoplight::Light::Config] The light configuration.
49
+ # @param config [Stoplight::Domain::Config] The light configuration.
41
50
  # @param from_color [String] The initial color of the light.
42
51
  # @param to_color [String] The target color of the light.
43
52
  # @param error [Exception, nil] An optional error to include in the notification.
44
53
  # @return [void]
45
54
  def notify(config, from_color, to_color, error = nil)
46
55
  fallback = proc do |exception|
47
- config.error_notifier.call(exception) if exception
56
+ error_notifier.call(exception) if exception
48
57
  nil
49
58
  end
50
59
 
@@ -55,7 +64,7 @@ module Stoplight
55
64
 
56
65
  # @return [Boolean]
57
66
  def ==(other)
58
- other.is_a?(FailSafe) && notifier == other.notifier
67
+ other.is_a?(self.class) && notifier == other.notifier
59
68
  end
60
69
 
61
70
  # @return [Stoplight::Light] The circuit breaker used to handle failures.
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ module Light
6
+ # Provides default settings for the Stoplight library.
7
+ # @api private
8
+ DefaultConfig = Domain::Config.empty.with(
9
+ cool_off_time: Default::COOL_OFF_TIME,
10
+ threshold: Default::THRESHOLD,
11
+ recovery_threshold: Default::RECOVERY_THRESHOLD,
12
+ window_size: Default::WINDOW_SIZE,
13
+ tracked_errors: Default::TRACKED_ERRORS,
14
+ skipped_errors: Default::SKIPPED_ERRORS
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ module Light
6
+ SystemConfig = DefaultConfig.with(
7
+ recovery_threshold: 3
8
+ )
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ # Concrete factory for building +Stoplight::Light++ instances with full dependency wiring.
6
+ #
7
+ # This factory implements the +Stoplight::Domain::LightFactory+ protocol. It knows how to:
8
+ # 1. Parse and transform user-provided settings
9
+ # 2. Wire together all Light dependencies using a DI container
10
+ # 3. Validate configuration compatibility
11
+ # 4. Construct fully-functional Light instances
12
+ #
13
+ # @see Stoplight::Domain::LightFactory
14
+ # @see Stoplight()
15
+ # @api private
16
+
17
+ class LightFactory < Domain::LightFactory
18
+ # @!attribute [r] container
19
+ # The dependency injection container holding all component configurations.
20
+ # Contains config, data_store, notifiers, strategies, etc.
21
+ # @return [Stoplight::Wiring::Container]
22
+ protected attr_reader :container
23
+
24
+ # @param container [Stoplight::Wiring::Container]
25
+ def initialize(container)
26
+ @container = container
27
+ end
28
+
29
+ # @param settings [Hash] Settings to override in the new factory
30
+ # @see Stoplight()
31
+ # @return [Stoplight::Wiring::LightFactory]
32
+ # @see Stoplight()
33
+ def with(**settings)
34
+ transformed_settings = transform_settings(settings)
35
+ config_settings = extract_config_settings(transformed_settings)
36
+ dependency_settings = extract_dependency_settings(transformed_settings)
37
+
38
+ validate_settings!(transformed_settings, config_settings, dependency_settings)
39
+
40
+ new_config = container.resolve(:config).with(**config_settings)
41
+ new_container = container.with(config: new_config, **dependency_settings)
42
+
43
+ self.class.new(new_container)
44
+ end
45
+
46
+ # Builds a fully-configured Light instance.
47
+ #
48
+ # The method resolves all dependencies from the container and constructs a Light that's
49
+ # ready to use. The Light is injected with a reference to this factory, allowing the
50
+ # +Stoplight::Light#with+ method to work for reconfiguration.
51
+ #
52
+ # @return [Stoplight::Light] Configured circuit breaker
53
+ # @raise [Stoplight::Error::ConfigurationError] If configuration is invalid
54
+ #
55
+ # @example
56
+ # factory = Stoplight::Wiring::LightFactory.new(container)
57
+ # light = factory.build
58
+ #
59
+ # # Light is ready to use
60
+ # light.run { api_call }
61
+
62
+ def build
63
+ validate!
64
+
65
+ Stoplight::Domain::Light.new(
66
+ container.resolve(:config),
67
+ data_store: container.resolve(:data_store),
68
+ green_run_strategy: container.resolve(:green_run_strategy),
69
+ yellow_run_strategy: container.resolve(:yellow_run_strategy),
70
+ red_run_strategy: container.resolve(:red_run_strategy),
71
+ factory: self
72
+ )
73
+ end
74
+
75
+ def ==(other)
76
+ other.is_a?(self.class) && other.container == container
77
+ end
78
+
79
+ private def extract_config_settings(settings)
80
+ settings.slice(*container.resolve(:config).members)
81
+ end
82
+
83
+ private def extract_dependency_settings(settings)
84
+ settings.slice(*container.keys)
85
+ end
86
+
87
+ private def validate_settings!(settings, config_settings, dependency_settings)
88
+ recognized_keys = config_settings.keys + dependency_settings.keys
89
+ unexpected_keys = settings.keys - recognized_keys
90
+
91
+ return if unexpected_keys.empty?
92
+ raise ArgumentError, "Unknown settings: #{unexpected_keys.join(", ")}"
93
+ end
94
+
95
+ private def transform_settings(settings)
96
+ settings.dup.tap do |transformed_settings|
97
+ transform_config_settings!(transformed_settings)
98
+ transform_dependencies_settings!(transformed_settings)
99
+ end
100
+ end
101
+
102
+ private def transform_config_settings!(settings)
103
+ if settings.key?(:tracked_errors)
104
+ settings[:tracked_errors] = normalize_array(settings[:tracked_errors])
105
+ end
106
+
107
+ if settings.key?(:skipped_errors)
108
+ settings[:skipped_errors] = normalize_array(settings[:skipped_errors])
109
+ end
110
+
111
+ if settings.key?(:cool_off_time)
112
+ settings[:cool_off_time] = normalize_cool_off_time(settings[:cool_off_time])
113
+ end
114
+ end
115
+
116
+ private def transform_dependencies_settings!(settings)
117
+ if settings.key?(:traffic_control)
118
+ settings[:traffic_control] = apply_traffic_control_dsl(settings[:traffic_control])
119
+ end
120
+
121
+ if settings.key?(:traffic_recovery)
122
+ settings[:traffic_recovery] = apply_traffic_recovery_dsl(settings[:traffic_recovery])
123
+ end
124
+ end
125
+
126
+ private def normalize_array(value) = Array(value)
127
+ private def normalize_cool_off_time(value) = value.to_i
128
+
129
+ private def apply_traffic_control_dsl(traffic_control)
130
+ case traffic_control
131
+ in Domain::TrafficControl::Base
132
+ traffic_control
133
+ in :consecutive_errors
134
+ Domain::TrafficControl::ConsecutiveErrors.new
135
+ in :error_rate
136
+ Domain::TrafficControl::ErrorRate.new
137
+ in {error_rate: error_rate_settings}
138
+ Domain::TrafficControl::ErrorRate.new(**error_rate_settings)
139
+ else
140
+ raise Domain::Error::ConfigurationError, <<~ERROR
141
+ unsupported traffic_control strategy provided (`#{traffic_control}`). Supported options:
142
+ * :consecutive_errors
143
+ * :error_rate
144
+ ERROR
145
+ end
146
+ end
147
+
148
+ def apply_traffic_recovery_dsl(traffic_recovery)
149
+ case traffic_recovery
150
+ in Domain::TrafficRecovery::Base
151
+ traffic_recovery
152
+ in :consecutive_successes
153
+ Domain::TrafficRecovery::ConsecutiveSuccesses.new
154
+ else
155
+ raise Domain::Error::ConfigurationError, <<~ERROR
156
+ unsupported traffic_recovery strategy provided (`#{traffic_recovery}`). Supported options:
157
+ * :consecutive_successes
158
+ ERROR
159
+ end
160
+ end
161
+
162
+ private def validate!
163
+ validate_traffic_control!(container.resolve(:traffic_control), container.resolve(:config))
164
+ validate_traffic_recovery!(container.resolve(:traffic_recovery), container.resolve(:config))
165
+ end
166
+
167
+ private def validate_traffic_control!(traffic_control, config)
168
+ traffic_control.check_compatibility(config).then do |compatibility_result|
169
+ if compatibility_result.incompatible?
170
+ raise Domain::Error::ConfigurationError.new(
171
+ "#{traffic_control.class.name} strategy is incompatible with the Stoplight configuration: #{compatibility_result.error_messages}"
172
+ )
173
+ end
174
+ end
175
+ end
176
+
177
+ private def validate_traffic_recovery!(traffic_recovery, config)
178
+ traffic_recovery.check_compatibility(config).then do |compatibility_result|
179
+ if compatibility_result.incompatible?
180
+ raise Domain::Error::ConfigurationError.new(
181
+ "#{traffic_recovery.class.name} strategy is incompatible with the Stoplight configuration: #{compatibility_result.error_messages}"
182
+ )
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Wiring
5
+ # Public API facade for backward compatibility and convenience
6
+ # @api public
7
+ module PublicApi
8
+ # Aliases for domain concepts
9
+ Color = Domain::Color
10
+ Error = Domain::Error
11
+ State = Domain::State
12
+
13
+ # Namespace aliases for data stores
14
+ module DataStore
15
+ Redis = Infrastructure::DataStore::Redis
16
+ Memory = Infrastructure::DataStore::Memory
17
+ end
18
+
19
+ # Namespace aliases for notifiers
20
+ module Notifier
21
+ Base = Domain::StateTransitionNotifier
22
+ Generic = Infrastructure::Notifier::Generic
23
+ IO = Infrastructure::Notifier::IO
24
+ Logger = Infrastructure::Notifier::Logger
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
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
@@ -0,0 +1,17 @@
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
data/lib/stoplight.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "zeitwerk"
4
4
 
5
5
  loader = Zeitwerk::Loader.for_gem
6
- loader.inflector.inflect("io" => "IO", "dsl" => "DSL")
6
+ loader.inflector.inflect("io" => "IO")
7
7
  loader.do_not_eager_load(
8
8
  "#{__dir__}/stoplight/data_store",
9
9
  "#{__dir__}/stoplight/admin",
@@ -14,24 +14,19 @@ loader.ignore("#{__dir__}/stoplight/rspec.rb", "#{__dir__}/stoplight/rspec")
14
14
  loader.setup
15
15
 
16
16
  module Stoplight # rubocop:disable Style/Documentation
17
- CONFIG_DSL = Config::DSL.new
18
- private_constant :CONFIG_DSL
17
+ include Wiring::PublicApi
19
18
 
20
19
  CONFIG_MUTEX = Mutex.new
21
20
  private_constant :CONFIG_MUTEX
22
21
 
23
22
  class << self
24
- ALREADY_CONFIGURED_WARNING = "Stoplight must be configured only once"
25
- private_constant :ALREADY_CONFIGURED_WARNING
26
-
27
23
  # Configures the Stoplight library.
28
24
  #
29
25
  # This method allows you to set up the library's configuration using a block.
30
26
  # It raises an error if called more than once.
31
27
  #
32
28
  # @yield [config] Provides a configuration object to the block.
33
- # @yieldparam config [Stoplight::Config::UserDefaultConfig] The configuration object.
34
- # @raise [Stoplight::Error::ConfigurationError] If the library is already configured.
29
+ # @yieldparam config [Stoplight::Wiring::DefaultConfiguration] The configuration object.
35
30
  # @return [void]
36
31
  #
37
32
  # @example
@@ -56,18 +51,12 @@ module Stoplight # rubocop:disable Style/Documentation
56
51
  # suppress this warning, which could be useful in test environments.
57
52
  #
58
53
  def configure(trust_me_im_an_engineer: false)
59
- user_defaults = Config::UserDefaultConfig.new
60
- yield(user_defaults) if block_given?
61
-
62
- reconfigured = !@default_config.nil?
54
+ warn_if_reconfiguring(trust_me_im_an_engineer) do
55
+ factory_builder = Wiring::DefaultFactoryBuilder.new
56
+ yield factory_builder.configuration if block_given?
63
57
 
64
- @default_config = Config::LibraryDefaultConfig.with(**user_defaults.to_h).tap do
65
- if reconfigured && !trust_me_im_an_engineer
66
- warn(
67
- "Stoplight reconfigured. Existing circuit breakers will not see new configuration. " \
68
- "New configuration: #{@default_config.inspect}"
69
- )
70
- end
58
+ @default_configuration = factory_builder.configuration
59
+ @default_light_factory = factory_builder.build
71
60
  end
72
61
  end
73
62
 
@@ -78,8 +67,7 @@ module Stoplight # rubocop:disable Style/Documentation
78
67
  # @return [Stoplight::Light]
79
68
  # @api private
80
69
  def system_light(name, **settings)
81
- config = Config::SystemConfig.with(name:, **settings)
82
- Stoplight::Light.new(config)
70
+ Wiring::SystemLightFactory.build_with(name: "__stoplight__#{name}", **settings)
83
71
  end
84
72
 
85
73
  # Create a Light with the user default configuration.
@@ -89,18 +77,40 @@ module Stoplight # rubocop:disable Style/Documentation
89
77
  # @return [Stoplight::Light]
90
78
  # @api private
91
79
  def light(name, **settings)
92
- config = Stoplight.default_config.with(name:, **settings)
93
- Stoplight::Light.new(config)
80
+ __stoplight__default_light_factory.build_with(name:, **settings)
94
81
  end
95
82
 
96
- # Retrieves the current configuration provider.
83
+ # Retrieves the current default dependencies.
97
84
  #
98
- # @return [Stoplight::Light::Config]
85
+ # @return [Stoplight::Domain::LightFactory]
86
+ # @api private
87
+ def __stoplight__default_light_factory
88
+ ensure_configured
89
+ @default_light_factory
90
+ end
91
+
92
+ def __stoplight__default_configuration
93
+ ensure_configured
94
+ @default_configuration
95
+ end
96
+
99
97
  # @api private
100
- def default_config
98
+ private def ensure_configured
101
99
  CONFIG_MUTEX.synchronize do
102
- @default_config ||= configure
100
+ configure unless configured?
101
+ end
102
+ end
103
+
104
+ private def warn_if_reconfiguring(trust_me_im_an_engineer)
105
+ if configured? && !trust_me_im_an_engineer
106
+ warn "Stoplight reconfigured. Existing circuit breakers will not see new configuration"
103
107
  end
108
+ yield
109
+ @configured = true
110
+ end
111
+
112
+ private def configured?
113
+ @configured == true
104
114
  end
105
115
  end
106
116
  end
@@ -117,7 +127,7 @@ end
117
127
  # @option settings [Numeric] :window_size The size of the rolling window for failure tracking.
118
128
  # @option settings [Array<StandardError>] :tracked_errors A list of errors to track.
119
129
  # @option settings [Array<Exception>] :skipped_errors A list of errors to skip.
120
- # @option settings [Stoplight::TrafficControl::Base, Symbol, {Symbol, Hash{Symbol, any}}] :traffic_control The
130
+ # @option settings [Symbol, {Symbol, Hash{Symbol, any}}] :traffic_control The
121
131
  # traffic control strategy to use.
122
132
  #
123
133
  # @return [Stoplight::Light] A new circuit breaker instance.