cmdx 1.20.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 +131 -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 -225
- data/lib/cmdx/context.rb +263 -242
- 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 +252 -473
- 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 -196
- data/lib/cmdx/signal.rb +165 -0
- data/lib/cmdx/task.rb +443 -336
- 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 +74 -82
- 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 +128 -52
- 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 +9 -6
- 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 -374
- 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 -62
- 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 -53
- data/lib/locales/ar.yml +0 -53
- data/lib/locales/az.yml +0 -53
- data/lib/locales/be.yml +0 -53
- data/lib/locales/bg.yml +0 -53
- data/lib/locales/bn.yml +0 -53
- data/lib/locales/bs.yml +0 -53
- data/lib/locales/ca.yml +0 -53
- data/lib/locales/cnr.yml +0 -53
- data/lib/locales/cs.yml +0 -53
- data/lib/locales/cy.yml +0 -53
- data/lib/locales/da.yml +0 -53
- data/lib/locales/de.yml +0 -53
- data/lib/locales/dz.yml +0 -53
- data/lib/locales/el.yml +0 -53
- data/lib/locales/eo.yml +0 -53
- data/lib/locales/es.yml +0 -53
- data/lib/locales/et.yml +0 -53
- data/lib/locales/eu.yml +0 -53
- data/lib/locales/fa.yml +0 -53
- data/lib/locales/fi.yml +0 -53
- data/lib/locales/fr.yml +0 -53
- data/lib/locales/fy.yml +0 -53
- data/lib/locales/gd.yml +0 -53
- data/lib/locales/gl.yml +0 -53
- data/lib/locales/he.yml +0 -53
- data/lib/locales/hi.yml +0 -53
- data/lib/locales/hr.yml +0 -53
- data/lib/locales/hu.yml +0 -53
- data/lib/locales/hy.yml +0 -53
- data/lib/locales/id.yml +0 -53
- data/lib/locales/is.yml +0 -53
- data/lib/locales/it.yml +0 -53
- data/lib/locales/ja.yml +0 -53
- data/lib/locales/ka.yml +0 -53
- data/lib/locales/kk.yml +0 -53
- data/lib/locales/km.yml +0 -53
- data/lib/locales/kn.yml +0 -53
- data/lib/locales/ko.yml +0 -53
- data/lib/locales/lb.yml +0 -53
- data/lib/locales/lo.yml +0 -53
- data/lib/locales/lt.yml +0 -53
- data/lib/locales/lv.yml +0 -53
- data/lib/locales/mg.yml +0 -53
- data/lib/locales/mk.yml +0 -53
- data/lib/locales/ml.yml +0 -53
- data/lib/locales/mn.yml +0 -53
- data/lib/locales/mr-IN.yml +0 -53
- data/lib/locales/ms.yml +0 -53
- data/lib/locales/nb.yml +0 -53
- data/lib/locales/ne.yml +0 -53
- data/lib/locales/nl.yml +0 -53
- data/lib/locales/nn.yml +0 -53
- data/lib/locales/oc.yml +0 -53
- data/lib/locales/or.yml +0 -53
- data/lib/locales/pa.yml +0 -53
- data/lib/locales/pl.yml +0 -53
- data/lib/locales/pt.yml +0 -53
- data/lib/locales/rm.yml +0 -53
- data/lib/locales/ro.yml +0 -53
- data/lib/locales/ru.yml +0 -53
- data/lib/locales/sc.yml +0 -53
- data/lib/locales/sk.yml +0 -53
- data/lib/locales/sl.yml +0 -53
- data/lib/locales/sq.yml +0 -53
- data/lib/locales/sr.yml +0 -53
- data/lib/locales/st.yml +0 -53
- data/lib/locales/sv.yml +0 -53
- data/lib/locales/sw.yml +0 -53
- data/lib/locales/ta.yml +0 -53
- data/lib/locales/te.yml +0 -53
- data/lib/locales/th.yml +0 -53
- data/lib/locales/tl.yml +0 -53
- data/lib/locales/tr.yml +0 -53
- data/lib/locales/tt.yml +0 -53
- data/lib/locales/ug.yml +0 -53
- data/lib/locales/uk.yml +0 -53
- data/lib/locales/ur.yml +0 -53
- data/lib/locales/uz.yml +0 -53
- data/lib/locales/vi.yml +0 -53
- data/lib/locales/wo.yml +0 -53
- data/lib/locales/zh-CN.yml +0 -53
- data/lib/locales/zh-HK.yml +0 -53
- data/lib/locales/zh-TW.yml +0 -53
- data/lib/locales/zh-YUE.yml +0 -53
data/lib/cmdx/result.rb
CHANGED
|
@@ -1,579 +1,358 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CMDx
|
|
4
|
-
#
|
|
5
|
-
# status
|
|
4
|
+
# Frozen outcome of a task execution. Provides read-only access to the
|
|
5
|
+
# task's signal (state/status/reason/metadata/cause), the chain it belongs
|
|
6
|
+
# to, its context, and lifecycle metadata (retries, duration, rollback,
|
|
7
|
+
# deprecated). Constructed by Runtime at the end of `execute`.
|
|
6
8
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# checking and conditional handling.
|
|
9
|
+
# @see Runtime#finalize_result
|
|
10
|
+
# @see Signal
|
|
10
11
|
class Result
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# @
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
# @
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# @rbs FAILURE_KEY_REGEX: Regexp
|
|
39
|
-
FAILURE_KEY_REGEX = /_failure\z/
|
|
40
|
-
private_constant :FAILURE_KEY_REGEX
|
|
41
|
-
|
|
42
|
-
# Returns the task instance associated with this result.
|
|
43
|
-
#
|
|
44
|
-
# @return [CMDx::Task] The task instance
|
|
45
|
-
#
|
|
46
|
-
# @example
|
|
47
|
-
# result.task.id # => "users/create"
|
|
48
|
-
#
|
|
49
|
-
# @rbs @task: Task
|
|
50
|
-
attr_reader :task
|
|
13
|
+
EVENTS = Set[
|
|
14
|
+
*Signal::STATES,
|
|
15
|
+
*Signal::STATUSES,
|
|
16
|
+
:ok,
|
|
17
|
+
:ko
|
|
18
|
+
].map!(&:to_sym).freeze
|
|
19
|
+
private_constant :EVENTS
|
|
20
|
+
|
|
21
|
+
attr_reader :chain
|
|
22
|
+
|
|
23
|
+
# @param chain [Chain] the chain this result belongs to
|
|
24
|
+
# @param task [Task] the executed task instance
|
|
25
|
+
# @param signal [Signal] the final signal from the task's lifecycle
|
|
26
|
+
# @param options [Hash{Symbol => Object}] frozen execution metadata
|
|
27
|
+
# @option options [String] :tid
|
|
28
|
+
# @option options [Boolean] :strict
|
|
29
|
+
# @option options [Boolean] :deprecated
|
|
30
|
+
# @option options [Boolean] :rolled_back
|
|
31
|
+
# @option options [Integer] :retries
|
|
32
|
+
# @option options [Float] :duration milliseconds
|
|
33
|
+
def initialize(chain, task, signal, **options)
|
|
34
|
+
@chain = chain
|
|
35
|
+
@task = task
|
|
36
|
+
@signal = signal
|
|
37
|
+
@options = options.freeze
|
|
38
|
+
end
|
|
51
39
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# @example
|
|
57
|
-
# result.state # => "complete"
|
|
58
|
-
#
|
|
59
|
-
# @rbs @state: String
|
|
60
|
-
attr_reader :state
|
|
40
|
+
# @return [String] uuid_v7 identifier for this execution
|
|
41
|
+
def tid
|
|
42
|
+
@options[:tid]
|
|
43
|
+
end
|
|
61
44
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# @example
|
|
67
|
-
# result.status # => "success"
|
|
68
|
-
#
|
|
69
|
-
# @rbs @status: String
|
|
70
|
-
attr_reader :status
|
|
45
|
+
# @return [Class<Task>] the task class that ran
|
|
46
|
+
def task
|
|
47
|
+
@task.class
|
|
48
|
+
end
|
|
71
49
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# @example
|
|
77
|
-
# result.metadata # => { duration: 1.5, code: 200, message: "Success" }
|
|
78
|
-
#
|
|
79
|
-
# @rbs @metadata: Hash[Symbol, untyped]
|
|
80
|
-
attr_reader :metadata
|
|
50
|
+
# @return [String] `"Task"` or `"Workflow"`
|
|
51
|
+
def type
|
|
52
|
+
task.type
|
|
53
|
+
end
|
|
81
54
|
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# @example
|
|
87
|
-
# result.reason # => "Validation failed"
|
|
88
|
-
#
|
|
89
|
-
# @rbs @reason: (String | nil)
|
|
90
|
-
attr_reader :reason
|
|
55
|
+
# @return [String, nil] correlation id or the global configuration's correlation id
|
|
56
|
+
def xid
|
|
57
|
+
chain.xid
|
|
58
|
+
end
|
|
91
59
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# @example
|
|
97
|
-
# result.cause # => #<StandardError: Connection timeout>
|
|
98
|
-
#
|
|
99
|
-
# @rbs @cause: (Exception | nil)
|
|
100
|
-
attr_reader :cause
|
|
60
|
+
# @return [String] uuid_v7 identifier for the chain this result belongs to
|
|
61
|
+
def cid
|
|
62
|
+
chain.id
|
|
63
|
+
end
|
|
101
64
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
# @example
|
|
107
|
-
# result.retries # => 2
|
|
108
|
-
#
|
|
109
|
-
# @rbs @retries: Integer
|
|
110
|
-
attr_accessor :retries
|
|
65
|
+
# @return [Integer, nil] this result's position in the chain
|
|
66
|
+
def index
|
|
67
|
+
@chain.index(self)
|
|
68
|
+
end
|
|
111
69
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# @example
|
|
117
|
-
# result.rolled_back? # => true
|
|
118
|
-
#
|
|
119
|
-
# @rbs @rolled_back: bool
|
|
120
|
-
attr_accessor :rolled_back
|
|
70
|
+
# @return [Boolean] true when this result is the root of the chain
|
|
71
|
+
def root?
|
|
72
|
+
!!@options[:root]
|
|
73
|
+
end
|
|
121
74
|
|
|
122
|
-
|
|
75
|
+
# @return [Context] frozen after the root task's teardown
|
|
76
|
+
def context
|
|
77
|
+
@task.context
|
|
78
|
+
end
|
|
123
79
|
alias ctx context
|
|
124
80
|
|
|
125
|
-
# @
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
#
|
|
129
|
-
# @raise [TypeError] When task is not a CMDx::Task instance
|
|
130
|
-
#
|
|
131
|
-
# @example
|
|
132
|
-
# result = CMDx::Result.new(my_task)
|
|
133
|
-
# result.state # => "initialized"
|
|
134
|
-
#
|
|
135
|
-
# @rbs (Task) -> void
|
|
136
|
-
def initialize(task)
|
|
137
|
-
raise TypeError, "must be a CMDx::Task" unless task.is_a?(CMDx::Task)
|
|
138
|
-
|
|
139
|
-
@task = task
|
|
140
|
-
@state = INITIALIZED
|
|
141
|
-
@status = SUCCESS
|
|
142
|
-
@metadata = {}
|
|
143
|
-
@reason = nil
|
|
144
|
-
@cause = nil
|
|
145
|
-
@retries = 0
|
|
146
|
-
@rolled_back = false
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
STATES.each do |s|
|
|
150
|
-
# @return [Boolean] Whether the result is in the specified state
|
|
151
|
-
#
|
|
152
|
-
# @example
|
|
153
|
-
# result.initialized? # => true
|
|
154
|
-
# result.executing? # => false
|
|
155
|
-
#
|
|
156
|
-
# @rbs () -> bool
|
|
157
|
-
define_method(:"#{s}?") { state == s }
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# @return [self] Returns self for method chaining
|
|
161
|
-
#
|
|
162
|
-
# @example
|
|
163
|
-
# result.executed! # Transitions to complete or interrupted
|
|
164
|
-
#
|
|
165
|
-
# @rbs () -> self
|
|
166
|
-
def executed!
|
|
167
|
-
success? ? complete! : interrupt!
|
|
81
|
+
# @return [Errors] frozen by Runtime teardown
|
|
82
|
+
def errors
|
|
83
|
+
@task.errors
|
|
168
84
|
end
|
|
169
85
|
|
|
170
|
-
# @return [
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
# result.executed? # => true if complete? || interrupted?
|
|
174
|
-
#
|
|
175
|
-
# @rbs () -> bool
|
|
176
|
-
def executed?
|
|
177
|
-
complete? || interrupted?
|
|
86
|
+
# @return [String] one of {Signal::STATES}
|
|
87
|
+
def state
|
|
88
|
+
@signal.state
|
|
178
89
|
end
|
|
179
90
|
|
|
180
|
-
# @
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# result.executing! # Transitions from initialized to executing
|
|
184
|
-
#
|
|
185
|
-
# @rbs () -> void
|
|
186
|
-
def executing!
|
|
187
|
-
return if executing?
|
|
188
|
-
|
|
189
|
-
raise "can only transition to #{EXECUTING} from #{INITIALIZED}" unless initialized?
|
|
190
|
-
|
|
191
|
-
@state = EXECUTING
|
|
91
|
+
# @return [Boolean]
|
|
92
|
+
def complete?
|
|
93
|
+
@signal.complete?
|
|
192
94
|
end
|
|
193
95
|
|
|
194
|
-
# @
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# result.complete! # Transitions from executing to complete
|
|
198
|
-
#
|
|
199
|
-
# @rbs () -> void
|
|
200
|
-
def complete!
|
|
201
|
-
return if complete?
|
|
202
|
-
|
|
203
|
-
raise "can only transition to #{COMPLETE} from #{EXECUTING}" unless executing?
|
|
204
|
-
|
|
205
|
-
@state = COMPLETE
|
|
96
|
+
# @return [Boolean]
|
|
97
|
+
def interrupted?
|
|
98
|
+
@signal.interrupted?
|
|
206
99
|
end
|
|
207
100
|
|
|
208
|
-
# @
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# result.interrupt! # Transitions from executing to interrupted
|
|
212
|
-
#
|
|
213
|
-
# @rbs () -> void
|
|
214
|
-
def interrupt!
|
|
215
|
-
return if interrupted?
|
|
216
|
-
|
|
217
|
-
raise "cannot transition to #{INTERRUPTED} from #{COMPLETE}" if complete?
|
|
218
|
-
|
|
219
|
-
@state = INTERRUPTED
|
|
101
|
+
# @return [String] one of {Signal::STATUSES}
|
|
102
|
+
def status
|
|
103
|
+
@signal.status
|
|
220
104
|
end
|
|
221
105
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# @example
|
|
226
|
-
# result.success? # => true
|
|
227
|
-
# result.failed? # => false
|
|
228
|
-
#
|
|
229
|
-
# @rbs () -> bool
|
|
230
|
-
define_method(:"#{s}?") { status == s }
|
|
106
|
+
# @return [Boolean]
|
|
107
|
+
def success?
|
|
108
|
+
@signal.success?
|
|
231
109
|
end
|
|
232
110
|
|
|
233
|
-
# @return [Boolean]
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
# result.good? # => true if !failed?
|
|
237
|
-
#
|
|
238
|
-
# @rbs () -> bool
|
|
239
|
-
def good?
|
|
240
|
-
!failed?
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def skipped?
|
|
113
|
+
@signal.skipped?
|
|
241
114
|
end
|
|
242
|
-
alias ok? good?
|
|
243
115
|
|
|
244
|
-
# @return [Boolean]
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
# result.bad? # => true if !success?
|
|
248
|
-
#
|
|
249
|
-
# @rbs () -> bool
|
|
250
|
-
def bad?
|
|
251
|
-
!success?
|
|
116
|
+
# @return [Boolean]
|
|
117
|
+
def failed?
|
|
118
|
+
@signal.failed?
|
|
252
119
|
end
|
|
253
120
|
|
|
254
|
-
# @
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
#
|
|
258
|
-
# @raise [ArgumentError] When no block is provided
|
|
259
|
-
#
|
|
260
|
-
# @example
|
|
261
|
-
# result.on(:bad) { |r| puts "Task had issues: #{r.reason}" }
|
|
262
|
-
# result.on(:success, :complete) { |r| puts "Task completed successfully" }
|
|
263
|
-
#
|
|
264
|
-
# @rbs () { (Result) -> void } -> self
|
|
265
|
-
def on(*states_or_statuses, &)
|
|
266
|
-
raise ArgumentError, "block required" unless block_given?
|
|
267
|
-
|
|
268
|
-
yield(self) if states_or_statuses.any? { |s| public_send(:"#{s}?") }
|
|
269
|
-
self
|
|
121
|
+
# @return [Boolean]
|
|
122
|
+
def ok?
|
|
123
|
+
@signal.ok?
|
|
270
124
|
end
|
|
271
125
|
|
|
272
|
-
# @
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
# @param metadata [Hash] Additional metadata about the skip
|
|
276
|
-
# @option metadata [Object] :* Any key-value pairs for additional metadata
|
|
277
|
-
#
|
|
278
|
-
# @raise [RuntimeError] When attempting to skip from invalid status
|
|
279
|
-
#
|
|
280
|
-
# @example
|
|
281
|
-
# result.skip!("Dependencies not met", cause: dependency_error)
|
|
282
|
-
# result.skip!("Already processed", halt: false)
|
|
283
|
-
#
|
|
284
|
-
# @rbs (?String? reason, halt: bool, cause: Exception?, **untyped metadata) -> void
|
|
285
|
-
def skip!(reason = nil, halt: true, cause: nil, **metadata)
|
|
286
|
-
return if skipped?
|
|
287
|
-
|
|
288
|
-
raise "can only transition to #{SKIPPED} from #{SUCCESS}" unless success?
|
|
289
|
-
|
|
290
|
-
@state = INTERRUPTED
|
|
291
|
-
@status = SKIPPED
|
|
292
|
-
@reason = reason || Locale.t("cmdx.faults.unspecified")
|
|
293
|
-
@cause = cause
|
|
294
|
-
@metadata = metadata
|
|
295
|
-
|
|
296
|
-
halt! if halt
|
|
126
|
+
# @return [Boolean]
|
|
127
|
+
def ko?
|
|
128
|
+
@signal.ko?
|
|
297
129
|
end
|
|
298
130
|
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
# @param cause [Exception, nil] Exception that caused the failure
|
|
302
|
-
# @param metadata [Hash] Additional metadata about the failure
|
|
303
|
-
# @option metadata [Object] :* Any key-value pairs for additional metadata
|
|
131
|
+
# Dispatches the block when any of `keys` matches a truthy predicate on
|
|
132
|
+
# this result. Returns `self` for chaining.
|
|
304
133
|
#
|
|
305
|
-
# @
|
|
134
|
+
# @param keys [Array<Symbol, String>] any of the predicate bases:
|
|
135
|
+
# `complete`, `interrupted`, `success`, `skipped`, `failed`, `ok`, `ko`
|
|
136
|
+
# @yieldparam result [Result] this result
|
|
137
|
+
# @return [Result] self for chaining
|
|
138
|
+
# @raise [ArgumentError] when no block is given or a key is unknown
|
|
306
139
|
#
|
|
307
140
|
# @example
|
|
308
|
-
# result
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return if failed?
|
|
141
|
+
# result
|
|
142
|
+
# .on(:success) { |r| deliver(r.context) }
|
|
143
|
+
# .on(:failed) { |r| alert(r.reason) }
|
|
144
|
+
def on(*keys)
|
|
145
|
+
raise ArgumentError, "block required" unless block_given?
|
|
314
146
|
|
|
315
|
-
|
|
147
|
+
yield(self) if keys.any? do |k|
|
|
148
|
+
unless EVENTS.include?(k.to_sym)
|
|
149
|
+
raise ArgumentError,
|
|
150
|
+
"unknown event #{k.inspect}, must be one of #{EVENTS.join(', ')}"
|
|
151
|
+
end
|
|
316
152
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
@reason = reason || Locale.t("cmdx.faults.unspecified")
|
|
320
|
-
@cause = cause
|
|
321
|
-
@metadata = metadata
|
|
153
|
+
public_send(:"#{k}?")
|
|
154
|
+
end
|
|
322
155
|
|
|
323
|
-
|
|
156
|
+
self
|
|
324
157
|
end
|
|
325
158
|
|
|
326
|
-
# @
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# result.halt! # Raises appropriate fault based on status
|
|
331
|
-
#
|
|
332
|
-
# @rbs () -> void
|
|
333
|
-
def halt!
|
|
334
|
-
return if success?
|
|
335
|
-
|
|
336
|
-
klass = skipped? ? SkipFault : FailFault
|
|
337
|
-
fault = klass.new(self)
|
|
338
|
-
|
|
339
|
-
# Strip the first two frames (this method and the delegator)
|
|
340
|
-
frames = caller_locations(3..-1)
|
|
341
|
-
|
|
342
|
-
unless frames.empty?
|
|
343
|
-
frames = frames.map(&:to_s)
|
|
344
|
-
|
|
345
|
-
if (cleaner = task.class.settings.backtrace_cleaner)
|
|
346
|
-
cleaner.call(frames)
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
fault.set_backtrace(frames)
|
|
350
|
-
end
|
|
159
|
+
# @return [String, nil]
|
|
160
|
+
def reason
|
|
161
|
+
@signal.reason
|
|
162
|
+
end
|
|
351
163
|
|
|
352
|
-
|
|
164
|
+
# @return [Hash{Symbol => Object}] frozen empty hash when none provided
|
|
165
|
+
def metadata
|
|
166
|
+
@signal.metadata
|
|
353
167
|
end
|
|
354
168
|
|
|
355
|
-
#
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
# @param metadata [Hash] Additional metadata to merge
|
|
359
|
-
# @option metadata [Object] :* Any key-value pairs for additional metadata
|
|
360
|
-
#
|
|
361
|
-
# @raise [TypeError] When result is not a CMDx::Result instance
|
|
169
|
+
# The upstream failed result this one was echoed from (via `Task#throw!`
|
|
170
|
+
# or a rescued {Fault} inside `work`). `nil` when this is a locally
|
|
171
|
+
# originated failure or the result didn't fail.
|
|
362
172
|
#
|
|
363
|
-
# @
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
# @rbs (Result result, halt: bool, cause: Exception?, **untyped metadata) -> void
|
|
368
|
-
def throw!(result, halt: true, cause: nil, **metadata)
|
|
369
|
-
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
|
370
|
-
|
|
371
|
-
@state = result.state
|
|
372
|
-
@status = result.status
|
|
373
|
-
@reason = result.reason
|
|
374
|
-
@cause = cause || result.cause
|
|
375
|
-
@metadata = result.metadata.merge(metadata)
|
|
173
|
+
# @return [Result, nil]
|
|
174
|
+
def origin
|
|
175
|
+
@signal.origin
|
|
176
|
+
end
|
|
376
177
|
|
|
377
|
-
|
|
178
|
+
# @return [Exception, nil]
|
|
179
|
+
def cause
|
|
180
|
+
@signal.cause
|
|
378
181
|
end
|
|
379
182
|
|
|
380
|
-
#
|
|
381
|
-
#
|
|
382
|
-
#
|
|
383
|
-
# cause = result.caused_failure
|
|
384
|
-
# puts "Caused by: #{cause.task.id}" if cause
|
|
183
|
+
# The originating failed result at the bottom of the propagation chain.
|
|
184
|
+
# Walks `origin` recursively. `self` when this result is the originator;
|
|
185
|
+
# `nil` when not failed.
|
|
385
186
|
#
|
|
386
|
-
# @
|
|
187
|
+
# @return [Result, nil]
|
|
387
188
|
def caused_failure
|
|
388
189
|
return unless failed?
|
|
389
190
|
|
|
390
|
-
|
|
191
|
+
@caused_failure ||= origin ? origin.caused_failure : self
|
|
391
192
|
end
|
|
392
193
|
|
|
393
|
-
# @return [Boolean]
|
|
394
|
-
#
|
|
395
|
-
# @example
|
|
396
|
-
# if result.caused_failure?
|
|
397
|
-
# puts "This task caused the failure"
|
|
398
|
-
# end
|
|
399
|
-
#
|
|
400
|
-
# @rbs () -> bool
|
|
194
|
+
# @return [Boolean] true when this result originated the failure chain
|
|
401
195
|
def caused_failure?
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
caused_failure == self
|
|
196
|
+
failed? && origin.nil?
|
|
405
197
|
end
|
|
406
198
|
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
# @example
|
|
410
|
-
# thrown = result.threw_failure
|
|
411
|
-
# puts "Thrown by: #{thrown.task.id}" if thrown
|
|
199
|
+
# The nearest upstream failed result. `self` when this result is the
|
|
200
|
+
# originator; `nil` when not failed.
|
|
412
201
|
#
|
|
413
|
-
# @
|
|
202
|
+
# @return [Result, nil]
|
|
414
203
|
def threw_failure
|
|
415
204
|
return unless failed?
|
|
416
205
|
|
|
417
|
-
|
|
418
|
-
|
|
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
|
|
206
|
+
origin || self
|
|
207
|
+
end
|
|
427
208
|
|
|
428
|
-
|
|
209
|
+
# @return [Boolean] true when this result re-threw an upstream failure
|
|
210
|
+
def thrown_failure?
|
|
211
|
+
failed? && !origin.nil?
|
|
429
212
|
end
|
|
430
213
|
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
#
|
|
434
|
-
# if result.threw_failure?
|
|
435
|
-
# puts "This task threw the failure"
|
|
436
|
-
# end
|
|
214
|
+
# The backtrace captured by `fail!` / `throw!` for Fault propagation.
|
|
215
|
+
# `nil` when this result is not a failure or the failure didn't capture
|
|
216
|
+
# a backtrace.
|
|
437
217
|
#
|
|
438
|
-
# @
|
|
439
|
-
def
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
threw_failure == self
|
|
218
|
+
# @return [Array<String>, nil]
|
|
219
|
+
def backtrace
|
|
220
|
+
@signal.backtrace
|
|
443
221
|
end
|
|
444
222
|
|
|
445
|
-
# @return [
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
# if result.thrown_failure?
|
|
449
|
-
# puts "This failure was thrown from another task"
|
|
450
|
-
# end
|
|
451
|
-
#
|
|
452
|
-
# @rbs () -> bool
|
|
453
|
-
def thrown_failure?
|
|
454
|
-
failed? && !caused_failure?
|
|
223
|
+
# @return [Integer]
|
|
224
|
+
def retries
|
|
225
|
+
@options[:retries] || 0
|
|
455
226
|
end
|
|
456
227
|
|
|
457
|
-
# @return [Boolean]
|
|
458
|
-
#
|
|
459
|
-
# @example
|
|
460
|
-
# result.retried? # => true
|
|
461
|
-
#
|
|
462
|
-
# @rbs () -> bool
|
|
228
|
+
# @return [Boolean]
|
|
463
229
|
def retried?
|
|
464
230
|
retries.positive?
|
|
465
231
|
end
|
|
466
232
|
|
|
467
|
-
# @return [Boolean]
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
# @
|
|
233
|
+
# @return [Boolean] true when produced via `execute!`
|
|
234
|
+
def strict?
|
|
235
|
+
!!@options[:strict]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @return [Boolean] true when the task class is marked deprecated
|
|
239
|
+
def deprecated?
|
|
240
|
+
!!@options[:deprecated]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# @return [Boolean] true when a failing task's `rollback` ran
|
|
473
244
|
def rolled_back?
|
|
474
|
-
!!@rolled_back
|
|
245
|
+
!!@options[:rolled_back]
|
|
475
246
|
end
|
|
476
247
|
|
|
477
|
-
# @return [
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
# position = result.index
|
|
481
|
-
# puts "Task #{position + 1} of #{chain.results.count}"
|
|
482
|
-
#
|
|
483
|
-
# @rbs () -> Integer
|
|
484
|
-
def index
|
|
485
|
-
@chain_index || chain.index(self)
|
|
248
|
+
# @return [Float, nil] lifecycle duration in milliseconds
|
|
249
|
+
def duration
|
|
250
|
+
@options[:duration]
|
|
486
251
|
end
|
|
487
252
|
|
|
488
|
-
# @return [String]
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
# result.outcome # => "success" or "interrupted"
|
|
492
|
-
#
|
|
493
|
-
# @rbs () -> String
|
|
494
|
-
def outcome
|
|
495
|
-
initialized? || thrown_failure? ? state : status
|
|
253
|
+
# @return [Array<Symbol, String>]
|
|
254
|
+
def tags
|
|
255
|
+
task.settings.tags
|
|
496
256
|
end
|
|
497
257
|
|
|
498
|
-
# @return [Hash
|
|
499
|
-
#
|
|
500
|
-
#
|
|
501
|
-
# result.to_h
|
|
502
|
-
# # => {state: "complete", status: "success", outcome: "success", metadata: {}}
|
|
503
|
-
#
|
|
504
|
-
# @rbs () -> Hash[Symbol, untyped]
|
|
258
|
+
# @return [Hash{Symbol => Object}] memoized serialization. Includes
|
|
259
|
+
# `:cause`, `:origin`, `:threw_failure`, `:caused_failure`, `:rolled_back`
|
|
260
|
+
# on failure.
|
|
505
261
|
def to_h
|
|
506
|
-
|
|
262
|
+
@to_h ||= {
|
|
263
|
+
xid:,
|
|
264
|
+
cid:,
|
|
265
|
+
index:,
|
|
266
|
+
root: root?,
|
|
267
|
+
type:,
|
|
268
|
+
task:,
|
|
269
|
+
tid:,
|
|
270
|
+
context:,
|
|
507
271
|
state:,
|
|
508
272
|
status:,
|
|
509
|
-
|
|
510
|
-
metadata
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
273
|
+
reason:,
|
|
274
|
+
metadata:,
|
|
275
|
+
strict: strict?,
|
|
276
|
+
deprecated: deprecated?,
|
|
277
|
+
retried: retried?,
|
|
278
|
+
retries:,
|
|
279
|
+
duration:,
|
|
280
|
+
tags:
|
|
281
|
+
}.tap do |hash|
|
|
282
|
+
if failed?
|
|
514
283
|
hash[:cause] = cause
|
|
284
|
+
hash[:origin] = hash_for_failure(:origin)
|
|
285
|
+
hash[:threw_failure] = hash_for_failure(:threw_failure)
|
|
286
|
+
hash[:caused_failure] = hash_for_failure(:caused_failure)
|
|
515
287
|
hash[:rolled_back] = rolled_back?
|
|
516
288
|
end
|
|
517
|
-
|
|
518
|
-
if failed?
|
|
519
|
-
STRIP_FAILURE.call(hash, self, :threw_failure)
|
|
520
|
-
STRIP_FAILURE.call(hash, self, :caused_failure)
|
|
521
|
-
end
|
|
522
289
|
end
|
|
523
290
|
end
|
|
524
291
|
|
|
525
|
-
#
|
|
292
|
+
# JSON-friendly hash view. Aliases the memoized {#to_h} for conventional
|
|
293
|
+
# `as_json` callers (e.g. Rails).
|
|
526
294
|
#
|
|
527
|
-
# @
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
295
|
+
# @return [Hash{Symbol => Object}]
|
|
296
|
+
def as_json(*)
|
|
297
|
+
to_h
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Serializes the result to a JSON string. Non-primitive entries (the
|
|
301
|
+
# `:task` Class, `:cause` Exception) emit via their stdlib `to_json`
|
|
302
|
+
# defaults; `:context` delegates to {Context#to_json}.
|
|
531
303
|
#
|
|
532
|
-
# @
|
|
304
|
+
# @param args [Array] forwarded to `Hash#to_json`
|
|
305
|
+
# @return [String]
|
|
306
|
+
def to_json(*args)
|
|
307
|
+
to_h.to_json(*args)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# @return [String] space-separated `key=value.inspect` pairs; failure
|
|
311
|
+
# references render as `<TaskClass uuid>`.
|
|
533
312
|
def to_s
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
"
|
|
313
|
+
@to_s ||= begin
|
|
314
|
+
buf = String.new(capacity: 256)
|
|
315
|
+
|
|
316
|
+
to_h.each_with_object(buf) do |(k, v), buf|
|
|
317
|
+
buf << " " unless buf.empty?
|
|
318
|
+
|
|
319
|
+
ks = k.name
|
|
320
|
+
|
|
321
|
+
if v.nil?
|
|
322
|
+
buf << ks << "=nil"
|
|
323
|
+
elsif ks == "origin" || ks.end_with?("_failure")
|
|
324
|
+
buf << ks << "=<" << v[:task].to_s << " " << v[:tid] << ">"
|
|
325
|
+
else
|
|
326
|
+
buf << ks << "=" << v.inspect
|
|
327
|
+
end
|
|
539
328
|
end
|
|
540
329
|
end
|
|
541
330
|
end
|
|
542
331
|
|
|
543
|
-
#
|
|
332
|
+
# Pattern-matching support for `case result in {...}`.
|
|
544
333
|
#
|
|
545
|
-
# @
|
|
546
|
-
#
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
# @rbs (*untyped) -> Array[untyped]
|
|
550
|
-
def deconstruct(*)
|
|
551
|
-
[state, status, reason, cause, metadata]
|
|
334
|
+
# @param keys [Array<Symbol>, nil] restrict the returned hash to these keys
|
|
335
|
+
# @return [Hash{Symbol => Object}]
|
|
336
|
+
def deconstruct_keys(keys)
|
|
337
|
+
keys.nil? ? to_h : to_h.slice(*keys)
|
|
552
338
|
end
|
|
553
339
|
|
|
554
|
-
#
|
|
555
|
-
#
|
|
556
|
-
# @example
|
|
557
|
-
# case result.deconstruct_keys
|
|
558
|
-
# in {state: "complete", good: true}
|
|
559
|
-
# puts "Task completed successfully"
|
|
560
|
-
# in {bad: true}
|
|
561
|
-
# puts "Task had issues"
|
|
562
|
-
# end
|
|
340
|
+
# Pattern-matching support for `case result in [...]`.
|
|
563
341
|
#
|
|
564
|
-
# @
|
|
565
|
-
def
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
342
|
+
# @return [Array<Array(Symbol, Object)>]
|
|
343
|
+
def deconstruct
|
|
344
|
+
to_h.to_a
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
private
|
|
348
|
+
|
|
349
|
+
# @param key [Symbol] reader name such as `:caused_failure` or `:threw_failure`
|
|
350
|
+
# @return [Hash{Symbol => Object}, nil] compact `{task:, tid:}` map for graph hints
|
|
351
|
+
def hash_for_failure(key)
|
|
352
|
+
r = public_send(key)
|
|
353
|
+
return if r.nil?
|
|
354
|
+
|
|
355
|
+
{ task: r.task, tid: r.tid }
|
|
577
356
|
end
|
|
578
357
|
|
|
579
358
|
end
|