stoplight 5.6.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.
- checksums.yaml +4 -4
- data/lib/stoplight/admin/dependencies.rb +1 -1
- data/lib/stoplight/admin/helpers.rb +10 -5
- data/lib/stoplight/admin/lights_repository.rb +18 -15
- data/lib/stoplight/admin.rb +2 -1
- data/lib/stoplight/common/deprecations.rb +11 -0
- data/lib/stoplight/domain/config.rb +5 -1
- data/lib/stoplight/domain/data_store.rb +17 -1
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +120 -16
- data/lib/stoplight/domain/light.rb +31 -20
- data/lib/stoplight/domain/metrics.rb +6 -27
- data/lib/stoplight/domain/recovery_lock_token.rb +15 -0
- data/lib/stoplight/domain/storage/metrics.rb +42 -0
- data/lib/stoplight/domain/storage/recovery_lock.rb +56 -0
- data/lib/stoplight/domain/storage/state.rb +87 -0
- data/lib/stoplight/domain/strategies/run_strategy.rb +0 -5
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +58 -32
- data/lib/stoplight/domain/tracker/base.rb +0 -29
- data/lib/stoplight/domain/tracker/recovery_probe.rb +23 -22
- data/lib/stoplight/domain/tracker/request.rb +23 -19
- data/lib/stoplight/domain/traffic_recovery/base.rb +1 -2
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +2 -8
- data/lib/stoplight/domain/traffic_recovery.rb +0 -1
- data/lib/stoplight/infrastructure/data_store/fail_safe.rb +164 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_store.rb +54 -0
- data/lib/stoplight/infrastructure/data_store/memory/recovery_lock_token.rb +20 -0
- data/lib/stoplight/infrastructure/data_store/memory.rb +61 -32
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_failure.lua +27 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/record_recovery_probe_success.lua +23 -0
- data/lib/stoplight/infrastructure/data_store/redis/lua_scripts/release_lock.lua +6 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_store.rb +73 -0
- data/lib/stoplight/infrastructure/data_store/redis/recovery_lock_token.rb +35 -0
- data/lib/stoplight/infrastructure/data_store/redis/scripting.rb +71 -0
- data/lib/stoplight/infrastructure/data_store/redis.rb +133 -162
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +62 -0
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +48 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +36 -0
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +55 -0
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +55 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/data_store/base.rb +11 -0
- data/lib/stoplight/wiring/data_store/memory.rb +10 -0
- data/lib/stoplight/wiring/data_store/redis.rb +25 -0
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_configuration.rb +1 -1
- data/lib/stoplight/wiring/default_factory_builder.rb +1 -1
- data/lib/stoplight/wiring/light_builder.rb +185 -0
- data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +55 -0
- data/lib/stoplight/wiring/light_factory/config_normalizer.rb +71 -0
- data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +72 -0
- data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +26 -0
- data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +21 -0
- data/lib/stoplight/wiring/light_factory.rb +45 -132
- data/lib/stoplight/wiring/notifier_factory.rb +26 -0
- data/lib/stoplight/wiring/public_api.rb +3 -2
- data/lib/stoplight.rb +18 -3
- metadata +50 -15
- data/lib/stoplight/infrastructure/data_store/redis/lua.rb +0 -25
- data/lib/stoplight/infrastructure/dependency_injection/container.rb +0 -249
- data/lib/stoplight/infrastructure/dependency_injection/unresolved_dependency_error.rb +0 -13
- data/lib/stoplight/wiring/container.rb +0 -80
- data/lib/stoplight/wiring/fail_safe_data_store.rb +0 -147
- data/lib/stoplight/wiring/fail_safe_notifier.rb +0 -79
- data/lib/stoplight/wiring/system_container.rb +0 -9
- data/lib/stoplight/wiring/system_light_factory.rb +0 -17
- /data/lib/stoplight/infrastructure/data_store/redis/{get_metrics.lua → lua_scripts/get_metrics.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{record_failure.lua → lua_scripts/record_failure.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{record_success.lua → lua_scripts/record_success.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_green.lua → lua_scripts/transition_to_green.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_red.lua → lua_scripts/transition_to_red.lua} +0 -0
- /data/lib/stoplight/infrastructure/data_store/redis/{transition_to_yellow.lua → lua_scripts/transition_to_yellow.lua} +0 -0
data/lib/stoplight/version.rb
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
module DataStore
|
|
6
|
+
class Redis < Base
|
|
7
|
+
# @!attribute redis
|
|
8
|
+
# @return [::Redis, ConnectionPool<::Redis>]
|
|
9
|
+
attr_reader :redis
|
|
10
|
+
|
|
11
|
+
# @!attribute warn_on_clock_skew
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
attr_reader :warn_on_clock_skew
|
|
14
|
+
|
|
15
|
+
# @param redis [::Redis, ConnectionPool<::Redis>]
|
|
16
|
+
# @param warn_on_clock_skew [Boolean] (true) Whether to warn about clock skew between Redis and
|
|
17
|
+
# the application server
|
|
18
|
+
def initialize(redis, warn_on_clock_skew: true)
|
|
19
|
+
@warn_on_clock_skew = warn_on_clock_skew
|
|
20
|
+
@redis = redis
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -37,7 +37,7 @@ module Stoplight
|
|
|
37
37
|
attr_accessor :notifiers
|
|
38
38
|
|
|
39
39
|
# @!attribute [rw] data_store
|
|
40
|
-
# @return [Stoplight::
|
|
40
|
+
# @return [Stoplight::Wiring::DataStore::Base] The default data store instance.
|
|
41
41
|
attr_accessor :data_store
|
|
42
42
|
|
|
43
43
|
# @!attribute [w] traffic_control
|
|
@@ -18,7 +18,7 @@ module Stoplight
|
|
|
18
18
|
# @return [Stoplight::Wiring::LightFactory]
|
|
19
19
|
# @api private the method is used internally by Stoplight
|
|
20
20
|
def build
|
|
21
|
-
LightFactory.new(
|
|
21
|
+
LightFactory.new(configuration.to_h)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/map"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
module Wiring
|
|
7
|
+
# Constructs a fully-wired Light instance from validated configuration.
|
|
8
|
+
#
|
|
9
|
+
# LightBuilder is the final assembly step in the Light creation pipeline.
|
|
10
|
+
# It receives validated config and dependencies from ConfigurationPipeline
|
|
11
|
+
# and wires together all infrastructure components (data stores, trackers,
|
|
12
|
+
# strategies) needed for a functioning circuit breaker.
|
|
13
|
+
#
|
|
14
|
+
# LightBuilder maintains a global registry (MEMORY_REGISTRY) that ensures
|
|
15
|
+
# the same Memory data store config object always produces the same
|
|
16
|
+
# data store instance:
|
|
17
|
+
#
|
|
18
|
+
# data_store = Stoplight::DataStore::Memory.new
|
|
19
|
+
# light1 = Stoplight("foo", data_store: data_store)
|
|
20
|
+
# light2 = Stoplight("bar", data_store: data_store)
|
|
21
|
+
# # light1 and light2 share the same underlying memory store
|
|
22
|
+
#
|
|
23
|
+
# light3 = Stoplight("baz", data_store: Stoplight::DataStore::Memory.new)
|
|
24
|
+
# # light3 has its own independent store
|
|
25
|
+
#
|
|
26
|
+
# This singleton behavior is keyed by config object identity (object_id),
|
|
27
|
+
# not by value equality.
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
class LightBuilder
|
|
31
|
+
FAILOVER_DATA_STORE_CONFIG = Stoplight::DataStore::Memory.new
|
|
32
|
+
private_constant :FAILOVER_DATA_STORE_CONFIG
|
|
33
|
+
|
|
34
|
+
MEMORY_REGISTRY = Concurrent::Map.new
|
|
35
|
+
private_constant :MEMORY_REGISTRY
|
|
36
|
+
|
|
37
|
+
# @!attribute data_store_config
|
|
38
|
+
# @return [Stoplight::DataStore::Bose]
|
|
39
|
+
private attr_reader :data_store_config
|
|
40
|
+
|
|
41
|
+
# @!attribute error_notifier
|
|
42
|
+
# @return [Proc]
|
|
43
|
+
private attr_reader :error_notifier
|
|
44
|
+
|
|
45
|
+
# @!attribute traffic_recovery
|
|
46
|
+
# @return [Stoplight::Domain::TrafficRecovery::Base]
|
|
47
|
+
private attr_reader :traffic_recovery
|
|
48
|
+
|
|
49
|
+
# @!attribute traffic_control
|
|
50
|
+
# @return [Stoplight::Domain::TrafficControl::Base]
|
|
51
|
+
private attr_reader :traffic_control
|
|
52
|
+
|
|
53
|
+
# @!attribute config
|
|
54
|
+
# @return [Stoplight::Domain::Config]
|
|
55
|
+
private attr_reader :config
|
|
56
|
+
|
|
57
|
+
# @!attribute factory
|
|
58
|
+
# @return [Stoplight::Domain::LightFactory]
|
|
59
|
+
private attr_reader :factory
|
|
60
|
+
|
|
61
|
+
def initialize(settings)
|
|
62
|
+
@notifiers = settings[:notifiers]
|
|
63
|
+
@data_store_config = settings[:data_store]
|
|
64
|
+
@error_notifier = settings[:error_notifier]
|
|
65
|
+
@traffic_recovery = settings[:traffic_recovery]
|
|
66
|
+
@traffic_control = settings[:traffic_control]
|
|
67
|
+
@config = settings[:config]
|
|
68
|
+
@factory = settings[:factory]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def build
|
|
72
|
+
Stoplight::Domain::Light.new(
|
|
73
|
+
config,
|
|
74
|
+
state_store:,
|
|
75
|
+
green_run_strategy:,
|
|
76
|
+
yellow_run_strategy:,
|
|
77
|
+
red_run_strategy:,
|
|
78
|
+
factory:
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def state_store = Stoplight::Infrastructure::Storage::CompatibilityState.new(config:, data_store:)
|
|
83
|
+
|
|
84
|
+
# @return [<Stoplight::Notifier::Base>]
|
|
85
|
+
private def notifiers
|
|
86
|
+
Array(@notifiers).map do |notifier|
|
|
87
|
+
Infrastructure::Notifier::FailSafe.new(notifier:, error_notifier:)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def redis_recovery_lock_store
|
|
92
|
+
Infrastructure::DataStore::Redis::RecoveryLockStore.new(
|
|
93
|
+
redis: data_store_config.redis,
|
|
94
|
+
lock_timeout: config.cool_off_time_in_milliseconds,
|
|
95
|
+
scripting:
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private def scripting = Infrastructure::DataStore::Redis::Scripting.new(redis: data_store_config.redis)
|
|
100
|
+
|
|
101
|
+
private def memory_recovery_lock_store
|
|
102
|
+
Infrastructure::DataStore::Memory::RecoveryLockStore.new
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private def failover_data_store
|
|
106
|
+
create_data_store(FAILOVER_DATA_STORE_CONFIG)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private def data_store
|
|
110
|
+
create_data_store(data_store_config)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private def metrics_store
|
|
114
|
+
Stoplight::Infrastructure::Storage::CompatibilityMetrics.new(config:, data_store:)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private def recovery_lock_store
|
|
118
|
+
Stoplight::Infrastructure::Storage::CompatibilityRecoveryLock.new(config:, data_store:)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private def request_tracker
|
|
122
|
+
Domain::Tracker::Request.new(traffic_control:, notifiers:, config:, metrics_store:, state_store:)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private def recovery_probe_tracker
|
|
126
|
+
Domain::Tracker::RecoveryProbe.new(
|
|
127
|
+
traffic_recovery:,
|
|
128
|
+
notifiers:,
|
|
129
|
+
config:,
|
|
130
|
+
metrics_store: recovery_metrics_store,
|
|
131
|
+
state_store:
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private def recovery_metrics_store
|
|
136
|
+
Stoplight::Infrastructure::Storage::CompatibilityRecoveryMetrics.new(config:, data_store:)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private def green_run_strategy
|
|
140
|
+
Domain::Strategies::GreenRunStrategy.new(config:, request_tracker:)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private def yellow_run_strategy
|
|
144
|
+
Domain::Strategies::YellowRunStrategy.new(
|
|
145
|
+
config:,
|
|
146
|
+
notifiers:,
|
|
147
|
+
request_tracker: recovery_probe_tracker,
|
|
148
|
+
red_run_strategy:,
|
|
149
|
+
state_store:,
|
|
150
|
+
metrics_store:,
|
|
151
|
+
recovery_lock_store:
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private def red_run_strategy
|
|
156
|
+
Domain::Strategies::RedRunStrategy.new(config:)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private def create_data_store(data_store_config)
|
|
160
|
+
case data_store_config
|
|
161
|
+
in Stoplight::DataStore::Memory
|
|
162
|
+
memory_registry.compute_if_absent(data_store_config.object_id) do
|
|
163
|
+
Infrastructure::DataStore::Memory.new(
|
|
164
|
+
recovery_lock_store: memory_recovery_lock_store
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
in Stoplight::DataStore::Redis
|
|
168
|
+
Infrastructure::DataStore::FailSafe.new(
|
|
169
|
+
data_store: Stoplight::Infrastructure::DataStore::Redis.new(
|
|
170
|
+
redis: data_store_config.redis,
|
|
171
|
+
warn_on_clock_skew: data_store_config.warn_on_clock_skew,
|
|
172
|
+
recovery_lock_store: redis_recovery_lock_store,
|
|
173
|
+
scripting:
|
|
174
|
+
),
|
|
175
|
+
error_notifier:,
|
|
176
|
+
failover_data_store:,
|
|
177
|
+
circuit_breaker: Stoplight.system_light("data_store:fail_safe:redis")
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private def memory_registry = MEMORY_REGISTRY
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
class LightFactory
|
|
6
|
+
# Validates that traffic control and recovery strategies are
|
|
7
|
+
# compatible with the provided configuration.
|
|
8
|
+
#
|
|
9
|
+
# Different strategies have different configuration requirements:
|
|
10
|
+
# - ErrorRate requires window_size and threshold ∈ [0,1]
|
|
11
|
+
# - ConsecutiveErrors requires threshold > 0
|
|
12
|
+
# - ConsecutiveSuccesses requires recovery_threshold > 0
|
|
13
|
+
#
|
|
14
|
+
# @raise [Stoplight::Error::ConfigurationError] if incompatible
|
|
15
|
+
class CompatibilityValidator
|
|
16
|
+
private attr_reader :dependencies
|
|
17
|
+
private attr_reader :config
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def call(config, dependencies) = new(config, dependencies).call
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(config, dependencies)
|
|
24
|
+
@config = config
|
|
25
|
+
@dependencies = dependencies
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call
|
|
29
|
+
validate_traffic_control!
|
|
30
|
+
validate_traffic_recovery!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private def validate_traffic_control!
|
|
34
|
+
traffic_control = dependencies.fetch(:traffic_control)
|
|
35
|
+
traffic_control.check_compatibility(config).then do |compatibility_result|
|
|
36
|
+
if compatibility_result.incompatible?
|
|
37
|
+
raise Domain::Error::ConfigurationError,
|
|
38
|
+
"#{traffic_control.class.name} incompatible with config: #{compatibility_result.error_messages}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate_traffic_recovery!
|
|
44
|
+
traffic_recovery = dependencies.fetch(:traffic_recovery)
|
|
45
|
+
traffic_recovery.check_compatibility(config).then do |compatibility_result|
|
|
46
|
+
if compatibility_result.incompatible?
|
|
47
|
+
raise Domain::Error::ConfigurationError,
|
|
48
|
+
"#{traffic_recovery.class.name} incompatible with config: #{compatibility_result.error_messages}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
class LightFactory
|
|
6
|
+
# Normalizes user-provided configuration values into canonical forms.
|
|
7
|
+
#
|
|
8
|
+
# Handles common user convenience patterns:
|
|
9
|
+
# - Single error class → Array of error classes
|
|
10
|
+
# - Numeric cool_off_time → Integer
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# config = Config.empty.with(
|
|
14
|
+
# tracked_errors: StandardError, # Single class
|
|
15
|
+
# cool_off_time: 30.5 # Float
|
|
16
|
+
# )
|
|
17
|
+
#
|
|
18
|
+
# normalized = ConfigNormalizer.call(config)
|
|
19
|
+
# normalized.tracked_errors #=> [StandardError] # Array
|
|
20
|
+
# normalized.cool_off_time #=> 30 # Integer
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
class ConfigNormalizer
|
|
24
|
+
class << self
|
|
25
|
+
def call(config) = new(config).call
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @!attribute config
|
|
29
|
+
# @return [Stoplight::Domain::Config]
|
|
30
|
+
private attr_reader :config
|
|
31
|
+
|
|
32
|
+
# @param [Stoplight::Domain::Config]
|
|
33
|
+
def initialize(config)
|
|
34
|
+
@config = config
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Stoplight::Domain::Config]
|
|
38
|
+
def call
|
|
39
|
+
config
|
|
40
|
+
.then { |c| normalize_tracked_errors(c) }
|
|
41
|
+
.then { |c| normalize_skipped_errors(c) }
|
|
42
|
+
.then { |c| normalize_cool_off_time(c) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private def normalize_tracked_errors(config)
|
|
46
|
+
if config.tracked_errors.is_a?(Array)
|
|
47
|
+
config
|
|
48
|
+
else
|
|
49
|
+
config.with(tracked_errors: Array(config.tracked_errors))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private def normalize_skipped_errors(config)
|
|
54
|
+
if config.skipped_errors.is_a?(Array)
|
|
55
|
+
config
|
|
56
|
+
else
|
|
57
|
+
config.with(skipped_errors: Array(config.skipped_errors))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private def normalize_cool_off_time(config)
|
|
62
|
+
if config.cool_off_time.is_a?(Integer)
|
|
63
|
+
config
|
|
64
|
+
else
|
|
65
|
+
config.with(cool_off_time: config.cool_off_time.to_i)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
class LightFactory
|
|
6
|
+
# Orchestrates DSL interpretation, normalization, and validation.
|
|
7
|
+
#
|
|
8
|
+
# ConfigurationPipeline is the entry point for transforming raw user settings
|
|
9
|
+
# into validated domain objects. It coordinates three steps:
|
|
10
|
+
#
|
|
11
|
+
# 1. Normalization - Convert user-friendly values to canonical forms
|
|
12
|
+
# 2. DSL Interpretation - Transform symbols/hashes into strategy objects
|
|
13
|
+
# 3. Validation - Ensure strategies are compatible with configuration
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
class ConfigurationPipeline
|
|
17
|
+
BASE_DEPENDENCIES = {
|
|
18
|
+
data_store: Default::DATA_STORE,
|
|
19
|
+
traffic_recovery: Default::TRAFFIC_RECOVERY,
|
|
20
|
+
traffic_control: Default::TRAFFIC_CONTROL,
|
|
21
|
+
notifiers: Default::NOTIFIERS,
|
|
22
|
+
error_notifier: Default::ERROR_NOTIFIER
|
|
23
|
+
}.freeze
|
|
24
|
+
private_constant :BASE_DEPENDENCIES
|
|
25
|
+
|
|
26
|
+
private attr_reader :dependency_settings
|
|
27
|
+
private attr_reader :config_settings
|
|
28
|
+
|
|
29
|
+
def self.process(config_settings, dependency_settings)
|
|
30
|
+
new(config_settings, dependency_settings).process
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(config_settings, dependency_settings)
|
|
34
|
+
@config_settings = config_settings
|
|
35
|
+
@dependency_settings = dependency_settings
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def process
|
|
39
|
+
config = build_config
|
|
40
|
+
dependencies = build_dependencies
|
|
41
|
+
|
|
42
|
+
CompatibilityValidator.call(config, dependencies)
|
|
43
|
+
|
|
44
|
+
[config, dependencies]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_config
|
|
48
|
+
base_config
|
|
49
|
+
.with(**config_settings)
|
|
50
|
+
.then { |cfg| ConfigNormalizer.call(cfg) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_dependencies
|
|
54
|
+
base_dependencies
|
|
55
|
+
.merge(dependency_settings)
|
|
56
|
+
.then { |deps| interpret_dsl(deps) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def interpret_dsl(dependencies)
|
|
60
|
+
dependencies.merge(
|
|
61
|
+
traffic_control: TrafficControlDsl.call(dependencies.fetch(:traffic_control)),
|
|
62
|
+
traffic_recovery: TrafficRecoveryDsl.call(dependencies.fetch(:traffic_recovery))
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def base_config = Light::DefaultConfig
|
|
67
|
+
|
|
68
|
+
def base_dependencies = BASE_DEPENDENCIES
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
class LightFactory
|
|
6
|
+
TrafficControlDsl = proc do |value|
|
|
7
|
+
case value
|
|
8
|
+
in Domain::TrafficControl::Base
|
|
9
|
+
value
|
|
10
|
+
in :consecutive_errors
|
|
11
|
+
Domain::TrafficControl::ConsecutiveErrors.new
|
|
12
|
+
in :error_rate
|
|
13
|
+
Domain::TrafficControl::ErrorRate.new
|
|
14
|
+
in {error_rate: error_rate_settings}
|
|
15
|
+
Domain::TrafficControl::ErrorRate.new(**error_rate_settings)
|
|
16
|
+
else
|
|
17
|
+
raise Stoplight::Error::ConfigurationError, <<~ERROR
|
|
18
|
+
unsupported traffic_control strategy provided (`#{value}`). Supported options:
|
|
19
|
+
* :consecutive_errors
|
|
20
|
+
* :error_rate
|
|
21
|
+
ERROR
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Wiring
|
|
5
|
+
class LightFactory
|
|
6
|
+
TrafficRecoveryDsl = proc do |value|
|
|
7
|
+
case value
|
|
8
|
+
in Domain::TrafficRecovery::Base
|
|
9
|
+
value
|
|
10
|
+
in :consecutive_successes
|
|
11
|
+
Domain::TrafficRecovery::ConsecutiveSuccesses.new
|
|
12
|
+
else
|
|
13
|
+
raise Domain::Error::ConfigurationError, <<~ERROR
|
|
14
|
+
unsupported traffic_recovery strategy provided (`#{value}`). Supported options:
|
|
15
|
+
* :consecutive_successes
|
|
16
|
+
ERROR
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|