cmdx 1.1.2 → 1.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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/CHANGELOG.md +5 -133
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -67
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -58
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
data/lib/cmdx/result.rb
CHANGED
@@ -1,101 +1,80 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Represents the execution result of a task, tracking state
|
4
|
+
# Represents the execution result of a CMDx task, tracking state transitions,
|
5
|
+
# status changes, and providing methods for handling different outcomes.
|
5
6
|
#
|
6
|
-
# Result
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# state transitions, status changes, and provide introspection capabilities
|
10
|
-
# for debugging and monitoring task execution.
|
7
|
+
# The Result class manages the lifecycle of task execution from initialization
|
8
|
+
# through completion or interruption, offering a fluent interface for status
|
9
|
+
# checking and conditional handling.
|
11
10
|
class Result
|
12
11
|
|
12
|
+
extend Forwardable
|
13
|
+
|
13
14
|
STATES = [
|
14
15
|
INITIALIZED = "initialized", # Initial state before execution
|
15
|
-
EXECUTING
|
16
|
-
COMPLETE
|
16
|
+
EXECUTING = "executing", # Currently executing task logic
|
17
|
+
COMPLETE = "complete", # Successfully completed execution
|
17
18
|
INTERRUPTED = "interrupted" # Execution was halted due to failure
|
18
19
|
].freeze
|
19
20
|
STATUSES = [
|
20
21
|
SUCCESS = "success", # Task completed successfully
|
21
22
|
SKIPPED = "skipped", # Task was skipped intentionally
|
22
|
-
FAILED
|
23
|
+
FAILED = "failed" # Task failed due to error or validation
|
23
24
|
].freeze
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
attr_reader :state
|
26
|
+
STRIP_FAILURE = proc do |hash, result, key|
|
27
|
+
unless result.send(:"#{key}?")
|
28
|
+
# Strip caused/threw failures since its the same info as the log line
|
29
|
+
hash[key] = result.send(key).to_h.except(:caused_failure, :threw_failure)
|
30
|
+
end
|
31
|
+
end.freeze
|
32
|
+
private_constant :STRIP_FAILURE
|
33
33
|
|
34
|
-
|
35
|
-
attr_reader :status
|
34
|
+
attr_reader :task, :state, :status, :metadata, :reason, :cause
|
36
35
|
|
37
|
-
|
38
|
-
attr_reader :metadata
|
36
|
+
def_delegators :task, :context, :chain
|
39
37
|
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# @param task [CMDx::Task] the task to create a result for
|
38
|
+
# @param task [CMDx::Task] The task instance this result represents
|
43
39
|
#
|
44
|
-
# @return [CMDx::Result]
|
40
|
+
# @return [CMDx::Result] A new result instance for the task
|
45
41
|
#
|
46
|
-
# @raise [TypeError]
|
42
|
+
# @raise [TypeError] When task is not a CMDx::Task instance
|
47
43
|
#
|
48
|
-
# @example
|
44
|
+
# @example
|
49
45
|
# result = CMDx::Result.new(my_task)
|
50
|
-
# result.state
|
51
|
-
# result.status #=> "success"
|
46
|
+
# result.state # => "initialized"
|
52
47
|
def initialize(task)
|
53
|
-
raise TypeError, "must be a Task
|
48
|
+
raise TypeError, "must be a CMDx::Task" unless task.is_a?(CMDx::Task)
|
54
49
|
|
55
|
-
@task
|
56
|
-
@state
|
57
|
-
@status
|
50
|
+
@task = task
|
51
|
+
@state = INITIALIZED
|
52
|
+
@status = SUCCESS
|
58
53
|
@metadata = {}
|
54
|
+
@reason = nil
|
55
|
+
@cause = nil
|
59
56
|
end
|
60
57
|
|
61
58
|
STATES.each do |s|
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# @return [Boolean] true if the result matches the state
|
65
|
-
#
|
66
|
-
# @example Check if result is initialized
|
67
|
-
# result.initialized? #=> true
|
68
|
-
#
|
69
|
-
# @example Check if result is executing
|
70
|
-
# result.executing? #=> false
|
71
|
-
#
|
72
|
-
# @example Check if result is complete
|
73
|
-
# result.complete? #=> false
|
59
|
+
# @return [Boolean] Whether the result is in the specified state
|
74
60
|
#
|
75
|
-
# @example
|
76
|
-
# result.
|
61
|
+
# @example
|
62
|
+
# result.initialized? # => true
|
63
|
+
# result.executing? # => false
|
77
64
|
define_method(:"#{s}?") { state == s }
|
78
65
|
|
79
|
-
#
|
66
|
+
# @param block [Proc] Block to execute conditionally
|
80
67
|
#
|
81
|
-
# @
|
68
|
+
# @yield [self] Executes the block if result is in specified state
|
82
69
|
#
|
83
|
-
# @return [
|
70
|
+
# @return [self] Returns self for method chaining
|
84
71
|
#
|
85
|
-
# @raise [ArgumentError]
|
72
|
+
# @raise [ArgumentError] When no block is provided
|
86
73
|
#
|
87
|
-
# @example
|
88
|
-
# result.
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# result.on_executing { |r| puts "Task is currently running" }
|
92
|
-
#
|
93
|
-
# @example Handle complete state
|
94
|
-
# result.on_complete { |r| puts "Task finished successfully" }
|
95
|
-
#
|
96
|
-
# @example Handle interrupted state
|
97
|
-
# result.on_interrupted { |r| puts "Task was interrupted" }
|
98
|
-
define_method(:"on_#{s}") do |&block|
|
74
|
+
# @example
|
75
|
+
# result.handle_initialized { |r| puts "Starting execution" }
|
76
|
+
# result.handle_complete { |r| puts "Task completed" }
|
77
|
+
define_method(:"handle_#{s}") do |&block|
|
99
78
|
raise ArgumentError, "block required" unless block
|
100
79
|
|
101
80
|
block.call(self) if send(:"#{s}?")
|
@@ -103,61 +82,43 @@ module CMDx
|
|
103
82
|
end
|
104
83
|
end
|
105
84
|
|
106
|
-
#
|
107
|
-
# based on the current status
|
108
|
-
#
|
109
|
-
# @return [void]
|
85
|
+
# @return [self] Returns self for method chaining
|
110
86
|
#
|
111
|
-
# @example
|
112
|
-
# result.executed!
|
113
|
-
# result.complete? #=> true
|
114
|
-
#
|
115
|
-
# @example Mark failed task as executed
|
116
|
-
# result.fail!
|
117
|
-
# result.executed!
|
118
|
-
# result.interrupted? #=> true
|
87
|
+
# @example
|
88
|
+
# result.executed! # Transitions to complete or interrupted
|
119
89
|
def executed!
|
120
90
|
success? ? complete! : interrupt!
|
121
91
|
end
|
122
92
|
|
123
|
-
#
|
124
|
-
#
|
125
|
-
# @return [Boolean] true if the result is complete or interrupted
|
93
|
+
# @return [Boolean] Whether the task has been executed (complete or interrupted)
|
126
94
|
#
|
127
|
-
# @example
|
128
|
-
# result.executed?
|
129
|
-
# result.executed!
|
130
|
-
# result.executed? #=> true
|
95
|
+
# @example
|
96
|
+
# result.executed? # => true if complete? || interrupted?
|
131
97
|
def executed?
|
132
98
|
complete? || interrupted?
|
133
99
|
end
|
134
100
|
|
135
|
-
#
|
101
|
+
# @param block [Proc] Block to execute conditionally
|
136
102
|
#
|
137
|
-
# @
|
103
|
+
# @yield [self] Executes the block if task has been executed
|
138
104
|
#
|
139
|
-
# @return [
|
105
|
+
# @return [self] Returns self for method chaining
|
140
106
|
#
|
141
|
-
# @raise [ArgumentError]
|
107
|
+
# @raise [ArgumentError] When no block is provided
|
142
108
|
#
|
143
|
-
# @example
|
144
|
-
# result.
|
145
|
-
def
|
109
|
+
# @example
|
110
|
+
# result.handle_executed { |r| puts "Task finished: #{r.outcome}" }
|
111
|
+
def handle_executed(&)
|
146
112
|
raise ArgumentError, "block required" unless block_given?
|
147
113
|
|
148
114
|
yield(self) if executed?
|
149
115
|
self
|
150
116
|
end
|
151
117
|
|
152
|
-
#
|
153
|
-
#
|
154
|
-
# @return [void]
|
155
|
-
#
|
156
|
-
# @raise [RuntimeError] if not transitioning from initialized state
|
118
|
+
# @raise [RuntimeError] When attempting to transition from invalid state
|
157
119
|
#
|
158
|
-
# @example
|
159
|
-
# result.executing!
|
160
|
-
# result.executing? #=> true
|
120
|
+
# @example
|
121
|
+
# result.executing! # Transitions from initialized to executing
|
161
122
|
def executing!
|
162
123
|
return if executing?
|
163
124
|
|
@@ -166,16 +127,10 @@ module CMDx
|
|
166
127
|
@state = EXECUTING
|
167
128
|
end
|
168
129
|
|
169
|
-
#
|
130
|
+
# @raise [RuntimeError] When attempting to transition from invalid state
|
170
131
|
#
|
171
|
-
# @
|
172
|
-
#
|
173
|
-
# @raise [RuntimeError] if not transitioning from executing state
|
174
|
-
#
|
175
|
-
# @example Complete task execution
|
176
|
-
# result.executing!
|
177
|
-
# result.complete!
|
178
|
-
# result.complete? #=> true
|
132
|
+
# @example
|
133
|
+
# result.complete! # Transitions from executing to complete
|
179
134
|
def complete!
|
180
135
|
return if complete?
|
181
136
|
|
@@ -184,16 +139,10 @@ module CMDx
|
|
184
139
|
@state = COMPLETE
|
185
140
|
end
|
186
141
|
|
187
|
-
#
|
188
|
-
#
|
189
|
-
# @return [void]
|
142
|
+
# @raise [RuntimeError] When attempting to transition from invalid state
|
190
143
|
#
|
191
|
-
# @
|
192
|
-
#
|
193
|
-
# @example Interrupt task execution
|
194
|
-
# result.executing!
|
195
|
-
# result.interrupt!
|
196
|
-
# result.interrupted? #=> true
|
144
|
+
# @example
|
145
|
+
# result.interrupt! # Transitions from executing to interrupted
|
197
146
|
def interrupt!
|
198
147
|
return if interrupted?
|
199
148
|
|
@@ -203,37 +152,25 @@ module CMDx
|
|
203
152
|
end
|
204
153
|
|
205
154
|
STATUSES.each do |s|
|
206
|
-
#
|
207
|
-
#
|
208
|
-
# @return [Boolean] true if the result matches the status
|
155
|
+
# @return [Boolean] Whether the result has the specified status
|
209
156
|
#
|
210
|
-
# @example
|
211
|
-
# result.success?
|
212
|
-
#
|
213
|
-
# @example Check if result is skipped
|
214
|
-
# result.skipped? #=> false
|
215
|
-
#
|
216
|
-
# @example Check if result is failed
|
217
|
-
# result.failed? #=> false
|
157
|
+
# @example
|
158
|
+
# result.success? # => true
|
159
|
+
# result.failed? # => false
|
218
160
|
define_method(:"#{s}?") { status == s }
|
219
161
|
|
220
|
-
#
|
221
|
-
#
|
222
|
-
# @param block [Proc] the block to execute if result matches the status
|
223
|
-
#
|
224
|
-
# @return [Result] returns self for method chaining
|
162
|
+
# @param block [Proc] Block to execute conditionally
|
225
163
|
#
|
226
|
-
# @
|
164
|
+
# @yield [self] Executes the block if result has specified status
|
227
165
|
#
|
228
|
-
# @
|
229
|
-
# result.on_success { |r| puts "Task completed successfully" }
|
166
|
+
# @return [self] Returns self for method chaining
|
230
167
|
#
|
231
|
-
# @
|
232
|
-
# result.on_skipped { |r| puts "Task was skipped: #{r.metadata[:reason]}" }
|
168
|
+
# @raise [ArgumentError] When no block is provided
|
233
169
|
#
|
234
|
-
# @example
|
235
|
-
# result.
|
236
|
-
|
170
|
+
# @example
|
171
|
+
# result.handle_success { |r| puts "Task succeeded" }
|
172
|
+
# result.handle_failed { |r| puts "Task failed: #{r.reason}" }
|
173
|
+
define_method(:"handle_#{s}") do |&block|
|
237
174
|
raise ArgumentError, "block required" unless block
|
238
175
|
|
239
176
|
block.call(self) if send(:"#{s}?")
|
@@ -241,298 +178,280 @@ module CMDx
|
|
241
178
|
end
|
242
179
|
end
|
243
180
|
|
244
|
-
#
|
181
|
+
# @return [Boolean] Whether the task execution was successful (not failed)
|
245
182
|
#
|
246
|
-
# @
|
247
|
-
#
|
248
|
-
# @example Check for good outcome
|
249
|
-
# result.good? #=> true (initially successful)
|
250
|
-
# result.fail!
|
251
|
-
# result.good? #=> false
|
183
|
+
# @example
|
184
|
+
# result.good? # => true if !failed?
|
252
185
|
def good?
|
253
186
|
!failed?
|
254
187
|
end
|
255
188
|
|
256
|
-
#
|
189
|
+
# @param block [Proc] Block to execute conditionally
|
257
190
|
#
|
258
|
-
# @
|
191
|
+
# @yield [self] Executes the block if task execution was successful
|
259
192
|
#
|
260
|
-
# @return [
|
193
|
+
# @return [self] Returns self for method chaining
|
261
194
|
#
|
262
|
-
# @raise [ArgumentError]
|
195
|
+
# @raise [ArgumentError] When no block is provided
|
263
196
|
#
|
264
|
-
# @example
|
265
|
-
# result.
|
266
|
-
def
|
197
|
+
# @example
|
198
|
+
# result.handle_good { |r| puts "Task completed successfully" }
|
199
|
+
def handle_good(&)
|
267
200
|
raise ArgumentError, "block required" unless block_given?
|
268
201
|
|
269
202
|
yield(self) if good?
|
270
203
|
self
|
271
204
|
end
|
272
205
|
|
273
|
-
#
|
274
|
-
#
|
275
|
-
# @return [Boolean] true if the result is not successful
|
206
|
+
# @return [Boolean] Whether the task execution was unsuccessful (not success)
|
276
207
|
#
|
277
|
-
# @example
|
278
|
-
# result.bad?
|
279
|
-
# result.skip!
|
280
|
-
# result.bad? #=> true
|
208
|
+
# @example
|
209
|
+
# result.bad? # => true if !success?
|
281
210
|
def bad?
|
282
211
|
!success?
|
283
212
|
end
|
284
213
|
|
285
|
-
#
|
214
|
+
# @param block [Proc] Block to execute conditionally
|
286
215
|
#
|
287
|
-
# @
|
216
|
+
# @yield [self] Executes the block if task execution was unsuccessful
|
288
217
|
#
|
289
|
-
# @return [
|
218
|
+
# @return [self] Returns self for method chaining
|
290
219
|
#
|
291
|
-
# @raise [ArgumentError]
|
220
|
+
# @raise [ArgumentError] When no block is provided
|
292
221
|
#
|
293
|
-
# @example
|
294
|
-
# result.
|
295
|
-
def
|
222
|
+
# @example
|
223
|
+
# result.handle_bad { |r| puts "Task had issues: #{r.reason}" }
|
224
|
+
def handle_bad(&)
|
296
225
|
raise ArgumentError, "block required" unless block_given?
|
297
226
|
|
298
227
|
yield(self) if bad?
|
299
228
|
self
|
300
229
|
end
|
301
230
|
|
302
|
-
#
|
303
|
-
#
|
304
|
-
# @param
|
305
|
-
# @
|
306
|
-
# @option metadata [Exception] :original_exception the original exception that caused skipping
|
307
|
-
#
|
308
|
-
# @return [void]
|
231
|
+
# @param reason [String, nil] Reason for skipping the task
|
232
|
+
# @param halt [Boolean] Whether to halt execution after skipping
|
233
|
+
# @param cause [Exception, nil] Exception that caused the skip
|
234
|
+
# @param metadata [Hash] Additional metadata about the skip
|
309
235
|
#
|
310
|
-
# @raise [RuntimeError]
|
311
|
-
# @raise [CMDx::Fault] if no original_exception in metadata (via halt!)
|
236
|
+
# @raise [RuntimeError] When attempting to skip from invalid status
|
312
237
|
#
|
313
|
-
# @example
|
314
|
-
# result.skip!(
|
315
|
-
# result.
|
316
|
-
|
317
|
-
def skip!(**metadata)
|
238
|
+
# @example
|
239
|
+
# result.skip!("Dependencies not met", cause: dependency_error)
|
240
|
+
# result.skip!("Already processed", halt: false)
|
241
|
+
def skip!(reason = nil, halt: true, cause: nil, **metadata)
|
318
242
|
return if skipped?
|
319
243
|
|
320
244
|
raise "can only transition to #{SKIPPED} from #{SUCCESS}" unless success?
|
321
245
|
|
322
|
-
@
|
246
|
+
@state = INTERRUPTED
|
247
|
+
@status = SKIPPED
|
248
|
+
@reason = reason || Locale.t("cmdx.faults.unspecified")
|
249
|
+
@cause = cause
|
323
250
|
@metadata = metadata
|
324
251
|
|
325
|
-
halt!
|
252
|
+
halt! if halt
|
326
253
|
end
|
327
254
|
|
328
|
-
#
|
329
|
-
#
|
330
|
-
# @param
|
331
|
-
# @
|
332
|
-
# @option metadata [Exception] :original_exception the original exception that caused failure
|
255
|
+
# @param reason [String, nil] Reason for task failure
|
256
|
+
# @param halt [Boolean] Whether to halt execution after failure
|
257
|
+
# @param cause [Exception, nil] Exception that caused the failure
|
258
|
+
# @param metadata [Hash] Additional metadata about the failure
|
333
259
|
#
|
334
|
-
# @
|
260
|
+
# @raise [RuntimeError] When attempting to fail from invalid status
|
335
261
|
#
|
336
|
-
# @
|
337
|
-
#
|
338
|
-
#
|
339
|
-
|
340
|
-
# result.fail!(reason: "Database connection failed")
|
341
|
-
# result.failed? #=> true
|
342
|
-
# result.metadata[:error] #=> "Database connection failed"
|
343
|
-
def fail!(**metadata)
|
262
|
+
# @example
|
263
|
+
# result.fail!("Validation failed", cause: validation_error)
|
264
|
+
# result.fail!("Network timeout", halt: false, timeout: 30)
|
265
|
+
def fail!(reason = nil, halt: true, cause: nil, **metadata)
|
344
266
|
return if failed?
|
345
267
|
|
346
268
|
raise "can only transition to #{FAILED} from #{SUCCESS}" unless success?
|
347
269
|
|
348
|
-
@
|
270
|
+
@state = INTERRUPTED
|
271
|
+
@status = FAILED
|
272
|
+
@reason = reason || Locale.t("cmdx.faults.unspecified")
|
273
|
+
@cause = cause
|
349
274
|
@metadata = metadata
|
350
275
|
|
351
|
-
halt!
|
276
|
+
halt! if halt
|
352
277
|
end
|
353
278
|
|
354
|
-
#
|
279
|
+
# @raise [SkipFault] When task was skipped
|
280
|
+
# @raise [FailFault] When task failed
|
355
281
|
#
|
356
|
-
# @
|
357
|
-
#
|
358
|
-
# @raise [CMDx::Fault] if the result is not successful
|
359
|
-
#
|
360
|
-
# @example Halt on failed result
|
361
|
-
# result.fail!(reason: "Something went wrong")
|
362
|
-
# result.halt! # raises CMDx::Fault
|
282
|
+
# @example
|
283
|
+
# result.halt! # Raises appropriate fault based on status
|
363
284
|
def halt!
|
364
285
|
return if success?
|
365
286
|
|
366
|
-
|
287
|
+
klass = skipped? ? SkipFault : FailFault
|
288
|
+
fault = klass.new(self)
|
289
|
+
|
290
|
+
# Strip the first two frames (this method and the delegator)
|
291
|
+
frames = caller_locations(3..-1)
|
292
|
+
fault.set_backtrace(frames.map(&:to_s)) unless frames.empty?
|
293
|
+
|
294
|
+
raise(fault)
|
367
295
|
end
|
368
296
|
|
369
|
-
#
|
370
|
-
#
|
371
|
-
# @param
|
372
|
-
# @param
|
297
|
+
# @param result [CMDx::Result] Result to throw from current result
|
298
|
+
# @param halt [Boolean] Whether to halt execution after throwing
|
299
|
+
# @param cause [Exception, nil] Exception that caused the throw
|
300
|
+
# @param metadata [Hash] Additional metadata to merge
|
373
301
|
#
|
374
|
-
# @
|
302
|
+
# @raise [TypeError] When result is not a CMDx::Result instance
|
375
303
|
#
|
376
|
-
# @
|
377
|
-
#
|
378
|
-
#
|
379
|
-
|
380
|
-
|
381
|
-
# current_result.throw!(failed_result)
|
382
|
-
# current_result.failed? #=> true
|
383
|
-
def throw!(result, local_metadata = {})
|
384
|
-
raise TypeError, "must be a Result" unless result.is_a?(Result)
|
304
|
+
# @example
|
305
|
+
# other_result = OtherTask.execute
|
306
|
+
# result.throw!(other_result, cause: upstream_error)
|
307
|
+
def throw!(result, halt: true, cause: nil, **metadata)
|
308
|
+
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
385
309
|
|
386
|
-
|
310
|
+
@state = result.state
|
311
|
+
@status = result.status
|
312
|
+
@reason = result.reason
|
313
|
+
@cause = cause || result.cause
|
314
|
+
@metadata = result.metadata.merge(metadata)
|
387
315
|
|
388
|
-
|
389
|
-
fail!(**md) if result.failed?
|
316
|
+
halt! if halt
|
390
317
|
end
|
391
318
|
|
392
|
-
#
|
393
|
-
#
|
394
|
-
# @return [CMDx::Result, nil] the result that caused the failure, or nil if not failed
|
319
|
+
# @return [CMDx::Result, nil] The result that caused this failure, or nil
|
395
320
|
#
|
396
|
-
# @example
|
397
|
-
# result.caused_failure
|
321
|
+
# @example
|
322
|
+
# cause = result.caused_failure
|
323
|
+
# puts "Caused by: #{cause.task.id}" if cause
|
398
324
|
def caused_failure
|
399
325
|
return unless failed?
|
400
326
|
|
401
327
|
chain.results.reverse.find(&:failed?)
|
402
328
|
end
|
403
329
|
|
404
|
-
#
|
330
|
+
# @return [Boolean] Whether this result caused the failure
|
405
331
|
#
|
406
|
-
# @
|
407
|
-
#
|
408
|
-
#
|
409
|
-
#
|
332
|
+
# @example
|
333
|
+
# if result.caused_failure?
|
334
|
+
# puts "This task caused the failure"
|
335
|
+
# end
|
410
336
|
def caused_failure?
|
411
337
|
return false unless failed?
|
412
338
|
|
413
339
|
caused_failure == self
|
414
340
|
end
|
415
341
|
|
416
|
-
#
|
342
|
+
# @return [CMDx::Result, nil] The result that threw this failure, or nil
|
417
343
|
#
|
418
|
-
# @
|
419
|
-
#
|
420
|
-
#
|
421
|
-
# result.threw_failure #=> #<Result task=PropagatorTask status=failed>
|
344
|
+
# @example
|
345
|
+
# thrown = result.threw_failure
|
346
|
+
# puts "Thrown by: #{thrown.task.id}" if thrown
|
422
347
|
def threw_failure
|
423
348
|
return unless failed?
|
424
349
|
|
350
|
+
current = index
|
425
351
|
results = chain.results.select(&:failed?)
|
426
|
-
results.find { |r| r.index >
|
352
|
+
results.find { |r| r.index > current } || results.last
|
427
353
|
end
|
428
354
|
|
429
|
-
#
|
430
|
-
#
|
431
|
-
# @return [Boolean] true if this result threw a failure
|
355
|
+
# @return [Boolean] Whether this result threw the failure
|
432
356
|
#
|
433
|
-
# @example
|
434
|
-
# result.threw_failure?
|
357
|
+
# @example
|
358
|
+
# if result.threw_failure?
|
359
|
+
# puts "This task threw the failure"
|
360
|
+
# end
|
435
361
|
def threw_failure?
|
436
362
|
return false unless failed?
|
437
363
|
|
438
364
|
threw_failure == self
|
439
365
|
end
|
440
366
|
|
441
|
-
#
|
442
|
-
#
|
443
|
-
# @return [Boolean] true if this result received a thrown failure
|
367
|
+
# @return [Boolean] Whether this result is a thrown failure
|
444
368
|
#
|
445
|
-
# @example
|
446
|
-
# result.thrown_failure?
|
369
|
+
# @example
|
370
|
+
# if result.thrown_failure?
|
371
|
+
# puts "This failure was thrown from another task"
|
372
|
+
# end
|
447
373
|
def thrown_failure?
|
448
374
|
failed? && !caused_failure?
|
449
375
|
end
|
450
376
|
|
451
|
-
#
|
452
|
-
#
|
453
|
-
# @return [Integer] the zero-based index of this result
|
377
|
+
# @return [Integer] Index of this result in the chain
|
454
378
|
#
|
455
|
-
# @example
|
456
|
-
# result.index
|
379
|
+
# @example
|
380
|
+
# position = result.index
|
381
|
+
# puts "Task #{position + 1} of #{chain.results.count}"
|
457
382
|
def index
|
458
383
|
chain.index(self)
|
459
384
|
end
|
460
385
|
|
461
|
-
#
|
386
|
+
# @return [String] The outcome of the task execution
|
462
387
|
#
|
463
|
-
# @
|
464
|
-
#
|
465
|
-
# @example Get result outcome
|
466
|
-
# result.outcome #=> "success"
|
467
|
-
# result.fail!
|
468
|
-
# result.outcome #=> "failed"
|
388
|
+
# @example
|
389
|
+
# result.outcome # => "success" or "interrupted"
|
469
390
|
def outcome
|
470
391
|
initialized? || thrown_failure? ? state : status
|
471
392
|
end
|
472
393
|
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# @param block [Proc] optional block to measure runtime for
|
394
|
+
# @return [Hash] Hash representation of the result
|
476
395
|
#
|
477
|
-
# @
|
478
|
-
#
|
479
|
-
#
|
480
|
-
# result.runtime #=> 1.234
|
481
|
-
#
|
482
|
-
# @example Measure runtime with block
|
483
|
-
# result.runtime { sleep 1 } #=> 1.0
|
484
|
-
def runtime(&)
|
485
|
-
return @runtime unless block_given?
|
486
|
-
|
487
|
-
@runtime = Utils::MonotonicRuntime.call(&)
|
488
|
-
end
|
489
|
-
|
490
|
-
# Converts the result to a hash representation
|
491
|
-
#
|
492
|
-
# @return [Hash] serialized representation of the result
|
493
|
-
#
|
494
|
-
# @example Convert to hash
|
495
|
-
# result.to_h #=> { state: "complete", status: "success", ... }
|
396
|
+
# @example
|
397
|
+
# result.to_h
|
398
|
+
# # => {state: "complete", status: "success", outcome: "success", metadata: {}}
|
496
399
|
def to_h
|
497
|
-
|
400
|
+
task.to_h.merge!(
|
401
|
+
state:,
|
402
|
+
status:,
|
403
|
+
outcome:,
|
404
|
+
metadata:
|
405
|
+
).tap do |hash|
|
406
|
+
if interrupted?
|
407
|
+
hash[:reason] = reason
|
408
|
+
hash[:cause] = cause
|
409
|
+
end
|
410
|
+
|
411
|
+
if failed?
|
412
|
+
STRIP_FAILURE.call(hash, self, :threw_failure)
|
413
|
+
STRIP_FAILURE.call(hash, self, :caused_failure)
|
414
|
+
end
|
415
|
+
end
|
498
416
|
end
|
499
417
|
|
500
|
-
#
|
418
|
+
# @return [String] String representation of the result
|
501
419
|
#
|
502
|
-
# @
|
503
|
-
#
|
504
|
-
# @example Convert to string
|
505
|
-
# result.to_s #=> "Result[complete/success]"
|
420
|
+
# @example
|
421
|
+
# result.to_s # => "task_id=my_task state=complete status=success"
|
506
422
|
def to_s
|
507
|
-
|
423
|
+
Utils::Format.to_str(to_h) do |key, value|
|
424
|
+
case key
|
425
|
+
when /failure/ then "#{key}=<[#{value[:index]}] #{value[:class]}: #{value[:id]}>"
|
426
|
+
else "#{key}=#{value.inspect}"
|
427
|
+
end
|
428
|
+
end
|
508
429
|
end
|
509
430
|
|
510
|
-
#
|
431
|
+
# @param keys [Array] Array of keys to deconstruct
|
511
432
|
#
|
512
|
-
# @return [Array
|
433
|
+
# @return [Array] Array containing state and status
|
513
434
|
#
|
514
|
-
# @example
|
515
|
-
#
|
516
|
-
#
|
517
|
-
|
518
|
-
# end
|
519
|
-
def deconstruct
|
435
|
+
# @example
|
436
|
+
# state, status = result.deconstruct
|
437
|
+
# puts "State: #{state}, Status: #{status}"
|
438
|
+
def deconstruct(*)
|
520
439
|
[state, status]
|
521
440
|
end
|
522
441
|
|
523
|
-
#
|
442
|
+
# @param keys [Array] Array of keys to deconstruct
|
524
443
|
#
|
525
|
-
# @
|
444
|
+
# @return [Hash] Hash with key-value pairs for pattern matching
|
526
445
|
#
|
527
|
-
# @
|
528
|
-
#
|
529
|
-
#
|
530
|
-
#
|
531
|
-
# in {
|
532
|
-
# puts "Task
|
446
|
+
# @example
|
447
|
+
# case result.deconstruct_keys
|
448
|
+
# in {state: "complete", good: true}
|
449
|
+
# puts "Task completed successfully"
|
450
|
+
# in {bad: true}
|
451
|
+
# puts "Task had issues"
|
533
452
|
# end
|
534
|
-
def deconstruct_keys(
|
535
|
-
|
453
|
+
def deconstruct_keys(*)
|
454
|
+
{
|
536
455
|
state: state,
|
537
456
|
status: status,
|
538
457
|
metadata: metadata,
|
@@ -540,10 +459,6 @@ module CMDx
|
|
540
459
|
good: good?,
|
541
460
|
bad: bad?
|
542
461
|
}
|
543
|
-
|
544
|
-
return attributes if keys.nil?
|
545
|
-
|
546
|
-
attributes.slice(*keys)
|
547
462
|
end
|
548
463
|
|
549
464
|
end
|