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
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "forwardable"
4
-
5
- module Stoplight
6
- class Light
7
- # Implements light configuration behavior
8
- module ConfigurationBuilderInterface
9
- # Configures data store to be used with this circuit breaker
10
- #
11
- # @example
12
- # Stoplight('example')
13
- # .with_data_store(Stoplight::DataStore::Memory.new)
14
- #
15
- # @param data_store [DataStore::Base]
16
- # @return [Stoplight::Light]
17
- # @deprecated consider using +Light#with+ for reconfiguration
18
- def with_data_store(data_store)
19
- reconfigure(config.with(data_store: data_store))
20
- end
21
-
22
- # Configures cool off time. Stoplight automatically tries to recover
23
- # from the red state after the cool off time.
24
- #
25
- # @example
26
- # Stoplight('example')
27
- # .cool_off_time(60)
28
- #
29
- # @param cool_off_time [Numeric] number of seconds
30
- # @return [Stoplight::Light]
31
- # @deprecated consider using +Light#with+ for reconfiguration
32
- def with_cool_off_time(cool_off_time)
33
- reconfigure(config.with(cool_off_time: cool_off_time))
34
- end
35
-
36
- # Configures custom threshold. After this number of failures Stoplight
37
- # switches to the red state:
38
- #
39
- # @example
40
- # Stoplight('example')
41
- # .with_threshold(5)
42
- #
43
- # @param threshold [Numeric]
44
- # @return [Stoplight::Light]
45
- # @deprecated consider using +Light#with+ for reconfiguration
46
- def with_threshold(threshold)
47
- reconfigure(config.with(threshold: threshold))
48
- end
49
-
50
- # Configures custom window size which Stoplight uses to count failures. For example,
51
- #
52
- # @example
53
- # Stoplight('example')
54
- # .with_threshold(5)
55
- # .with_window_size(60)
56
- #
57
- # The above example will turn to red light only when 5 errors happen
58
- # within 60 seconds period.
59
- #
60
- # @param window_size [Numeric] number of seconds
61
- # @return [Stoplight::Light]
62
- # @deprecated consider using +Light#with+ for reconfiguration
63
- def with_window_size(window_size)
64
- reconfigure(config.with(window_size: window_size))
65
- end
66
-
67
- # Configures custom notifier
68
- #
69
- # @example
70
- # io = StringIO.new
71
- # notifier = Stoplight::Notifier::IO.new(io)
72
- # Stoplight('example')
73
- # .with_notifiers([notifier])
74
- #
75
- # @param notifiers [Array<Notifier::Base>]
76
- # @return [Stoplight::Light]
77
- # @deprecated consider using +Light#with+ for reconfiguration
78
- def with_notifiers(notifiers)
79
- reconfigure(config.with(notifiers: notifiers))
80
- end
81
-
82
- # @param error_notifier [Proc]
83
- # @return [Stoplight::Light]
84
- # @api private
85
- # @deprecated consider using +Light#with+ for reconfiguration
86
- def with_error_notifier(&error_notifier)
87
- reconfigure(config.with(error_notifier: error_notifier))
88
- end
89
-
90
- # Configures a custom list of tracked errors that counts toward the threshold.
91
- #
92
- # @example
93
- # light = Stoplight('example')
94
- # .with_tracked_errors(TimeoutError, NetworkError)
95
- # light.run { call_external_service }
96
- #
97
- # In the example above, the +TimeoutError+ and +NetworkError+ exceptions
98
- # will be counted towards the threshold for moving the circuit breaker into the red state.
99
- # If not configured, the default tracked error is +StandardError+.
100
- #
101
- # @param tracked_errors [Array<StandardError>]
102
- # @return [Stoplight::Light]
103
- # @deprecated consider using +Light#with+ for reconfiguration
104
- def with_tracked_errors(*tracked_errors)
105
- reconfigure(config.with(tracked_errors: tracked_errors.dup.freeze))
106
- end
107
-
108
- # Configures a custom list of skipped errors that do not count toward the threshold.
109
- # Typically, such errors does not represent a real failure and handled somewhere else
110
- # in the code.
111
- #
112
- # @example
113
- # light = Stoplight('example')
114
- # .with_skipped_errors(ActiveRecord::RecordNotFound)
115
- # light.run { User.find(123) }
116
- #
117
- # In the example above, the +ActiveRecord::RecordNotFound+ doesn't
118
- # move the circuit breaker into the red state.
119
- #
120
- # @param skipped_errors [Array<Exception>]
121
- # @return [Stoplight::Light]
122
- # @deprecated consider using +Light#with+ for reconfiguration
123
- def with_skipped_errors(*skipped_errors)
124
- reconfigure(config.with(skipped_errors: skipped_errors))
125
- end
126
- end
127
- end
128
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- class Light
5
- # Defines how the light executes when it is green.
6
- #
7
- # This strategy clears failures after successful execution and handles errors
8
- # by either raising them or invoking a fallback if provided.
9
- #
10
- # @api private
11
- class GreenRunStrategy < RunStrategy
12
- # Executes the provided code block when the light is in the green state.
13
- #
14
- # @param fallback [Proc, nil] A fallback proc to execute in case of an error.
15
- # @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
16
- # @yield The code block to execute.
17
- # @return [Object] The result of the code block if successful.
18
- # @raise [Exception] Re-raises the error if it is not tracked or no fallback is provided.
19
- def execute(fallback, metadata:, &code)
20
- # TODO: Consider implementing sampling rate to limit the memory footprint
21
- code.call.tap { record_success }
22
- rescue => error
23
- if config.track_error?(error)
24
- record_error(error)
25
-
26
- if fallback
27
- fallback.call(error)
28
- else
29
- raise
30
- end
31
- else
32
- # User chose to not track the error, so we record it as a success
33
- record_success
34
- raise
35
- end
36
- end
37
-
38
- private def record_error(error)
39
- failure = Stoplight::Failure.from_error(error)
40
- metadata = data_store.record_failure(config, failure)
41
-
42
- if config.traffic_control.stop_traffic?(config, metadata) && data_store.transition_to_color(config, Color::RED)
43
- config.notifiers.each do |notifier|
44
- notifier.notify(config, Color::GREEN, Color::RED, error)
45
- end
46
- end
47
- end
48
-
49
- private def record_success
50
- data_store.record_success(config)
51
- end
52
- end
53
- end
54
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- class Light
5
- # Defines how the light executes when it is red.
6
- #
7
- # This strategy prevents execution of the code block and either raises an error
8
- # or invokes a fallback if provided.
9
- #
10
- # @api private
11
- class RedRunStrategy < RunStrategy
12
- # Executes the fallback proc when the light is in the red state.
13
- #
14
- # @param fallback [Proc, nil] A fallback proc to execute instead of the code block.
15
- # @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
16
- # @return [Object, nil] The result of the fallback proc if provided.
17
- # @raise [Stoplight::Error::RedLight] Raises an error if no fallback is provided.
18
- def execute(fallback, metadata:)
19
- if fallback
20
- fallback.call(nil)
21
- else
22
- raise Error::RedLight.new(
23
- config.name,
24
- cool_off_time: config.cool_off_time,
25
- retry_after: metadata.recovery_scheduled_after
26
- )
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- class Light
5
- # Represents an abstract strategy for running a light's operations.
6
- # Every new strategy should be a child of this class.
7
- #
8
- # @api private
9
- # @abstract
10
- class RunStrategy
11
- # @!attribute [r] config
12
- # @return [Stoplight::Light::Config] The configuration for the light.
13
- private attr_reader :config
14
-
15
- # @!attribute [r] data_store
16
- # @return [Stoplight::DataStore::Base] The data store associated with the light.
17
- private attr_reader :data_store
18
-
19
- # @param config [Stoplight::Light::Config] The configuration for the light.
20
- def initialize(config)
21
- @config = config
22
- @data_store = config.data_store
23
- end
24
-
25
- # @param fallback [Proc, nil] A fallback proc to execute in case of an error.
26
- # @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
27
- def execute(fallback, metadata:, &code)
28
- raise NotImplementedError, "Subclasses must implement the execute method"
29
- end
30
- end
31
- end
32
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- class Light
5
- # Defines how the light executes when it is yellow.
6
- #
7
- # This strategy clears failures after successful execution and notifies
8
- # about color switch from Red to Green. It also handles errors by either
9
- # raising them or invoking a fallback if provided.
10
- #
11
- # @api private
12
- class YellowRunStrategy < RunStrategy
13
- # Executes the provided code block when the light is in the yellow state.
14
- #
15
- # @param fallback [Proc, nil] A fallback proc to execute in case of an error.
16
- # @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
17
- # @yield The code block to execute.
18
- # @return [Object] The result of the code block if successful.
19
- # @raise [Exception] Re-raises the error if it is not tracked or no fallback is provided.
20
- def execute(fallback, metadata:, &code)
21
- transition_to_yellow(metadata:)
22
- # TODO: We need to employ a probabilistic approach here to avoid "thundering herd" problem
23
- code.call.tap { record_recovery_probe_success }
24
- rescue => error
25
- if config.track_error?(error)
26
- record_recovery_probe_failure(error)
27
-
28
- if fallback
29
- fallback.call(error)
30
- else
31
- raise
32
- end
33
- else
34
- record_recovery_probe_success
35
- raise
36
- end
37
- end
38
-
39
- private def record_recovery_probe_success
40
- metadata = data_store.record_recovery_probe_success(config)
41
-
42
- recover(metadata)
43
- end
44
-
45
- private def record_recovery_probe_failure(error)
46
- failure = Failure.from_error(error)
47
- metadata = data_store.record_recovery_probe_failure(config, failure)
48
-
49
- recover(metadata)
50
- end
51
-
52
- # @param metadata [Stoplight::Metadata]
53
- # @return [void]
54
- def transition_to_yellow(metadata:)
55
- return unless metadata.color == Color::YELLOW
56
-
57
- if metadata.recovery_scheduled_after && config.data_store.transition_to_color(config, Color::YELLOW)
58
- config.notifiers.each do |notifier|
59
- notifier.notify(config, Color::RED, Color::YELLOW, nil)
60
- end
61
- end
62
- end
63
-
64
- private def recover(metadata)
65
- recovery_result = config.traffic_recovery.determine_color(config, metadata)
66
-
67
- case recovery_result
68
- when TrafficRecovery::GREEN
69
- if data_store.transition_to_color(config, Color::GREEN)
70
- config.notifiers.each do |notifier|
71
- notifier.notify(config, Color::YELLOW, Color::GREEN, nil)
72
- end
73
- end
74
- when TrafficRecovery::YELLOW
75
- if data_store.transition_to_color(config, Color::YELLOW)
76
- config.notifiers.each do |notifier|
77
- notifier.notify(config, Color::RED, Color::YELLOW, nil)
78
- end
79
- end
80
- when TrafficRecovery::RED
81
- if data_store.transition_to_color(config, Color::RED)
82
- config.notifiers.each do |notifier|
83
- notifier.notify(config, Color::YELLOW, Color::RED, nil)
84
- end
85
- end
86
- when TrafficRecovery::PASS
87
- # No state change, do nothing
88
- else
89
- raise "recovery strategy returned an expected color: #{recovery_result}"
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,191 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- #
5
- # @api private use +Stoplight()+ method instead
6
- class Light
7
- extend Forwardable
8
- include ConfigurationBuilderInterface
9
-
10
- # @!attribute [r] config
11
- # @return [Stoplight::Light::Config]
12
- # @api private
13
- attr_reader :config
14
-
15
- # @!attribute [r] name
16
- # The name of the light.
17
- # @return [String]
18
- def_delegator :config, :name
19
-
20
- # @param config [Stoplight::Light::Config]
21
- def initialize(config, green_run_strategy: nil, yellow_run_strategy: nil, red_run_strategy: nil)
22
- @config = config
23
- @green_run_strategy = green_run_strategy
24
- @yellow_run_strategy = yellow_run_strategy
25
- @red_run_strategy = red_run_strategy
26
- end
27
-
28
- # Returns the current state of the light:
29
- # * +Stoplight::State::LOCKED_GREEN+ -- light is locked green and allows all traffic
30
- # * +Stoplight::State::LOCKED_RED+ -- light is locked red and blocks all traffic
31
- # * +Stoplight::State::UNLOCKED+ -- light is not locked and follow the configured rules
32
- #
33
- # @return [String]
34
- def state
35
- metadata.locked_state
36
- end
37
-
38
- # Returns current color:
39
- # * +Stoplight::Color::GREEN+ -- circuit breaker is closed
40
- # * +Stoplight::Color::RED+ -- circuit breaker is open
41
- # * +Stoplight::Color::YELLOW+ -- circuit breaker is half-open
42
- #
43
- # @example
44
- # light = Stoplight('example')
45
- # light.color #=> Color::GREEN
46
- #
47
- # @return [String] returns current light color
48
- def color
49
- metadata.color
50
- end
51
-
52
- # Runs the given block of code with this circuit breaker
53
- #
54
- # @example
55
- # light = Stoplight('example')
56
- # light.run { 2/0 }
57
- #
58
- # @example Running with fallback
59
- # light = Stoplight('example')
60
- # light.run(->(error) { 0 }) { 1 / 0 } #=> 0
61
- #
62
- # @param fallback [Proc, nil] (nil) fallback code to run if the circuit breaker is open
63
- # @raise [Stoplight::Error::RedLight]
64
- # @return [any]
65
- # @raise [Error::RedLight]
66
- def run(fallback = nil, &code)
67
- raise ArgumentError, "nothing to run. Please, pass a block into `Light#run`" unless block_given?
68
-
69
- metadata.then do |metadata|
70
- strategy = state_strategy_factory(metadata.color)
71
- strategy.execute(fallback, metadata:, &code)
72
- end
73
- end
74
-
75
- # Locks light in either +State::LOCKED_RED+ or +State::LOCKED_GREEN+
76
- #
77
- # @example
78
- # light = Stoplight('example-locked')
79
- # light.lock(Stoplight::Color::RED)
80
- #
81
- # @param color [String] should be either +Color::RED+ or +Color::GREEN+
82
- # @return [Stoplight::Light] returns locked light (circuit breaker)
83
- def lock(color)
84
- state = case color
85
- when Color::RED then State::LOCKED_RED
86
- when Color::GREEN then State::LOCKED_GREEN
87
- else raise Error::IncorrectColor
88
- end
89
-
90
- config.data_store.set_state(config, state)
91
-
92
- self
93
- end
94
-
95
- # Unlocks light and sets its state to State::UNLOCKED
96
- #
97
- # @example
98
- # light = Stoplight('example-locked')
99
- # light.lock(Stoplight::Color::RED)
100
- # light.unlock
101
- #
102
- # @return [Stoplight::Light] returns unlocked light (circuit breaker)
103
- def unlock
104
- config.data_store.set_state(config, Stoplight::State::UNLOCKED)
105
-
106
- self
107
- end
108
-
109
- # Two lights considered equal if they have the same configuration.
110
- #
111
- # @param other [any]
112
- # @return [Boolean]
113
- def ==(other)
114
- other.is_a?(self.class) && config == other.config
115
- end
116
-
117
- # Reconfigures the light with updated settings and returns a new instance.
118
- #
119
- # This method allows you to modify the configuration of a +Stoplight::Light+ object
120
- # by providing a hash of settings. The original light remains unchanged, and a new
121
- # light instance with the updated configuration is returned.
122
- #
123
- # @param settings [Hash] A hash of configuration options to update.
124
- # @option settings [String] :name The name of the light.
125
- # @option settings [Numeric] :cool_off_time The cool-off time in seconds before the light attempts recovery.
126
- # @option settings [Numeric] :threshold The failure threshold to trigger the red state.
127
- # @option settings [Numeric] :window_size The time window in seconds for counting failures.
128
- # @option settings [Stoplight::DataStore::Base] :data_store The data store to use for persisting light state.
129
- # @option settings [Array<Stoplight::Notifier::Base>] :notifiers A list of notifiers to handle light events.
130
- # @option settings [Proc] :error_notifier A custom error notifier to handle exceptions.
131
- # @option settings [Array<StandardError>] :tracked_errors A list of errors to track for failure counting.
132
- # @option settings [Array<StandardError>] :skipped_errors A list of errors to skip from failure counting.
133
- # @return [Stoplight::Light] A new `Stoplight::Light` instance with the updated configuration.
134
- #
135
- # @example Reconfiguring a light with custom settings
136
- # light = Stoplight('payment-api')
137
- #
138
- # # Create a light for invoices with a higher threshold
139
- # invoices_light = light.with(tracked_errors: [TimeoutError], threshold: 10)
140
- #
141
- # # Create a light for payments with a lower threshold
142
- # payment_light = light.with(threshold: 5)
143
- #
144
- # # Run the lights with their respective configurations
145
- # invoices_light.run(->(error) { [] }) { call_invoices_api }
146
- # payment_light.run(->(error) { nil }) { call_payment_api }
147
- # @see +Stoplight()+
148
- def with(**settings)
149
- reconfigure(config.with(**settings))
150
- end
151
-
152
- private
153
-
154
- def state_strategy_factory(color)
155
- case color
156
- when Color::GREEN
157
- green_run_strategy
158
- when Color::YELLOW
159
- yellow_run_strategy
160
- else
161
- red_run_strategy
162
- end
163
- end
164
-
165
- # @return [Stoplight::Runnable::RunStrategy]
166
- def green_run_strategy
167
- @green_run_strategy ||= GreenRunStrategy.new(config)
168
- end
169
-
170
- # @return [Stoplight::Runnable::RunStrategy]
171
- def yellow_run_strategy
172
- @yellow_run_strategy ||= YellowRunStrategy.new(config)
173
- end
174
-
175
- # @return [Stoplight::Runnable::RunStrategy]
176
- def red_run_strategy
177
- @red_run_strategy ||= RedRunStrategy.new(config)
178
- end
179
-
180
- # @param config [Stoplight::Light::Config]
181
- # @return [Stoplight::Light]
182
- def reconfigure(config)
183
- self.class.new(config)
184
- end
185
-
186
- # @return [Stoplight::Metadata]
187
- def metadata
188
- config.data_store.get_metadata(config)
189
- end
190
- end
191
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- # @api private
5
- Metadata = Data.define(
6
- :successes,
7
- :errors,
8
- :recovery_probe_successes,
9
- :recovery_probe_errors,
10
- :last_error_at,
11
- :last_success_at,
12
- :consecutive_errors,
13
- :consecutive_successes,
14
- :last_error,
15
- :breached_at,
16
- :locked_state,
17
- :recovery_scheduled_after,
18
- :recovery_started_at,
19
- :recovered_at,
20
- :current_time
21
- ) do
22
- def initialize(
23
- current_time: Time.now,
24
- successes: 0,
25
- errors: 0,
26
- recovery_probe_successes: 0,
27
- recovery_probe_errors: 0,
28
- last_error_at: nil,
29
- last_success_at: nil,
30
- consecutive_errors: 0,
31
- consecutive_successes: 0,
32
- last_error: nil,
33
- breached_at: nil,
34
- locked_state: nil,
35
- recovery_started_at: nil,
36
- recovery_scheduled_after: nil,
37
- recovered_at: nil
38
- )
39
- super(
40
- recovery_probe_successes: recovery_probe_successes.to_i,
41
- recovery_probe_errors: recovery_probe_errors.to_i,
42
- successes: successes.to_i,
43
- errors: errors.to_i,
44
- last_error_at: (Time.at(Integer(last_error_at)) if last_error_at),
45
- last_success_at: (Time.at(Integer(last_success_at)) if last_success_at),
46
- consecutive_errors: consecutive_errors.to_i,
47
- consecutive_successes: consecutive_successes.to_i,
48
- last_error:,
49
- breached_at: (Time.at(Integer(breached_at)) if breached_at),
50
- locked_state: locked_state || State::UNLOCKED,
51
- recovery_scheduled_after: (Time.at(Integer(recovery_scheduled_after)) if recovery_scheduled_after),
52
- recovery_started_at: (Time.at(Integer(recovery_started_at)) if recovery_started_at),
53
- recovered_at: (Time.at(Integer(recovered_at)) if recovered_at),
54
- current_time:,
55
- )
56
- end
57
-
58
- # Creates a new Metadata instance with updated attributes. This method overrides
59
- # the default +with+ method provided by +Data.define+ to ensure constructor
60
- # logic is applied.
61
- #
62
- # @param kwargs [Hash{Symbol => Object}]
63
- # @return [Metadata]
64
- def with(**kwargs)
65
- self.class.new(**to_h.merge(current_time: Time.now, **kwargs))
66
- end
67
-
68
- # @return [String] one of +Color::GREEN+, +Color::RED+, or +Color::YELLOW+
69
- def color
70
- if locked_state == State::LOCKED_GREEN
71
- Color::GREEN
72
- elsif locked_state == State::LOCKED_RED
73
- Color::RED
74
- elsif (recovery_scheduled_after && recovery_scheduled_after < current_time) || recovery_started_at
75
- Color::YELLOW
76
- elsif breached_at
77
- Color::RED
78
- else
79
- Color::GREEN
80
- end
81
- end
82
-
83
- # Calculates the error rate based on the number of successes and errors.
84
- #
85
- # @return [Float]
86
- def error_rate
87
- if (successes + errors).zero?
88
- 0.0
89
- else
90
- errors.fdiv(successes + errors)
91
- end
92
- end
93
-
94
- # @return [Integer]
95
- def requests
96
- successes + errors
97
- end
98
- end
99
- end