cmdx 1.21.0 → 2.0.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/CHANGELOG.md +118 -1
- data/README.md +37 -24
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callbacks.rb +179 -0
- data/lib/cmdx/chain.rb +78 -175
- data/lib/cmdx/coercions/array.rb +19 -33
- data/lib/cmdx/coercions/big_decimal.rb +12 -29
- data/lib/cmdx/coercions/boolean.rb +25 -45
- data/lib/cmdx/coercions/coerce.rb +32 -0
- data/lib/cmdx/coercions/complex.rb +12 -27
- data/lib/cmdx/coercions/date.rb +29 -33
- data/lib/cmdx/coercions/date_time.rb +29 -33
- data/lib/cmdx/coercions/float.rb +8 -29
- data/lib/cmdx/coercions/hash.rb +17 -43
- data/lib/cmdx/coercions/integer.rb +8 -32
- data/lib/cmdx/coercions/rational.rb +12 -33
- data/lib/cmdx/coercions/string.rb +6 -24
- data/lib/cmdx/coercions/symbol.rb +12 -26
- data/lib/cmdx/coercions/time.rb +31 -35
- data/lib/cmdx/coercions.rb +174 -0
- data/lib/cmdx/configuration.rb +45 -237
- data/lib/cmdx/context.rb +264 -243
- data/lib/cmdx/deprecation.rb +67 -0
- data/lib/cmdx/deprecators/error.rb +22 -0
- data/lib/cmdx/deprecators/log.rb +22 -0
- data/lib/cmdx/deprecators/warn.rb +21 -0
- data/lib/cmdx/deprecators.rb +101 -0
- data/lib/cmdx/errors.rb +145 -79
- data/lib/cmdx/executors/fiber.rb +42 -0
- data/lib/cmdx/executors/thread.rb +36 -0
- data/lib/cmdx/executors.rb +95 -0
- data/lib/cmdx/fault.rb +85 -78
- data/lib/cmdx/i18n_proxy.rb +104 -0
- data/lib/cmdx/input.rb +294 -0
- data/lib/cmdx/inputs.rb +218 -0
- data/lib/cmdx/log_formatters/json.rb +9 -20
- data/lib/cmdx/log_formatters/key_value.rb +10 -21
- data/lib/cmdx/log_formatters/line.rb +7 -19
- data/lib/cmdx/log_formatters/logstash.rb +8 -21
- data/lib/cmdx/log_formatters/raw.rb +8 -20
- data/lib/cmdx/logger_proxy.rb +30 -0
- data/lib/cmdx/mergers/deep_merge.rb +23 -0
- data/lib/cmdx/mergers/last_write_wins.rb +23 -0
- data/lib/cmdx/mergers/no_merge.rb +20 -0
- data/lib/cmdx/mergers.rb +95 -0
- data/lib/cmdx/middlewares.rb +128 -0
- data/lib/cmdx/output.rb +115 -0
- data/lib/cmdx/outputs.rb +66 -0
- data/lib/cmdx/pipeline.rb +144 -131
- data/lib/cmdx/railtie.rb +10 -36
- data/lib/cmdx/result.rb +247 -524
- data/lib/cmdx/retriers/bounded_random.rb +24 -0
- data/lib/cmdx/retriers/decorrelated_jitter.rb +28 -0
- data/lib/cmdx/retriers/exponential.rb +23 -0
- data/lib/cmdx/retriers/fibonacci.rb +39 -0
- data/lib/cmdx/retriers/full_random.rb +23 -0
- data/lib/cmdx/retriers/half_random.rb +24 -0
- data/lib/cmdx/retriers/linear.rb +23 -0
- data/lib/cmdx/retriers.rb +106 -0
- data/lib/cmdx/retry.rb +117 -138
- data/lib/cmdx/runtime.rb +251 -0
- data/lib/cmdx/settings.rb +68 -200
- data/lib/cmdx/signal.rb +165 -0
- data/lib/cmdx/task.rb +443 -343
- data/lib/cmdx/telemetry.rb +108 -0
- data/lib/cmdx/util.rb +73 -0
- data/lib/cmdx/validators/absence.rb +10 -39
- data/lib/cmdx/validators/exclusion.rb +33 -52
- data/lib/cmdx/validators/format.rb +19 -49
- data/lib/cmdx/validators/inclusion.rb +33 -54
- data/lib/cmdx/validators/length.rb +125 -127
- data/lib/cmdx/validators/numeric.rb +123 -123
- data/lib/cmdx/validators/presence.rb +10 -39
- data/lib/cmdx/validators/validate.rb +31 -0
- data/lib/cmdx/validators.rb +161 -0
- data/lib/cmdx/version.rb +2 -4
- data/lib/cmdx/workflow.rb +71 -96
- data/lib/cmdx.rb +111 -42
- data/lib/generators/cmdx/install_generator.rb +7 -17
- data/lib/generators/cmdx/task_generator.rb +12 -29
- data/lib/generators/cmdx/templates/install.rb +120 -48
- data/lib/generators/cmdx/templates/task.rb.tt +1 -1
- data/lib/generators/cmdx/templates/workflow.rb.tt +1 -2
- data/lib/generators/cmdx/workflow_generator.rb +12 -29
- data/lib/locales/en.yml +8 -7
- data/mkdocs.yml +25 -23
- metadata +39 -138
- data/lib/cmdx/attribute.rb +0 -440
- data/lib/cmdx/attribute_registry.rb +0 -185
- data/lib/cmdx/attribute_value.rb +0 -252
- data/lib/cmdx/callback_registry.rb +0 -169
- data/lib/cmdx/coercion_registry.rb +0 -138
- data/lib/cmdx/deprecator.rb +0 -77
- data/lib/cmdx/exception.rb +0 -46
- data/lib/cmdx/executor.rb +0 -378
- data/lib/cmdx/identifier.rb +0 -30
- data/lib/cmdx/locale.rb +0 -78
- data/lib/cmdx/middleware_registry.rb +0 -148
- data/lib/cmdx/middlewares/correlate.rb +0 -140
- data/lib/cmdx/middlewares/runtime.rb +0 -77
- data/lib/cmdx/middlewares/timeout.rb +0 -78
- data/lib/cmdx/parallelizer.rb +0 -100
- data/lib/cmdx/utils/call.rb +0 -53
- data/lib/cmdx/utils/condition.rb +0 -71
- data/lib/cmdx/utils/format.rb +0 -82
- data/lib/cmdx/utils/normalize.rb +0 -52
- data/lib/cmdx/utils/wrap.rb +0 -38
- data/lib/cmdx/validator_registry.rb +0 -143
- data/lib/generators/cmdx/locale_generator.rb +0 -39
- data/lib/locales/af.yml +0 -55
- data/lib/locales/ar.yml +0 -55
- data/lib/locales/az.yml +0 -55
- data/lib/locales/be.yml +0 -55
- data/lib/locales/bg.yml +0 -55
- data/lib/locales/bn.yml +0 -55
- data/lib/locales/bs.yml +0 -55
- data/lib/locales/ca.yml +0 -55
- data/lib/locales/cnr.yml +0 -55
- data/lib/locales/cs.yml +0 -55
- data/lib/locales/cy.yml +0 -55
- data/lib/locales/da.yml +0 -55
- data/lib/locales/de.yml +0 -55
- data/lib/locales/dz.yml +0 -55
- data/lib/locales/el.yml +0 -55
- data/lib/locales/eo.yml +0 -55
- data/lib/locales/es.yml +0 -55
- data/lib/locales/et.yml +0 -55
- data/lib/locales/eu.yml +0 -55
- data/lib/locales/fa.yml +0 -55
- data/lib/locales/fi.yml +0 -55
- data/lib/locales/fr.yml +0 -55
- data/lib/locales/fy.yml +0 -55
- data/lib/locales/gd.yml +0 -55
- data/lib/locales/gl.yml +0 -55
- data/lib/locales/he.yml +0 -55
- data/lib/locales/hi.yml +0 -55
- data/lib/locales/hr.yml +0 -55
- data/lib/locales/hu.yml +0 -55
- data/lib/locales/hy.yml +0 -55
- data/lib/locales/id.yml +0 -55
- data/lib/locales/is.yml +0 -55
- data/lib/locales/it.yml +0 -55
- data/lib/locales/ja.yml +0 -55
- data/lib/locales/ka.yml +0 -55
- data/lib/locales/kk.yml +0 -55
- data/lib/locales/km.yml +0 -55
- data/lib/locales/kn.yml +0 -55
- data/lib/locales/ko.yml +0 -55
- data/lib/locales/lb.yml +0 -55
- data/lib/locales/lo.yml +0 -55
- data/lib/locales/lt.yml +0 -55
- data/lib/locales/lv.yml +0 -55
- data/lib/locales/mg.yml +0 -55
- data/lib/locales/mk.yml +0 -55
- data/lib/locales/ml.yml +0 -55
- data/lib/locales/mn.yml +0 -55
- data/lib/locales/mr-IN.yml +0 -55
- data/lib/locales/ms.yml +0 -55
- data/lib/locales/nb.yml +0 -55
- data/lib/locales/ne.yml +0 -55
- data/lib/locales/nl.yml +0 -55
- data/lib/locales/nn.yml +0 -55
- data/lib/locales/oc.yml +0 -55
- data/lib/locales/or.yml +0 -55
- data/lib/locales/pa.yml +0 -55
- data/lib/locales/pl.yml +0 -55
- data/lib/locales/pt.yml +0 -55
- data/lib/locales/rm.yml +0 -55
- data/lib/locales/ro.yml +0 -55
- data/lib/locales/ru.yml +0 -55
- data/lib/locales/sc.yml +0 -55
- data/lib/locales/sk.yml +0 -55
- data/lib/locales/sl.yml +0 -55
- data/lib/locales/sq.yml +0 -55
- data/lib/locales/sr.yml +0 -55
- data/lib/locales/st.yml +0 -55
- data/lib/locales/sv.yml +0 -55
- data/lib/locales/sw.yml +0 -55
- data/lib/locales/ta.yml +0 -55
- data/lib/locales/te.yml +0 -55
- data/lib/locales/th.yml +0 -55
- data/lib/locales/tl.yml +0 -55
- data/lib/locales/tr.yml +0 -55
- data/lib/locales/tt.yml +0 -55
- data/lib/locales/ug.yml +0 -55
- data/lib/locales/uk.yml +0 -55
- data/lib/locales/ur.yml +0 -55
- data/lib/locales/uz.yml +0 -55
- data/lib/locales/vi.yml +0 -55
- data/lib/locales/wo.yml +0 -55
- data/lib/locales/zh-CN.yml +0 -55
- data/lib/locales/zh-HK.yml +0 -55
- data/lib/locales/zh-TW.yml +0 -55
- data/lib/locales/zh-YUE.yml +0 -55
data/lib/cmdx/runtime.rb
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CMDx
|
|
4
|
+
# Orchestrates a task's full lifecycle: chain acquisition, middlewares,
|
|
5
|
+
# telemetry, deprecation, callbacks, input resolution, `work` (wrapped in
|
|
6
|
+
# retry), output verification, rollback on failure, result finalization,
|
|
7
|
+
# and teardown (freeze + chain clear).
|
|
8
|
+
#
|
|
9
|
+
# Signal propagation: Runtime wraps `work` in `catch(Signal::TAG)` so
|
|
10
|
+
# `success!` / `skip!` / `fail!` / `throw!` break out cleanly. Raised
|
|
11
|
+
# Faults are converted to echoed signals (carrying the upstream failed
|
|
12
|
+
# result as `:origin`); other `StandardError`s become failed signals with
|
|
13
|
+
# the exception as `:cause`. `execute!` (strict mode) re-raises on failure
|
|
14
|
+
# after the result is finalized, raising a {Fault} built from the deepest
|
|
15
|
+
# originating result so `fault.task` points at the leaf that failed.
|
|
16
|
+
#
|
|
17
|
+
# @note Always used via the class method; never new Runtime manually.
|
|
18
|
+
# @see Task.execute
|
|
19
|
+
# @see Task.execute!
|
|
20
|
+
class Runtime
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
|
|
24
|
+
# @param task [Task]
|
|
25
|
+
# @param strict [Boolean] when true, re-raise on failure (`execute!` semantics)
|
|
26
|
+
# @return [Result] the finalized, frozen result
|
|
27
|
+
# @raise [Fault, StandardError] only when `strict: true` and the task failed
|
|
28
|
+
def execute(task, strict: false)
|
|
29
|
+
new(task, strict:).execute
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param task [Task]
|
|
35
|
+
# @param strict [Boolean]
|
|
36
|
+
def initialize(task, strict: false)
|
|
37
|
+
@task = task
|
|
38
|
+
@strict = strict
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Runs the full lifecycle. Teardown runs in `ensure`, guaranteeing the
|
|
42
|
+
# task's context/errors get frozen and the fiber chain is cleared even
|
|
43
|
+
# when strict mode re-raises.
|
|
44
|
+
#
|
|
45
|
+
# @return [Result]
|
|
46
|
+
# @raise [Fault, StandardError] under strict mode on failure
|
|
47
|
+
def execute
|
|
48
|
+
acquire_chain
|
|
49
|
+
|
|
50
|
+
run_middlewares do
|
|
51
|
+
emit_telemetry(:task_started)
|
|
52
|
+
run_deprecation
|
|
53
|
+
run_lifecycle
|
|
54
|
+
finalize_result
|
|
55
|
+
raise_signal! if @strict
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
@result
|
|
59
|
+
ensure
|
|
60
|
+
run_teardown
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def acquire_chain
|
|
66
|
+
@root = Chain.current.nil?
|
|
67
|
+
return unless @root
|
|
68
|
+
|
|
69
|
+
xid = @task.class.settings.correlation_id&.call
|
|
70
|
+
Chain.current = Chain.new(xid)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def run_middlewares(&)
|
|
74
|
+
middlewares = @task.class.middlewares
|
|
75
|
+
return yield if middlewares.empty?
|
|
76
|
+
|
|
77
|
+
middlewares.process(@task, &)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def run_deprecation
|
|
81
|
+
deprecation = @task.class.deprecation
|
|
82
|
+
return unless deprecation
|
|
83
|
+
|
|
84
|
+
deprecation.execute(@task) do
|
|
85
|
+
@deprecated = true
|
|
86
|
+
emit_telemetry(:task_deprecated)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def run_lifecycle
|
|
91
|
+
measure_duration do
|
|
92
|
+
run_callbacks(:before_execution)
|
|
93
|
+
run_around(:around_execution) do
|
|
94
|
+
run_callbacks(:before_validation)
|
|
95
|
+
perform_work
|
|
96
|
+
perform_rollback if @signal.failed?
|
|
97
|
+
run_callbacks(:after_execution)
|
|
98
|
+
end
|
|
99
|
+
run_callbacks(:"on_#{@signal.state}")
|
|
100
|
+
run_callbacks(:"on_#{@signal.status}")
|
|
101
|
+
run_callbacks(:on_ok) if @signal.ok?
|
|
102
|
+
run_callbacks(:on_ko) if @signal.ko?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def raise_signal!
|
|
107
|
+
return unless @result.failed?
|
|
108
|
+
|
|
109
|
+
cause = @signal.cause
|
|
110
|
+
raise cause if cause && !cause.is_a?(Fault)
|
|
111
|
+
|
|
112
|
+
raise Fault, @result.caused_failure
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def finalize_result
|
|
116
|
+
@result = Result.new(
|
|
117
|
+
Chain.current,
|
|
118
|
+
@task,
|
|
119
|
+
@signal,
|
|
120
|
+
root: @root,
|
|
121
|
+
tid: @task.tid,
|
|
122
|
+
strict: @strict,
|
|
123
|
+
deprecated: @deprecated,
|
|
124
|
+
rolled_back: @rolled_back,
|
|
125
|
+
retries: @retries,
|
|
126
|
+
duration: @duration
|
|
127
|
+
).tap do |result|
|
|
128
|
+
@root ? Chain.current.unshift(result) : Chain.current.push(result)
|
|
129
|
+
emit_telemetry(:task_executed, result:)
|
|
130
|
+
@task.logger.info do
|
|
131
|
+
exclusions = @task.class.settings.log_exclusions
|
|
132
|
+
exclusions.empty? ? result : result.to_h.except(*exclusions)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def run_teardown
|
|
138
|
+
@task.context.freeze if @root
|
|
139
|
+
@task.errors.freeze
|
|
140
|
+
@task.freeze
|
|
141
|
+
return unless @root
|
|
142
|
+
|
|
143
|
+
Chain.current.freeze
|
|
144
|
+
Chain.clear
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def measure_duration
|
|
148
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
149
|
+
yield
|
|
150
|
+
ensure
|
|
151
|
+
@duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @param event [Symbol] callback event from {Callbacks::EVENTS}
|
|
155
|
+
# @return [void]
|
|
156
|
+
def run_callbacks(event)
|
|
157
|
+
callbacks = @task.class.callbacks
|
|
158
|
+
return if callbacks.empty?
|
|
159
|
+
|
|
160
|
+
callbacks.process(event, @task)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @param event [Symbol] callback event from {Callbacks::EVENTS}
|
|
164
|
+
# @yield nested lifecycle segment wrapped by `around_execution` callbacks
|
|
165
|
+
# @return [Object] the wrapped block's return value
|
|
166
|
+
def run_around(event, &)
|
|
167
|
+
callbacks = @task.class.callbacks
|
|
168
|
+
return yield if callbacks.empty?
|
|
169
|
+
|
|
170
|
+
callbacks.around(event, @task, &)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def perform_work
|
|
174
|
+
@signal = catch(Signal::TAG) do
|
|
175
|
+
resolve_inputs!
|
|
176
|
+
retry_execution { @task.work }
|
|
177
|
+
verify_outputs!
|
|
178
|
+
Signal.success(nil, metadata: @task.metadata)
|
|
179
|
+
rescue Fault => e
|
|
180
|
+
Signal.echoed(e.result, cause: e, metadata: @task.metadata)
|
|
181
|
+
rescue Error => e
|
|
182
|
+
raise(e)
|
|
183
|
+
rescue StandardError => e
|
|
184
|
+
Signal.failed("[#{e.class}] #{e.message}", cause: e, metadata: @task.metadata)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def perform_rollback
|
|
189
|
+
return unless @task.respond_to?(:rollback)
|
|
190
|
+
|
|
191
|
+
@rolled_back = true
|
|
192
|
+
emit_telemetry(:task_rolled_back)
|
|
193
|
+
@task.rollback
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def resolve_inputs!
|
|
197
|
+
inputs = @task.class.inputs
|
|
198
|
+
return if inputs.empty?
|
|
199
|
+
|
|
200
|
+
inputs.resolve(@task)
|
|
201
|
+
signal_errors!
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def retry_execution
|
|
205
|
+
@task.class.retry_on.process(@task) do |attempt|
|
|
206
|
+
@retries = attempt
|
|
207
|
+
emit_telemetry(:task_retried, attempt:) if attempt.positive?
|
|
208
|
+
yield
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
signal_errors!
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def verify_outputs!
|
|
215
|
+
outputs = @task.class.outputs
|
|
216
|
+
return if outputs.empty?
|
|
217
|
+
|
|
218
|
+
outputs.verify(@task)
|
|
219
|
+
signal_errors!
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def signal_errors!
|
|
223
|
+
return if @task.errors.empty?
|
|
224
|
+
|
|
225
|
+
throw(Signal::TAG, Signal.failed(@task.errors.to_s, metadata: @task.metadata))
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# @param name [Symbol] telemetry channel from {Telemetry::EVENTS}
|
|
229
|
+
# @param payload [Hash{Symbol => Object}] forwarded onto {Telemetry::Event#payload}
|
|
230
|
+
# @return [void]
|
|
231
|
+
def emit_telemetry(name, payload = EMPTY_HASH)
|
|
232
|
+
telemetry = @task.class.telemetry
|
|
233
|
+
return unless telemetry.subscribed?(name)
|
|
234
|
+
|
|
235
|
+
event = Telemetry::Event.new(
|
|
236
|
+
xid: Chain.current.xid,
|
|
237
|
+
cid: Chain.current.id,
|
|
238
|
+
root: @root,
|
|
239
|
+
type: @task.class.type,
|
|
240
|
+
task: @task.class,
|
|
241
|
+
tid: @task.tid,
|
|
242
|
+
name:,
|
|
243
|
+
payload:,
|
|
244
|
+
timestamp: Time.now.utc
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
telemetry.emit(name, event)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
end
|
|
251
|
+
end
|
data/lib/cmdx/settings.rb
CHANGED
|
@@ -1,225 +1,93 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CMDx
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Per-task configuration overrides. Options are frozen on construction;
|
|
5
|
+
# {#build} returns a new instance rather than mutating. Every getter falls
|
|
6
|
+
# back to {CMDx.configuration} when the option wasn't set on the task.
|
|
7
7
|
class Settings
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
names.each do |name|
|
|
21
|
-
ivar = :"@#{name}"
|
|
22
|
-
|
|
23
|
-
attr_writer(name)
|
|
9
|
+
# @param options [Hash{Symbol => Object}] task-specific overrides
|
|
10
|
+
# @option options [Logger] :logger
|
|
11
|
+
# @option options [#call] :log_formatter
|
|
12
|
+
# @option options [Integer] :log_level
|
|
13
|
+
# @option options [#call] :backtrace_cleaner
|
|
14
|
+
# @option options [Array<Symbol>] :log_exclusions
|
|
15
|
+
# @option options [Array<Symbol, String>] :tags
|
|
16
|
+
# @option options [Boolean] :strict_context
|
|
17
|
+
def initialize(options = EMPTY_HASH)
|
|
18
|
+
@options = options.freeze
|
|
19
|
+
end
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
# Returns a new Settings with `new_options` merged on top. Returns `self`
|
|
22
|
+
# unchanged when `new_options` is empty (used by Task inheritance).
|
|
23
|
+
#
|
|
24
|
+
# @param new_options [Hash{Symbol => Object}] overrides to layer on top
|
|
25
|
+
# @return [Settings] merged instance (or `self` when no changes)
|
|
26
|
+
def build(new_options)
|
|
27
|
+
return self if new_options.empty?
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
self.class.new(@options.merge(new_options))
|
|
30
|
+
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# @return [Logger] task-level logger or the global configuration's logger
|
|
33
|
+
def logger
|
|
34
|
+
@options.fetch(:logger) do
|
|
35
|
+
CMDx.configuration.logger
|
|
34
36
|
end
|
|
37
|
+
end
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
39
|
+
# @return [#call] Logger formatter used when logging task results
|
|
40
|
+
def log_formatter
|
|
41
|
+
@options.fetch(:log_formatter) do
|
|
42
|
+
CMDx.configuration.log_formatter
|
|
59
43
|
end
|
|
60
|
-
|
|
61
44
|
end
|
|
62
45
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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] dump_context
|
|
117
|
-
# @return [Boolean] true if context should be included in Task#to_h
|
|
118
|
-
delegate_to_configuration :dump_context
|
|
119
|
-
|
|
120
|
-
# @!attribute [rw] rollback_on
|
|
121
|
-
# @return [Array<String>] Statuses that trigger rollback
|
|
122
|
-
delegate_to_configuration :rollback_on
|
|
123
|
-
|
|
124
|
-
# @!attribute [rw] task_breakpoints
|
|
125
|
-
# @return [Array<String>] Default task breakpoint statuses
|
|
126
|
-
delegate_to_configuration :task_breakpoints
|
|
127
|
-
|
|
128
|
-
# @!attribute [rw] workflow_breakpoints
|
|
129
|
-
# @return [Array<String>] Default workflow breakpoint statuses
|
|
130
|
-
delegate_to_configuration :workflow_breakpoints
|
|
131
|
-
|
|
132
|
-
# @!attribute [rw] backtrace_cleaner
|
|
133
|
-
# @return [Proc, nil] The backtrace cleaner proc
|
|
134
|
-
delegate_to_parent :backtrace_cleaner, with_fallback: true
|
|
135
|
-
|
|
136
|
-
# @!attribute [rw] breakpoints
|
|
137
|
-
# @return [Array<String>, nil] Per-task breakpoints override
|
|
138
|
-
delegate_to_parent :breakpoints
|
|
139
|
-
|
|
140
|
-
# @!attribute [rw] deprecate
|
|
141
|
-
# @return [Symbol, Proc, Boolean, nil] Deprecation behavior
|
|
142
|
-
delegate_to_parent :deprecate
|
|
143
|
-
|
|
144
|
-
# @!attribute [rw] exception_handler
|
|
145
|
-
# @return [Proc, nil] The exception handler proc
|
|
146
|
-
delegate_to_parent :exception_handler, with_fallback: true
|
|
147
|
-
|
|
148
|
-
# @!attribute [rw] logger
|
|
149
|
-
# @return [Logger] The logger instance
|
|
150
|
-
delegate_to_parent :logger, with_fallback: true
|
|
151
|
-
|
|
152
|
-
# @!attribute [rw] log_formatter
|
|
153
|
-
# @return [Proc, nil] Per-task log formatter override
|
|
154
|
-
delegate_to_parent :log_formatter
|
|
155
|
-
|
|
156
|
-
# @!attribute [rw] log_level
|
|
157
|
-
# @return [Integer, nil] Per-task log level override
|
|
158
|
-
delegate_to_parent :log_level
|
|
159
|
-
|
|
160
|
-
# @!attribute [rw] retries
|
|
161
|
-
# @return [Integer, nil] Number of retries on failure
|
|
162
|
-
delegate_to_parent :retries
|
|
163
|
-
|
|
164
|
-
# @!attribute [rw] retry_jitter
|
|
165
|
-
# @return [Numeric, Symbol, Proc, nil] Jitter between retries
|
|
166
|
-
delegate_to_parent :retry_jitter
|
|
167
|
-
|
|
168
|
-
# @!attribute [rw] retry_on
|
|
169
|
-
# @return [Array<Class>, Class, nil] Exception classes to retry on
|
|
170
|
-
delegate_to_parent :retry_on
|
|
171
|
-
|
|
172
|
-
# Creates a new Settings instance, inheriting registries from a parent
|
|
173
|
-
# Settings or the global Configuration. Scalar settings are resolved
|
|
174
|
-
# lazily via delegation rather than eagerly copied.
|
|
175
|
-
#
|
|
176
|
-
# @param parent [Settings, nil] Parent settings to inherit from
|
|
177
|
-
# @param overrides [Hash] Field values to override after inheritance
|
|
178
|
-
#
|
|
179
|
-
# @example
|
|
180
|
-
# Settings.new(parent: ParentTask.settings, deprecate: true)
|
|
181
|
-
#
|
|
182
|
-
# @rbs (?parent: Settings?, **untyped overrides) -> void
|
|
183
|
-
def initialize(parent: nil, **overrides)
|
|
184
|
-
@parent = parent
|
|
185
|
-
|
|
186
|
-
init_registries
|
|
187
|
-
init_collections
|
|
46
|
+
# @return [Integer] `Logger` severity level
|
|
47
|
+
def log_level
|
|
48
|
+
@options.fetch(:log_level) do
|
|
49
|
+
CMDx.configuration.log_level
|
|
50
|
+
end
|
|
51
|
+
end
|
|
188
52
|
|
|
189
|
-
|
|
53
|
+
# @return [Array<Symbol>] keys to exclude from logging
|
|
54
|
+
def log_exclusions
|
|
55
|
+
@options.fetch(:log_exclusions) do
|
|
56
|
+
CMDx.configuration.log_exclusions
|
|
57
|
+
end
|
|
190
58
|
end
|
|
191
59
|
|
|
192
|
-
|
|
60
|
+
# @return [#call, nil] callable that cleans fault backtrace frames
|
|
61
|
+
def backtrace_cleaner
|
|
62
|
+
@options.fetch(:backtrace_cleaner) do
|
|
63
|
+
CMDx.configuration.backtrace_cleaner
|
|
64
|
+
end
|
|
65
|
+
end
|
|
193
66
|
|
|
194
|
-
#
|
|
195
|
-
#
|
|
67
|
+
# Returns a fresh array each call so callers can mutate the result
|
|
68
|
+
# without affecting other tasks (or hitting `FrozenError` on the
|
|
69
|
+
# shared sentinel).
|
|
196
70
|
#
|
|
197
|
-
# @
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
@coercions = @parent.coercions.dup
|
|
203
|
-
@validators = @parent.validators.dup
|
|
204
|
-
@attributes = @parent.attributes.dup
|
|
205
|
-
else
|
|
206
|
-
config = CMDx.configuration
|
|
71
|
+
# @return [Array<Symbol, String>] task tags, surfaced on result hashes
|
|
72
|
+
def tags
|
|
73
|
+
tags = @options[:tags]
|
|
74
|
+
tags ? tags.dup : []
|
|
75
|
+
end
|
|
207
76
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
77
|
+
# @return [Boolean] whether this task's {Context} should raise on
|
|
78
|
+
# unknown dynamic reads; falls back to
|
|
79
|
+
# {Configuration#strict_context}
|
|
80
|
+
def strict_context
|
|
81
|
+
@options.fetch(:strict_context) do
|
|
82
|
+
CMDx.configuration.strict_context
|
|
213
83
|
end
|
|
214
84
|
end
|
|
215
85
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
@returns = @parent&.returns&.dup || EMPTY_ARRAY
|
|
222
|
-
@tags = @parent&.tags&.dup || EMPTY_ARRAY
|
|
86
|
+
# @return [String, nil] correlation id or the global configuration's correlation id
|
|
87
|
+
def correlation_id
|
|
88
|
+
@options.fetch(:correlation_id) do
|
|
89
|
+
CMDx.configuration.correlation_id
|
|
90
|
+
end
|
|
223
91
|
end
|
|
224
92
|
|
|
225
93
|
end
|