cmdx 1.18.0 → 1.20.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/.DS_Store +0 -0
- data/CHANGELOG.md +73 -9
- data/README.md +1 -1
- data/lib/cmdx/attribute.rb +88 -20
- data/lib/cmdx/attribute_registry.rb +79 -8
- data/lib/cmdx/attribute_value.rb +8 -3
- data/lib/cmdx/callback_registry.rb +60 -26
- data/lib/cmdx/chain.rb +47 -4
- data/lib/cmdx/coercion_registry.rb +42 -20
- data/lib/cmdx/coercions/array.rb +8 -3
- data/lib/cmdx/coercions/big_decimal.rb +1 -1
- data/lib/cmdx/coercions/boolean.rb +6 -2
- data/lib/cmdx/coercions/complex.rb +1 -1
- data/lib/cmdx/coercions/date.rb +2 -7
- data/lib/cmdx/coercions/date_time.rb +2 -7
- data/lib/cmdx/coercions/float.rb +1 -1
- data/lib/cmdx/coercions/hash.rb +1 -1
- data/lib/cmdx/coercions/integer.rb +4 -5
- data/lib/cmdx/coercions/rational.rb +1 -1
- data/lib/cmdx/coercions/string.rb +1 -1
- data/lib/cmdx/coercions/symbol.rb +1 -1
- data/lib/cmdx/coercions/time.rb +1 -7
- data/lib/cmdx/configuration.rb +26 -0
- data/lib/cmdx/context.rb +9 -6
- data/lib/cmdx/deprecator.rb +27 -14
- data/lib/cmdx/errors.rb +3 -4
- data/lib/cmdx/exception.rb +7 -0
- data/lib/cmdx/executor.rb +77 -54
- data/lib/cmdx/identifier.rb +4 -6
- data/lib/cmdx/locale.rb +32 -9
- data/lib/cmdx/middleware_registry.rb +43 -23
- data/lib/cmdx/middlewares/correlate.rb +4 -2
- data/lib/cmdx/middlewares/timeout.rb +11 -10
- data/lib/cmdx/parallelizer.rb +100 -0
- data/lib/cmdx/pipeline.rb +42 -23
- data/lib/cmdx/railtie.rb +1 -1
- data/lib/cmdx/result.rb +27 -11
- data/lib/cmdx/retry.rb +166 -0
- data/lib/cmdx/settings.rb +222 -0
- data/lib/cmdx/task.rb +53 -61
- data/lib/cmdx/utils/format.rb +17 -1
- data/lib/cmdx/utils/normalize.rb +52 -0
- data/lib/cmdx/utils/wrap.rb +38 -0
- data/lib/cmdx/validator_registry.rb +45 -20
- data/lib/cmdx/validators/absence.rb +1 -1
- data/lib/cmdx/validators/exclusion.rb +2 -2
- data/lib/cmdx/validators/format.rb +1 -1
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +1 -1
- data/lib/cmdx/validators/numeric.rb +1 -1
- data/lib/cmdx/validators/presence.rb +1 -1
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx.rb +12 -0
- data/lib/generators/cmdx/templates/install.rb +11 -0
- data/mkdocs.yml +5 -1
- metadata +6 -15
data/lib/cmdx/pipeline.rb
CHANGED
|
@@ -6,6 +6,14 @@ module CMDx
|
|
|
6
6
|
# and handling breakpoints that can interrupt execution at specific task statuses.
|
|
7
7
|
class Pipeline
|
|
8
8
|
|
|
9
|
+
# @rbs SEQUENTIAL_REGEXP: Regexp
|
|
10
|
+
SEQUENTIAL_REGEXP = /\Asequential\z/
|
|
11
|
+
private_constant :SEQUENTIAL_REGEXP
|
|
12
|
+
|
|
13
|
+
# @rbs PARALLEL_REGEXP: Regexp
|
|
14
|
+
PARALLEL_REGEXP = /\Aparallel\z/
|
|
15
|
+
private_constant :PARALLEL_REGEXP
|
|
16
|
+
|
|
9
17
|
# Returns the workflow being executed by this pipeline.
|
|
10
18
|
#
|
|
11
19
|
# @return [Workflow] The workflow instance
|
|
@@ -54,13 +62,20 @@ module CMDx
|
|
|
54
62
|
#
|
|
55
63
|
# @rbs () -> void
|
|
56
64
|
def execute
|
|
65
|
+
default_breakpoints = Utils::Normalize.statuses(
|
|
66
|
+
workflow.class.settings.breakpoints ||
|
|
67
|
+
workflow.class.settings.workflow_breakpoints
|
|
68
|
+
)
|
|
69
|
+
|
|
57
70
|
workflow.class.pipeline.each do |group|
|
|
58
71
|
next unless Utils::Condition.evaluate(workflow, group.options)
|
|
59
72
|
|
|
60
|
-
breakpoints =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
breakpoints =
|
|
74
|
+
if group.options.key?(:breakpoints)
|
|
75
|
+
Utils::Normalize.statuses(group.options[:breakpoints])
|
|
76
|
+
else
|
|
77
|
+
default_breakpoints
|
|
78
|
+
end
|
|
64
79
|
|
|
65
80
|
execute_group_tasks(group, breakpoints)
|
|
66
81
|
end
|
|
@@ -82,8 +97,8 @@ module CMDx
|
|
|
82
97
|
# @rbs (untyped group, Array[String] breakpoints) -> void
|
|
83
98
|
def execute_group_tasks(group, breakpoints)
|
|
84
99
|
case strategy = group.options[:strategy]
|
|
85
|
-
when NilClass,
|
|
86
|
-
when
|
|
100
|
+
when NilClass, SEQUENTIAL_REGEXP then execute_tasks_in_sequence(group, breakpoints)
|
|
101
|
+
when PARALLEL_REGEXP then execute_tasks_in_parallel(group, breakpoints)
|
|
87
102
|
else raise "unknown execution strategy #{strategy.inspect}"
|
|
88
103
|
end
|
|
89
104
|
end
|
|
@@ -111,39 +126,43 @@ module CMDx
|
|
|
111
126
|
end
|
|
112
127
|
end
|
|
113
128
|
|
|
114
|
-
#
|
|
129
|
+
# Each task receives a snapshot of the workflow context to prevent
|
|
130
|
+
# unsynchronized concurrent writes to a shared Hash. Snapshots are
|
|
131
|
+
# merged back into the workflow context after all tasks complete.
|
|
115
132
|
#
|
|
116
133
|
# @param group [CMDx::Group] The task group to execute in parallel
|
|
117
|
-
# @param breakpoints [Array<
|
|
118
|
-
# @option group.options [Integer] :
|
|
119
|
-
# @option group.options [Integer] :in_processes Number of processes to use
|
|
134
|
+
# @param breakpoints [Array<String>] Status values that trigger execution breaks
|
|
135
|
+
# @option group.options [Integer] :pool_size Number of concurrent threads (defaults to task count)
|
|
120
136
|
#
|
|
121
137
|
# @return [void]
|
|
122
138
|
#
|
|
123
|
-
# @raise [
|
|
139
|
+
# @raise [Fault] When a task result status matches a breakpoint
|
|
124
140
|
#
|
|
125
141
|
# @example
|
|
126
142
|
# execute_tasks_in_parallel(group, ["failed"])
|
|
127
143
|
#
|
|
128
144
|
# @rbs (untyped group, Array[String] breakpoints) -> void
|
|
129
145
|
def execute_tasks_in_parallel(group, breakpoints)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
throwable_result = nil
|
|
146
|
+
contexts = group.tasks.map { Context.new(workflow.context.to_h) }
|
|
147
|
+
ctx_pairs = group.tasks.zip(contexts)
|
|
148
|
+
pool_size = group.options.fetch(:pool_size, ctx_pairs.size)
|
|
134
149
|
|
|
135
|
-
|
|
150
|
+
results = Parallelizer.call(ctx_pairs, pool_size:) do |task, context|
|
|
136
151
|
Chain.current = workflow.chain
|
|
137
|
-
|
|
138
|
-
task_result = task.execute(workflow.context)
|
|
139
|
-
next unless breakpoints.include?(task_result.status)
|
|
140
|
-
|
|
141
|
-
raise Parallel::Break, throwable_result = task_result
|
|
152
|
+
task.execute(context)
|
|
142
153
|
end
|
|
143
154
|
|
|
144
|
-
|
|
155
|
+
contexts.each { |ctx| workflow.context.merge!(ctx) }
|
|
156
|
+
|
|
157
|
+
faulted = results.select { |r| breakpoints.include?(r.status) }
|
|
158
|
+
return if faulted.empty?
|
|
145
159
|
|
|
146
|
-
workflow.
|
|
160
|
+
workflow.public_send(
|
|
161
|
+
:"#{faulted.last.status}!",
|
|
162
|
+
Locale.t("cmdx.faults.unspecified"),
|
|
163
|
+
source: :parallel,
|
|
164
|
+
faults: faulted.map(&:to_h)
|
|
165
|
+
)
|
|
147
166
|
end
|
|
148
167
|
|
|
149
168
|
end
|
data/lib/cmdx/railtie.rb
CHANGED
|
@@ -24,7 +24,7 @@ module CMDx
|
|
|
24
24
|
#
|
|
25
25
|
# @rbs (untyped app) -> void
|
|
26
26
|
initializer("cmdx.configure_locales") do |app|
|
|
27
|
-
|
|
27
|
+
Utils::Wrap.array(app.config.i18n.available_locales).each do |locale|
|
|
28
28
|
path = CMDx.gem_path.join("lib/locales/#{locale}.yml")
|
|
29
29
|
next unless File.file?(path)
|
|
30
30
|
|
data/lib/cmdx/result.rb
CHANGED
|
@@ -28,13 +28,17 @@ module CMDx
|
|
|
28
28
|
|
|
29
29
|
# @rbs STRIP_FAILURE: Proc
|
|
30
30
|
STRIP_FAILURE = proc do |hash, result, key|
|
|
31
|
-
unless result.
|
|
31
|
+
unless result.public_send(:"#{key}?")
|
|
32
32
|
# Strip caused/threw failures since its the same info as the log line
|
|
33
|
-
hash[key] = result.
|
|
33
|
+
hash[key] = result.public_send(key).to_h.except(:caused_failure, :threw_failure)
|
|
34
34
|
end
|
|
35
35
|
end.freeze
|
|
36
36
|
private_constant :STRIP_FAILURE
|
|
37
37
|
|
|
38
|
+
# @rbs FAILURE_KEY_REGEX: Regexp
|
|
39
|
+
FAILURE_KEY_REGEX = /_failure\z/
|
|
40
|
+
private_constant :FAILURE_KEY_REGEX
|
|
41
|
+
|
|
38
42
|
# Returns the task instance associated with this result.
|
|
39
43
|
#
|
|
40
44
|
# @return [CMDx::Task] The task instance
|
|
@@ -261,7 +265,7 @@ module CMDx
|
|
|
261
265
|
def on(*states_or_statuses, &)
|
|
262
266
|
raise ArgumentError, "block required" unless block_given?
|
|
263
267
|
|
|
264
|
-
yield(self) if states_or_statuses.any? { |s|
|
|
268
|
+
yield(self) if states_or_statuses.any? { |s| public_send(:"#{s}?") }
|
|
265
269
|
self
|
|
266
270
|
end
|
|
267
271
|
|
|
@@ -338,7 +342,7 @@ module CMDx
|
|
|
338
342
|
unless frames.empty?
|
|
339
343
|
frames = frames.map(&:to_s)
|
|
340
344
|
|
|
341
|
-
if (cleaner = task.class.settings
|
|
345
|
+
if (cleaner = task.class.settings.backtrace_cleaner)
|
|
342
346
|
cleaner.call(frames)
|
|
343
347
|
end
|
|
344
348
|
|
|
@@ -383,7 +387,7 @@ module CMDx
|
|
|
383
387
|
def caused_failure
|
|
384
388
|
return unless failed?
|
|
385
389
|
|
|
386
|
-
chain.results.
|
|
390
|
+
chain.results.reverse_each.find(&:failed?)
|
|
387
391
|
end
|
|
388
392
|
|
|
389
393
|
# @return [Boolean] Whether this result caused the failure
|
|
@@ -411,8 +415,17 @@ module CMDx
|
|
|
411
415
|
return unless failed?
|
|
412
416
|
|
|
413
417
|
current = index
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
last_failed = nil
|
|
419
|
+
|
|
420
|
+
chain.results.each do |r|
|
|
421
|
+
next unless r.failed?
|
|
422
|
+
|
|
423
|
+
return r if r.index > current
|
|
424
|
+
|
|
425
|
+
last_failed = r
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
last_failed
|
|
416
429
|
end
|
|
417
430
|
|
|
418
431
|
# @return [Boolean] Whether this result threw the failure
|
|
@@ -469,7 +482,7 @@ module CMDx
|
|
|
469
482
|
#
|
|
470
483
|
# @rbs () -> Integer
|
|
471
484
|
def index
|
|
472
|
-
chain.index(self)
|
|
485
|
+
@chain_index || chain.index(self)
|
|
473
486
|
end
|
|
474
487
|
|
|
475
488
|
# @return [String] The outcome of the task execution
|
|
@@ -513,13 +526,16 @@ module CMDx
|
|
|
513
526
|
#
|
|
514
527
|
# @example
|
|
515
528
|
# result.to_s # => "task_id=my_task state=complete status=success"
|
|
529
|
+
# @example With failure
|
|
530
|
+
# result.to_s # => "task_id=my_task state=complete status=failed threw_failure=<[1] MyTask: my_task>"
|
|
516
531
|
#
|
|
517
532
|
# @rbs () -> String
|
|
518
533
|
def to_s
|
|
519
534
|
Utils::Format.to_str(to_h) do |key, value|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
else
|
|
535
|
+
if FAILURE_KEY_REGEX.match?(key)
|
|
536
|
+
"#{key}=<[#{value[:index]}] #{value[:class]}: #{value[:id]}>"
|
|
537
|
+
else
|
|
538
|
+
"#{key}=#{value.inspect}"
|
|
523
539
|
end
|
|
524
540
|
end
|
|
525
541
|
end
|
data/lib/cmdx/retry.rb
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
# Manages retry logic and state for task execution.
|
|
5
|
+
#
|
|
6
|
+
# The Retry class tracks retry availability, attempt counts, and
|
|
7
|
+
# remaining retries for a given task. It also resolves exception
|
|
8
|
+
# matching and computes wait times using configurable jitter strategies.
|
|
9
|
+
class Retry
|
|
10
|
+
|
|
11
|
+
# Returns the task instance associated with this retry.
|
|
12
|
+
#
|
|
13
|
+
# @return [Task] the task being retried
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# retry_instance.task # => #<CreateUser ...>
|
|
17
|
+
#
|
|
18
|
+
# @rbs @task: Task
|
|
19
|
+
attr_reader :task
|
|
20
|
+
|
|
21
|
+
# Creates a new Retry instance for the given task.
|
|
22
|
+
#
|
|
23
|
+
# @param task [Task] the task to manage retries for
|
|
24
|
+
#
|
|
25
|
+
# @return [Retry] a new Retry instance
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# retry_instance = Retry.new(task)
|
|
29
|
+
#
|
|
30
|
+
# @rbs (Task task) -> void
|
|
31
|
+
def initialize(task)
|
|
32
|
+
@task = task
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the total number of retries configured for the task.
|
|
36
|
+
#
|
|
37
|
+
# @return [Integer] the configured retry count
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# retry_instance.available # => 3
|
|
41
|
+
#
|
|
42
|
+
# @rbs () -> Integer
|
|
43
|
+
def available
|
|
44
|
+
Integer(task.class.settings.retries || 0)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Checks if the task has any retries configured.
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean] true if retries are configured
|
|
50
|
+
#
|
|
51
|
+
# @example
|
|
52
|
+
# retry_instance.available? # => true
|
|
53
|
+
#
|
|
54
|
+
# @rbs () -> bool
|
|
55
|
+
def available?
|
|
56
|
+
available.positive?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns the number of retry attempts already made.
|
|
60
|
+
#
|
|
61
|
+
# @return [Integer] the current retry attempt count
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# retry_instance.attempts # => 1
|
|
65
|
+
#
|
|
66
|
+
# @rbs () -> Integer
|
|
67
|
+
def attempts
|
|
68
|
+
Integer(task.result.retries || 0)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Checks if the task has been retried at least once.
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if at least one retry has occurred
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# retry_instance.retried? # => true
|
|
77
|
+
#
|
|
78
|
+
# @rbs () -> bool
|
|
79
|
+
def retried?
|
|
80
|
+
attempts.positive?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns the number of retries still available.
|
|
84
|
+
#
|
|
85
|
+
# @return [Integer] the remaining retry count
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# retry_instance.remaining # => 2
|
|
89
|
+
#
|
|
90
|
+
# @rbs () -> Integer
|
|
91
|
+
def remaining
|
|
92
|
+
available - attempts
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Checks if there are retries still available.
|
|
96
|
+
#
|
|
97
|
+
# @return [Boolean] true if remaining retries exist
|
|
98
|
+
#
|
|
99
|
+
# @example
|
|
100
|
+
# retry_instance.remaining? # => true
|
|
101
|
+
#
|
|
102
|
+
# @rbs () -> bool
|
|
103
|
+
def remaining?
|
|
104
|
+
remaining.positive?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns the list of exception classes eligible for retry.
|
|
108
|
+
#
|
|
109
|
+
# @return [Array<Class>] exception classes that trigger a retry
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# retry_instance.exceptions # => [StandardError, CMDx::TimeoutError]
|
|
113
|
+
#
|
|
114
|
+
# @rbs () -> Array[Class]
|
|
115
|
+
def exceptions
|
|
116
|
+
@exceptions ||= Utils::Wrap.array(
|
|
117
|
+
task.class.settings.retry_on ||
|
|
118
|
+
[StandardError, CMDx::TimeoutError]
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Checks if the given exception matches any configured retry exception.
|
|
123
|
+
#
|
|
124
|
+
# @param exception [Exception] the exception to check
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean] true if the exception qualifies for retry
|
|
127
|
+
#
|
|
128
|
+
# @example
|
|
129
|
+
# retry_instance.exception?(RuntimeError.new("fail")) # => true
|
|
130
|
+
#
|
|
131
|
+
# @rbs (Exception exception) -> bool
|
|
132
|
+
def exception?(exception)
|
|
133
|
+
exceptions.any? { |e| exception.class <= e }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Computes the wait time before the next retry attempt.
|
|
137
|
+
#
|
|
138
|
+
# Supports multiple jitter strategies: a Symbol calls a task method,
|
|
139
|
+
# a Proc is evaluated in the task instance context, a callable object
|
|
140
|
+
# receives the task and attempts, and a Numeric is multiplied by the
|
|
141
|
+
# attempt count.
|
|
142
|
+
#
|
|
143
|
+
# @return [Float] the wait duration in seconds
|
|
144
|
+
#
|
|
145
|
+
# @example With numeric jitter (0.5 * attempts)
|
|
146
|
+
# retry_instance.wait # => 1.0
|
|
147
|
+
# @example With symbol jitter referencing a task method
|
|
148
|
+
# retry_instance.wait # => 2.5
|
|
149
|
+
#
|
|
150
|
+
# @rbs () -> Float
|
|
151
|
+
def wait
|
|
152
|
+
jitter = task.class.settings.retry_jitter
|
|
153
|
+
|
|
154
|
+
if jitter.is_a?(Symbol)
|
|
155
|
+
task.send(jitter, attempts)
|
|
156
|
+
elsif jitter.is_a?(Proc)
|
|
157
|
+
task.instance_exec(attempts, &jitter)
|
|
158
|
+
elsif jitter.respond_to?(:call)
|
|
159
|
+
jitter.call(task, attempts)
|
|
160
|
+
else
|
|
161
|
+
jitter.to_f * attempts
|
|
162
|
+
end.to_f
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
# Value object encapsulating all per-task configuration. Registries are
|
|
5
|
+
# deep-duped on inheritance; scalar settings delegate to a parent Settings
|
|
6
|
+
# or to the global Configuration rather than eagerly copying values.
|
|
7
|
+
class Settings
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Defines a reader that delegates to the parent Settings chain,
|
|
14
|
+
# falling through to Configuration when no parent exists.
|
|
15
|
+
#
|
|
16
|
+
# @param names [Array<Symbol>] Setting names to define
|
|
17
|
+
#
|
|
18
|
+
# @rbs (*Symbol names) -> void
|
|
19
|
+
def delegate_to_configuration(*names)
|
|
20
|
+
names.each do |name|
|
|
21
|
+
ivar = :"@#{name}"
|
|
22
|
+
|
|
23
|
+
attr_writer(name)
|
|
24
|
+
|
|
25
|
+
define_method(name) do
|
|
26
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
27
|
+
|
|
28
|
+
value = @parent ? @parent.public_send(name) : CMDx.configuration.public_send(name)
|
|
29
|
+
instance_variable_set(ivar, value)
|
|
30
|
+
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Defines a reader that delegates to the parent Settings only.
|
|
37
|
+
# Returns nil when the chain is exhausted.
|
|
38
|
+
#
|
|
39
|
+
# @param names [Array<Symbol>] Setting names to define
|
|
40
|
+
# @param with_fallback [Boolean] Whether to fall back to Configuration
|
|
41
|
+
#
|
|
42
|
+
# @rbs (*Symbol names, with_fallback: bool) -> void
|
|
43
|
+
def delegate_to_parent(*names, with_fallback: false)
|
|
44
|
+
names.each do |name|
|
|
45
|
+
ivar = :"@#{name}"
|
|
46
|
+
|
|
47
|
+
attr_writer(name)
|
|
48
|
+
|
|
49
|
+
define_method(name) do
|
|
50
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
51
|
+
|
|
52
|
+
value = @parent&.public_send(name)
|
|
53
|
+
value ||= CMDx.configuration.public_send(name) if with_fallback
|
|
54
|
+
instance_variable_set(ivar, value)
|
|
55
|
+
|
|
56
|
+
value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the attribute registry for task parameters.
|
|
64
|
+
#
|
|
65
|
+
# @return [AttributeRegistry] The attribute registry
|
|
66
|
+
#
|
|
67
|
+
# @rbs @attributes: AttributeRegistry
|
|
68
|
+
attr_accessor :attributes
|
|
69
|
+
|
|
70
|
+
# Returns the callback registry for task lifecycle hooks.
|
|
71
|
+
#
|
|
72
|
+
# @return [CallbackRegistry] The callback registry
|
|
73
|
+
#
|
|
74
|
+
# @rbs @callbacks: CallbackRegistry
|
|
75
|
+
attr_accessor :callbacks
|
|
76
|
+
|
|
77
|
+
# Returns the coercion registry for type conversions.
|
|
78
|
+
#
|
|
79
|
+
# @return [CoercionRegistry] The coercion registry
|
|
80
|
+
#
|
|
81
|
+
# @rbs @coercions: CoercionRegistry
|
|
82
|
+
attr_accessor :coercions
|
|
83
|
+
|
|
84
|
+
# Returns the middleware registry for task execution.
|
|
85
|
+
#
|
|
86
|
+
# @return [MiddlewareRegistry] The middleware registry
|
|
87
|
+
#
|
|
88
|
+
# @rbs @middlewares: MiddlewareRegistry
|
|
89
|
+
attr_accessor :middlewares
|
|
90
|
+
|
|
91
|
+
# Returns the validator registry for attribute validation.
|
|
92
|
+
#
|
|
93
|
+
# @return [ValidatorRegistry] The validator registry
|
|
94
|
+
#
|
|
95
|
+
# @rbs @validators: ValidatorRegistry
|
|
96
|
+
attr_accessor :validators
|
|
97
|
+
|
|
98
|
+
# Returns the expected return keys after execution.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Symbol>] Expected return keys after execution
|
|
101
|
+
#
|
|
102
|
+
# @rbs @returns: Array[Symbol]
|
|
103
|
+
attr_accessor :returns
|
|
104
|
+
|
|
105
|
+
# Returns the tags for task categorization.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<Symbol>] Tags for categorization
|
|
108
|
+
#
|
|
109
|
+
# @rbs @tags: Array[Symbol]
|
|
110
|
+
attr_accessor :tags
|
|
111
|
+
|
|
112
|
+
# @!attribute [rw] backtrace
|
|
113
|
+
# @return [Boolean] true if backtraces should be logged
|
|
114
|
+
delegate_to_configuration :backtrace
|
|
115
|
+
|
|
116
|
+
# @!attribute [rw] rollback_on
|
|
117
|
+
# @return [Array<String>] Statuses that trigger rollback
|
|
118
|
+
delegate_to_configuration :rollback_on
|
|
119
|
+
|
|
120
|
+
# @!attribute [rw] task_breakpoints
|
|
121
|
+
# @return [Array<String>] Default task breakpoint statuses
|
|
122
|
+
delegate_to_configuration :task_breakpoints
|
|
123
|
+
|
|
124
|
+
# @!attribute [rw] workflow_breakpoints
|
|
125
|
+
# @return [Array<String>] Default workflow breakpoint statuses
|
|
126
|
+
delegate_to_configuration :workflow_breakpoints
|
|
127
|
+
|
|
128
|
+
# @!attribute [rw] backtrace_cleaner
|
|
129
|
+
# @return [Proc, nil] The backtrace cleaner proc
|
|
130
|
+
delegate_to_parent :backtrace_cleaner, with_fallback: true
|
|
131
|
+
|
|
132
|
+
# @!attribute [rw] breakpoints
|
|
133
|
+
# @return [Array<String>, nil] Per-task breakpoints override
|
|
134
|
+
delegate_to_parent :breakpoints
|
|
135
|
+
|
|
136
|
+
# @!attribute [rw] deprecate
|
|
137
|
+
# @return [Symbol, Proc, Boolean, nil] Deprecation behavior
|
|
138
|
+
delegate_to_parent :deprecate
|
|
139
|
+
|
|
140
|
+
# @!attribute [rw] exception_handler
|
|
141
|
+
# @return [Proc, nil] The exception handler proc
|
|
142
|
+
delegate_to_parent :exception_handler, with_fallback: true
|
|
143
|
+
|
|
144
|
+
# @!attribute [rw] logger
|
|
145
|
+
# @return [Logger] The logger instance
|
|
146
|
+
delegate_to_parent :logger, with_fallback: true
|
|
147
|
+
|
|
148
|
+
# @!attribute [rw] log_formatter
|
|
149
|
+
# @return [Proc, nil] Per-task log formatter override
|
|
150
|
+
delegate_to_parent :log_formatter
|
|
151
|
+
|
|
152
|
+
# @!attribute [rw] log_level
|
|
153
|
+
# @return [Integer, nil] Per-task log level override
|
|
154
|
+
delegate_to_parent :log_level
|
|
155
|
+
|
|
156
|
+
# @!attribute [rw] retries
|
|
157
|
+
# @return [Integer, nil] Number of retries on failure
|
|
158
|
+
delegate_to_parent :retries
|
|
159
|
+
|
|
160
|
+
# @!attribute [rw] retry_jitter
|
|
161
|
+
# @return [Numeric, Symbol, Proc, nil] Jitter between retries
|
|
162
|
+
delegate_to_parent :retry_jitter
|
|
163
|
+
|
|
164
|
+
# @!attribute [rw] retry_on
|
|
165
|
+
# @return [Array<Class>, Class, nil] Exception classes to retry on
|
|
166
|
+
delegate_to_parent :retry_on
|
|
167
|
+
|
|
168
|
+
# Creates a new Settings instance, inheriting registries from a parent
|
|
169
|
+
# Settings or the global Configuration. Scalar settings are resolved
|
|
170
|
+
# lazily via delegation rather than eagerly copied.
|
|
171
|
+
#
|
|
172
|
+
# @param parent [Settings, nil] Parent settings to inherit from
|
|
173
|
+
# @param overrides [Hash] Field values to override after inheritance
|
|
174
|
+
#
|
|
175
|
+
# @example
|
|
176
|
+
# Settings.new(parent: ParentTask.settings, deprecate: true)
|
|
177
|
+
#
|
|
178
|
+
# @rbs (?parent: Settings?, **untyped overrides) -> void
|
|
179
|
+
def initialize(parent: nil, **overrides)
|
|
180
|
+
@parent = parent
|
|
181
|
+
|
|
182
|
+
init_registries
|
|
183
|
+
init_collections
|
|
184
|
+
|
|
185
|
+
overrides.each { |key, value| public_send(:"#{key}=", value) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
private
|
|
189
|
+
|
|
190
|
+
# Dups registries from the parent Settings or global Configuration
|
|
191
|
+
# so each task class gets its own mutable copy.
|
|
192
|
+
#
|
|
193
|
+
# @rbs () -> void
|
|
194
|
+
def init_registries
|
|
195
|
+
if @parent
|
|
196
|
+
@middlewares = @parent.middlewares.dup
|
|
197
|
+
@callbacks = @parent.callbacks.dup
|
|
198
|
+
@coercions = @parent.coercions.dup
|
|
199
|
+
@validators = @parent.validators.dup
|
|
200
|
+
@attributes = @parent.attributes.dup
|
|
201
|
+
else
|
|
202
|
+
config = CMDx.configuration
|
|
203
|
+
|
|
204
|
+
@middlewares = config.middlewares.dup
|
|
205
|
+
@callbacks = config.callbacks.dup
|
|
206
|
+
@coercions = config.coercions.dup
|
|
207
|
+
@validators = config.validators.dup
|
|
208
|
+
@attributes = AttributeRegistry.new
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Initializes array-valued settings that need their own copy
|
|
213
|
+
# to avoid cross-class mutation.
|
|
214
|
+
#
|
|
215
|
+
# @rbs () -> void
|
|
216
|
+
def init_collections
|
|
217
|
+
@returns = @parent&.returns&.dup || EMPTY_ARRAY
|
|
218
|
+
@tags = @parent&.tags&.dup || EMPTY_ARRAY
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
end
|