cmdx 1.0.1 → 1.1.1
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/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/result.rb
CHANGED
@@ -1,62 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Represents the execution result of a task, tracking state, status, and metadata.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# and
|
10
|
-
#
|
11
|
-
# @example Basic result usage
|
12
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
13
|
-
# result.success? # => true
|
14
|
-
# result.complete? # => true
|
15
|
-
# result.runtime # => 0.5
|
16
|
-
#
|
17
|
-
# @example Result with failure handling
|
18
|
-
# result = ProcessOrderTask.call(invalid_params)
|
19
|
-
# result.failed? # => true
|
20
|
-
# result.bad? # => true
|
21
|
-
# result.metadata # => { reason: "Invalid parameters" }
|
22
|
-
#
|
23
|
-
# @example Result state callbacks
|
24
|
-
# ProcessOrderTask.call(order_id: 123)
|
25
|
-
# .on_success { |result| logger.info "Order processed successfully" }
|
26
|
-
# .on_failed { |result| logger.error "Order processing failed: #{result.metadata[:reason]}" }
|
27
|
-
#
|
28
|
-
# @example Result chaining and failure propagation
|
29
|
-
# result1 = FirstTask.call
|
30
|
-
# result2 = SecondTask.call
|
31
|
-
# result2.throw!(result1) if result1.failed? # Propagate failure
|
32
|
-
#
|
33
|
-
# @see CMDx::Task Task execution and result creation
|
34
|
-
# @see CMDx::Chain Chain execution context and result tracking
|
35
|
-
# @see CMDx::Fault Fault handling for result failures
|
6
|
+
# Result objects encapsulate the outcome of task execution, providing detailed
|
7
|
+
# information about execution state (initialized, executing, complete, interrupted),
|
8
|
+
# status (success, skipped, failed), and associated metadata. They support
|
9
|
+
# state transitions, status changes, and provide introspection capabilities
|
10
|
+
# for debugging and monitoring task execution.
|
36
11
|
class Result
|
37
12
|
|
38
|
-
|
39
|
-
|
13
|
+
STATES = [
|
14
|
+
INITIALIZED = "initialized", # Initial state before execution
|
15
|
+
EXECUTING = "executing", # Currently executing task logic
|
16
|
+
COMPLETE = "complete", # Successfully completed execution
|
17
|
+
INTERRUPTED = "interrupted" # Execution was halted due to failure
|
18
|
+
].freeze
|
19
|
+
STATUSES = [
|
20
|
+
SUCCESS = "success", # Task completed successfully
|
21
|
+
SKIPPED = "skipped", # Task was skipped intentionally
|
22
|
+
FAILED = "failed" # Task failed due to error or validation
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
cmdx_attr_delegator :context, :chain,
|
26
|
+
to: :task
|
40
27
|
|
41
28
|
# @return [CMDx::Task] The task instance that generated this result
|
29
|
+
attr_reader :task
|
30
|
+
|
42
31
|
# @return [String] The current execution state (initialized, executing, complete, interrupted)
|
32
|
+
attr_reader :state
|
33
|
+
|
43
34
|
# @return [String] The current execution status (success, skipped, failed)
|
35
|
+
attr_reader :status
|
36
|
+
|
44
37
|
# @return [Hash] Additional metadata associated with the result
|
45
|
-
attr_reader :
|
38
|
+
attr_reader :metadata
|
46
39
|
|
47
|
-
# Initializes a new
|
40
|
+
# Initializes a new result for the given task
|
41
|
+
#
|
42
|
+
# @param task [CMDx::Task] the task to create a result for
|
48
43
|
#
|
49
|
-
#
|
50
|
-
# Results start in initialized state with success status.
|
44
|
+
# @return [CMDx::Result] a new result instance
|
51
45
|
#
|
52
|
-
# @
|
53
|
-
# @raise [TypeError] If task is not a Task or Workflow instance
|
46
|
+
# @raise [TypeError] if task is not a Task or Workflow
|
54
47
|
#
|
55
|
-
# @example
|
56
|
-
#
|
57
|
-
# result
|
58
|
-
# result.
|
59
|
-
# result.success? # => true
|
48
|
+
# @example Create a result for a task
|
49
|
+
# result = CMDx::Result.new(my_task)
|
50
|
+
# result.state #=> "initialized"
|
51
|
+
# result.status #=> "success"
|
60
52
|
def initialize(task)
|
61
53
|
raise TypeError, "must be a Task or Workflow" unless task.is_a?(Task)
|
62
54
|
|
@@ -66,27 +58,43 @@ module CMDx
|
|
66
58
|
@metadata = {}
|
67
59
|
end
|
68
60
|
|
69
|
-
# Available execution states for task results.
|
70
|
-
#
|
71
|
-
# States represent the execution lifecycle of a task from initialization
|
72
|
-
# through completion or interruption.
|
73
|
-
STATES = [
|
74
|
-
INITIALIZED = "initialized", # Initial state before execution
|
75
|
-
EXECUTING = "executing", # Currently executing task logic
|
76
|
-
COMPLETE = "complete", # Successfully completed execution
|
77
|
-
INTERRUPTED = "interrupted" # Execution was halted due to failure
|
78
|
-
].freeze
|
79
|
-
|
80
|
-
# Dynamically defines state predicate and callback methods.
|
81
|
-
#
|
82
|
-
# For each state, creates:
|
83
|
-
# - Predicate method (e.g., `executing?`)
|
84
|
-
# - Callback method (e.g., `on_executing`)
|
85
61
|
STATES.each do |s|
|
86
|
-
#
|
62
|
+
# Checks if the result is in the specified state.
|
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
|
74
|
+
#
|
75
|
+
# @example Check if result is interrupted
|
76
|
+
# result.interrupted? #=> false
|
87
77
|
define_method(:"#{s}?") { state == s }
|
88
78
|
|
89
|
-
#
|
79
|
+
# Executes the provided block if the result is in the specified state.
|
80
|
+
#
|
81
|
+
# @param block [Proc] the block to execute if result matches the state
|
82
|
+
#
|
83
|
+
# @return [Result] returns self for method chaining
|
84
|
+
#
|
85
|
+
# @raise [ArgumentError] if no block is provided
|
86
|
+
#
|
87
|
+
# @example Handle initialized state
|
88
|
+
# result.on_initialized { |r| puts "Task is ready to start" }
|
89
|
+
#
|
90
|
+
# @example Handle executing state
|
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" }
|
90
98
|
define_method(:"on_#{s}") do |&block|
|
91
99
|
raise ArgumentError, "block required" unless block
|
92
100
|
|
@@ -95,43 +103,45 @@ module CMDx
|
|
95
103
|
end
|
96
104
|
end
|
97
105
|
|
98
|
-
# Marks the result as executed
|
99
|
-
#
|
100
|
-
# Transitions to complete state if successful, or interrupted state
|
101
|
-
# if the task has failed or been skipped.
|
106
|
+
# Marks the result as executed by transitioning to complete or interrupted state
|
107
|
+
# based on the current status
|
102
108
|
#
|
103
109
|
# @return [void]
|
104
110
|
#
|
105
|
-
# @example
|
111
|
+
# @example Mark successful task as executed
|
106
112
|
# result.executed!
|
107
|
-
# result.complete?
|
113
|
+
# result.complete? #=> true
|
108
114
|
#
|
109
|
-
# @example
|
110
|
-
# result.fail!
|
115
|
+
# @example Mark failed task as executed
|
116
|
+
# result.fail!
|
111
117
|
# result.executed!
|
112
|
-
# result.interrupted?
|
118
|
+
# result.interrupted? #=> true
|
113
119
|
def executed!
|
114
120
|
success? ? complete! : interrupt!
|
115
121
|
end
|
116
122
|
|
117
|
-
# Checks if the result has been executed (
|
123
|
+
# Checks if the result has been executed (either complete or interrupted)
|
118
124
|
#
|
119
|
-
# @return [Boolean] true if result is complete or interrupted
|
125
|
+
# @return [Boolean] true if the result is complete or interrupted
|
120
126
|
#
|
121
|
-
# @example
|
122
|
-
# result.executed?
|
127
|
+
# @example Check if result was executed
|
128
|
+
# result.executed? #=> false
|
129
|
+
# result.executed!
|
130
|
+
# result.executed? #=> true
|
123
131
|
def executed?
|
124
132
|
complete? || interrupted?
|
125
133
|
end
|
126
134
|
|
127
|
-
# Executes
|
135
|
+
# Executes the provided block if the result has been executed
|
128
136
|
#
|
129
|
-
# @
|
130
|
-
# @return [Result] Self for method chaining
|
131
|
-
# @raise [ArgumentError] If no block is provided
|
137
|
+
# @param block [Proc] the block to execute if result was executed
|
132
138
|
#
|
133
|
-
# @
|
134
|
-
#
|
139
|
+
# @return [Result] returns self for method chaining
|
140
|
+
#
|
141
|
+
# @raise [ArgumentError] if no block is provided
|
142
|
+
#
|
143
|
+
# @example Handle executed results
|
144
|
+
# result.on_executed { |r| puts "Task execution finished" }
|
135
145
|
def on_executed(&)
|
136
146
|
raise ArgumentError, "block required" unless block_given?
|
137
147
|
|
@@ -139,14 +149,15 @@ module CMDx
|
|
139
149
|
self
|
140
150
|
end
|
141
151
|
|
142
|
-
# Transitions the result to executing state
|
152
|
+
# Transitions the result to executing state
|
143
153
|
#
|
144
154
|
# @return [void]
|
145
|
-
# @raise [RuntimeError] If not transitioning from initialized state
|
146
155
|
#
|
147
|
-
# @
|
156
|
+
# @raise [RuntimeError] if not transitioning from initialized state
|
157
|
+
#
|
158
|
+
# @example Start task execution
|
148
159
|
# result.executing!
|
149
|
-
# result.executing?
|
160
|
+
# result.executing? #=> true
|
150
161
|
def executing!
|
151
162
|
return if executing?
|
152
163
|
|
@@ -155,14 +166,16 @@ module CMDx
|
|
155
166
|
@state = EXECUTING
|
156
167
|
end
|
157
168
|
|
158
|
-
# Transitions the result to complete state
|
169
|
+
# Transitions the result to complete state
|
159
170
|
#
|
160
171
|
# @return [void]
|
161
|
-
# @raise [RuntimeError] If not transitioning from executing state
|
162
172
|
#
|
163
|
-
# @
|
173
|
+
# @raise [RuntimeError] if not transitioning from executing state
|
174
|
+
#
|
175
|
+
# @example Complete task execution
|
176
|
+
# result.executing!
|
164
177
|
# result.complete!
|
165
|
-
# result.complete?
|
178
|
+
# result.complete? #=> true
|
166
179
|
def complete!
|
167
180
|
return if complete?
|
168
181
|
|
@@ -171,14 +184,16 @@ module CMDx
|
|
171
184
|
@state = COMPLETE
|
172
185
|
end
|
173
186
|
|
174
|
-
# Transitions the result to interrupted state
|
187
|
+
# Transitions the result to interrupted state
|
175
188
|
#
|
176
189
|
# @return [void]
|
177
|
-
# @raise [RuntimeError] If trying to interrupt from complete state
|
178
190
|
#
|
179
|
-
# @
|
191
|
+
# @raise [RuntimeError] if transitioning from complete state
|
192
|
+
#
|
193
|
+
# @example Interrupt task execution
|
194
|
+
# result.executing!
|
180
195
|
# result.interrupt!
|
181
|
-
# result.interrupted?
|
196
|
+
# result.interrupted? #=> true
|
182
197
|
def interrupt!
|
183
198
|
return if interrupted?
|
184
199
|
|
@@ -187,25 +202,37 @@ module CMDx
|
|
187
202
|
@state = INTERRUPTED
|
188
203
|
end
|
189
204
|
|
190
|
-
# Available execution statuses for task results.
|
191
|
-
#
|
192
|
-
# Statuses represent the outcome of task logic execution.
|
193
|
-
STATUSES = [
|
194
|
-
SUCCESS = "success", # Task completed successfully
|
195
|
-
SKIPPED = "skipped", # Task was skipped intentionally
|
196
|
-
FAILED = "failed" # Task failed due to error or validation
|
197
|
-
].freeze
|
198
|
-
|
199
|
-
# Dynamically defines status predicate and callback methods.
|
200
|
-
#
|
201
|
-
# For each status, creates:
|
202
|
-
# - Predicate method (e.g., `success?`)
|
203
|
-
# - Callback method (e.g., `on_success`)
|
204
205
|
STATUSES.each do |s|
|
205
|
-
#
|
206
|
+
# Checks if the result has the specified status.
|
207
|
+
#
|
208
|
+
# @return [Boolean] true if the result matches the status
|
209
|
+
#
|
210
|
+
# @example Check if result is successful
|
211
|
+
# result.success? #=> true
|
212
|
+
#
|
213
|
+
# @example Check if result is skipped
|
214
|
+
# result.skipped? #=> false
|
215
|
+
#
|
216
|
+
# @example Check if result is failed
|
217
|
+
# result.failed? #=> false
|
206
218
|
define_method(:"#{s}?") { status == s }
|
207
219
|
|
208
|
-
#
|
220
|
+
# Executes the provided block if the result has the specified status.
|
221
|
+
#
|
222
|
+
# @param block [Proc] the block to execute if result matches the status
|
223
|
+
#
|
224
|
+
# @return [Result] returns self for method chaining
|
225
|
+
#
|
226
|
+
# @raise [ArgumentError] if no block is provided
|
227
|
+
#
|
228
|
+
# @example Handle successful status
|
229
|
+
# result.on_success { |r| puts "Task completed successfully" }
|
230
|
+
#
|
231
|
+
# @example Handle skipped status
|
232
|
+
# result.on_skipped { |r| puts "Task was skipped: #{r.metadata[:reason]}" }
|
233
|
+
#
|
234
|
+
# @example Handle failed status
|
235
|
+
# result.on_failed { |r| puts "Task failed: #{r.metadata[:error]}" }
|
209
236
|
define_method(:"on_#{s}") do |&block|
|
210
237
|
raise ArgumentError, "block required" unless block
|
211
238
|
|
@@ -214,24 +241,28 @@ module CMDx
|
|
214
241
|
end
|
215
242
|
end
|
216
243
|
|
217
|
-
# Checks if the result
|
244
|
+
# Checks if the result has a good outcome (not failed)
|
218
245
|
#
|
219
|
-
# @return [Boolean] true if not failed
|
246
|
+
# @return [Boolean] true if the result is not failed
|
220
247
|
#
|
221
|
-
# @example
|
222
|
-
# result.good?
|
248
|
+
# @example Check for good outcome
|
249
|
+
# result.good? #=> true (initially successful)
|
250
|
+
# result.fail!
|
251
|
+
# result.good? #=> false
|
223
252
|
def good?
|
224
253
|
!failed?
|
225
254
|
end
|
226
255
|
|
227
|
-
# Executes
|
256
|
+
# Executes the provided block if the result has a good outcome
|
228
257
|
#
|
229
|
-
# @
|
230
|
-
# @return [Result] Self for method chaining
|
231
|
-
# @raise [ArgumentError] If no block is provided
|
258
|
+
# @param block [Proc] the block to execute if result is good
|
232
259
|
#
|
233
|
-
# @
|
234
|
-
#
|
260
|
+
# @return [Result] returns self for method chaining
|
261
|
+
#
|
262
|
+
# @raise [ArgumentError] if no block is provided
|
263
|
+
#
|
264
|
+
# @example Handle good results
|
265
|
+
# result.on_good { |r| puts "Task had good outcome" }
|
235
266
|
def on_good(&)
|
236
267
|
raise ArgumentError, "block required" unless block_given?
|
237
268
|
|
@@ -239,24 +270,28 @@ module CMDx
|
|
239
270
|
self
|
240
271
|
end
|
241
272
|
|
242
|
-
# Checks if the result
|
273
|
+
# Checks if the result has a bad outcome (not successful)
|
243
274
|
#
|
244
|
-
# @return [Boolean] true if not successful
|
275
|
+
# @return [Boolean] true if the result is not successful
|
245
276
|
#
|
246
|
-
# @example
|
247
|
-
# result.bad?
|
277
|
+
# @example Check for bad outcome
|
278
|
+
# result.bad? #=> false (initially successful)
|
279
|
+
# result.skip!
|
280
|
+
# result.bad? #=> true
|
248
281
|
def bad?
|
249
282
|
!success?
|
250
283
|
end
|
251
284
|
|
252
|
-
# Executes
|
285
|
+
# Executes the provided block if the result has a bad outcome
|
286
|
+
#
|
287
|
+
# @param block [Proc] the block to execute if result is bad
|
253
288
|
#
|
254
|
-
# @
|
255
|
-
# @return [Result] Self for method chaining
|
256
|
-
# @raise [ArgumentError] If no block is provided
|
289
|
+
# @return [Result] returns self for method chaining
|
257
290
|
#
|
258
|
-
# @
|
259
|
-
#
|
291
|
+
# @raise [ArgumentError] if no block is provided
|
292
|
+
#
|
293
|
+
# @example Handle bad outcome
|
294
|
+
# result.on_bad { |r| puts "Task had bad outcome: #{r.status}" }
|
260
295
|
def on_bad(&)
|
261
296
|
raise ArgumentError, "block required" unless block_given?
|
262
297
|
|
@@ -264,22 +299,21 @@ module CMDx
|
|
264
299
|
self
|
265
300
|
end
|
266
301
|
|
267
|
-
#
|
302
|
+
# Transitions the result to skipped status and sets metadata
|
268
303
|
#
|
269
|
-
#
|
270
|
-
#
|
304
|
+
# @param metadata [Hash] additional metadata about why the task was skipped
|
305
|
+
# @option metadata [String] :reason the reason for skipping
|
306
|
+
# @option metadata [Exception] :original_exception the original exception that caused skipping
|
271
307
|
#
|
272
|
-
# @param metadata [Hash] Additional metadata about the skip
|
273
308
|
# @return [void]
|
274
|
-
# @raise [RuntimeError] If not transitioning from success status
|
275
|
-
# @raise [CMDx::Fault] If halting due to skip (unless original_exception present)
|
276
309
|
#
|
277
|
-
# @
|
278
|
-
#
|
279
|
-
# result.skipped? # => true
|
310
|
+
# @raise [RuntimeError] if not transitioning from success status
|
311
|
+
# @raise [CMDx::Fault] if no original_exception in metadata (via halt!)
|
280
312
|
#
|
281
|
-
# @example Skip with
|
282
|
-
# result.skip!(
|
313
|
+
# @example Skip a task with reason
|
314
|
+
# result.skip!(reason: "Dependencies not met")
|
315
|
+
# result.skipped? #=> true
|
316
|
+
# result.metadata[:reason] #=> "Dependencies not met"
|
283
317
|
def skip!(**metadata)
|
284
318
|
return if skipped?
|
285
319
|
|
@@ -291,22 +325,21 @@ module CMDx
|
|
291
325
|
halt! unless metadata[:original_exception]
|
292
326
|
end
|
293
327
|
|
294
|
-
#
|
328
|
+
# Transitions the result to failed status and sets metadata
|
295
329
|
#
|
296
|
-
#
|
297
|
-
#
|
330
|
+
# @param metadata [Hash] additional metadata about the failure
|
331
|
+
# @option metadata [String] :error the error message
|
332
|
+
# @option metadata [Exception] :original_exception the original exception that caused failure
|
298
333
|
#
|
299
|
-
# @param metadata [Hash] Additional metadata about the failure
|
300
334
|
# @return [void]
|
301
|
-
# @raise [RuntimeError] If not transitioning from success status
|
302
|
-
# @raise [CMDx::Fault] If halting due to failure (unless original_exception present)
|
303
335
|
#
|
304
|
-
# @
|
305
|
-
#
|
306
|
-
# result.failed? # => true
|
336
|
+
# @raise [RuntimeError] if not transitioning from success status
|
337
|
+
# @raise [CMDx::Fault] if no original_exception in metadata (via halt!)
|
307
338
|
#
|
308
|
-
# @example
|
309
|
-
# result.fail!(
|
339
|
+
# @example Fail a task with error message
|
340
|
+
# result.fail!(reason: "Database connection failed")
|
341
|
+
# result.failed? #=> true
|
342
|
+
# result.metadata[:error] #=> "Database connection failed"
|
310
343
|
def fail!(**metadata)
|
311
344
|
return if failed?
|
312
345
|
|
@@ -318,37 +351,35 @@ module CMDx
|
|
318
351
|
halt! unless metadata[:original_exception]
|
319
352
|
end
|
320
353
|
|
321
|
-
# Halts execution by raising a fault if the result is not successful
|
354
|
+
# Halts execution by raising a fault if the result is not successful
|
322
355
|
#
|
323
356
|
# @return [void]
|
324
|
-
# @raise [CMDx::Fault] If result status is not success
|
325
357
|
#
|
326
|
-
# @
|
358
|
+
# @raise [CMDx::Fault] if the result is not successful
|
359
|
+
#
|
360
|
+
# @example Halt on failed result
|
327
361
|
# result.fail!(reason: "Something went wrong")
|
328
|
-
# result.halt!
|
362
|
+
# result.halt! # raises CMDx::Fault
|
329
363
|
def halt!
|
330
364
|
return if success?
|
331
365
|
|
332
366
|
raise Fault.build(self)
|
333
367
|
end
|
334
368
|
|
335
|
-
#
|
369
|
+
# Throws the status and metadata from another result to this result
|
336
370
|
#
|
337
|
-
#
|
338
|
-
#
|
371
|
+
# @param result [CMDx::Result] the result to throw from
|
372
|
+
# @param local_metadata [Hash] additional metadata to merge
|
339
373
|
#
|
340
|
-
# @param result [CMDx::Result] The result to propagate from
|
341
|
-
# @param local_metadata [Hash] Additional metadata to merge
|
342
374
|
# @return [void]
|
343
|
-
# @raise [TypeError] If result parameter is not a Result instance
|
344
375
|
#
|
345
|
-
# @
|
346
|
-
# first_result = FirstTask.call
|
347
|
-
# second_result = SecondTask.call
|
348
|
-
# second_result.throw!(first_result) if first_result.failed?
|
376
|
+
# @raise [TypeError] if result is not a Result instance
|
349
377
|
#
|
350
|
-
# @example
|
351
|
-
#
|
378
|
+
# @example Throw from a failed result
|
379
|
+
# failed_result = Result.new(task)
|
380
|
+
# failed_result.fail!(reason: "network timeout")
|
381
|
+
# current_result.throw!(failed_result)
|
382
|
+
# current_result.failed? #=> true
|
352
383
|
def throw!(result, local_metadata = {})
|
353
384
|
raise TypeError, "must be a Result" unless result.is_a?(Result)
|
354
385
|
|
@@ -358,38 +389,36 @@ module CMDx
|
|
358
389
|
fail!(**md) if result.failed?
|
359
390
|
end
|
360
391
|
|
361
|
-
# Finds the result that originally caused a failure in the
|
392
|
+
# Finds the result that originally caused a failure in the chain
|
362
393
|
#
|
363
|
-
# @return [CMDx::Result, nil]
|
394
|
+
# @return [CMDx::Result, nil] the result that caused the failure, or nil if not failed
|
364
395
|
#
|
365
|
-
# @example
|
366
|
-
#
|
367
|
-
# puts "Original failure: #{failed_result.metadata[:reason]}" if failed_result
|
396
|
+
# @example Find the original failure cause
|
397
|
+
# result.caused_failure #=> #<Result task=OriginalTask status=failed>
|
368
398
|
def caused_failure
|
369
399
|
return unless failed?
|
370
400
|
|
371
401
|
chain.results.reverse.find(&:failed?)
|
372
402
|
end
|
373
403
|
|
374
|
-
# Checks if this result
|
404
|
+
# Checks if this result caused a failure in the chain
|
375
405
|
#
|
376
|
-
# @return [Boolean] true if this result caused the failure
|
406
|
+
# @return [Boolean] true if this result caused the failure
|
377
407
|
#
|
378
|
-
# @example
|
379
|
-
# result.caused_failure?
|
408
|
+
# @example Check if result caused failure
|
409
|
+
# result.caused_failure? #=> true
|
380
410
|
def caused_failure?
|
381
411
|
return false unless failed?
|
382
412
|
|
383
413
|
caused_failure == self
|
384
414
|
end
|
385
415
|
|
386
|
-
# Finds the result that
|
416
|
+
# Finds the result that this failure was thrown to
|
387
417
|
#
|
388
|
-
# @return [CMDx::Result, nil]
|
418
|
+
# @return [CMDx::Result, nil] the result that received the thrown failure, or nil if not failed
|
389
419
|
#
|
390
|
-
# @example
|
391
|
-
#
|
392
|
-
# puts "Failure thrown by: #{throwing_result.task.class}" if throwing_result
|
420
|
+
# @example Find where failure was thrown
|
421
|
+
# result.threw_failure #=> #<Result task=PropagatorTask status=failed>
|
393
422
|
def threw_failure
|
394
423
|
return unless failed?
|
395
424
|
|
@@ -397,145 +426,111 @@ module CMDx
|
|
397
426
|
results.find { |r| r.index > index } || results.last
|
398
427
|
end
|
399
428
|
|
400
|
-
# Checks if this result threw
|
429
|
+
# Checks if this result threw a failure to another result
|
401
430
|
#
|
402
|
-
# @return [Boolean] true if this result threw a failure
|
431
|
+
# @return [Boolean] true if this result threw a failure
|
403
432
|
#
|
404
|
-
# @example
|
405
|
-
# result.threw_failure?
|
433
|
+
# @example Check if result threw failure
|
434
|
+
# result.threw_failure? #=> false
|
406
435
|
def threw_failure?
|
407
436
|
return false unless failed?
|
408
437
|
|
409
438
|
threw_failure == self
|
410
439
|
end
|
411
440
|
|
412
|
-
# Checks if this result received a thrown failure
|
441
|
+
# Checks if this result received a thrown failure from another result
|
413
442
|
#
|
414
|
-
# @return [Boolean] true if
|
443
|
+
# @return [Boolean] true if this result received a thrown failure
|
415
444
|
#
|
416
|
-
# @example
|
417
|
-
# result.thrown_failure?
|
445
|
+
# @example Check if result received thrown failure
|
446
|
+
# result.thrown_failure? #=> true
|
418
447
|
def thrown_failure?
|
419
448
|
failed? && !caused_failure?
|
420
449
|
end
|
421
450
|
|
422
|
-
#
|
451
|
+
# Returns the index of this result in the chain
|
423
452
|
#
|
424
|
-
# @return [Integer]
|
453
|
+
# @return [Integer] the zero-based index of this result
|
425
454
|
#
|
426
|
-
# @example
|
427
|
-
# result.index
|
455
|
+
# @example Get result index
|
456
|
+
# result.index #=> 2
|
428
457
|
def index
|
429
458
|
chain.index(self)
|
430
459
|
end
|
431
460
|
|
432
|
-
#
|
461
|
+
# Returns the outcome of the result (state for certain cases, status otherwise)
|
433
462
|
#
|
434
|
-
#
|
435
|
-
# otherwise returns the status.
|
463
|
+
# @return [String] the outcome (state or status)
|
436
464
|
#
|
437
|
-
# @
|
438
|
-
#
|
439
|
-
#
|
440
|
-
# result.outcome
|
465
|
+
# @example Get result outcome
|
466
|
+
# result.outcome #=> "success"
|
467
|
+
# result.fail!
|
468
|
+
# result.outcome #=> "failed"
|
441
469
|
def outcome
|
442
470
|
initialized? || thrown_failure? ? state : status
|
443
471
|
end
|
444
472
|
|
445
|
-
#
|
473
|
+
# Gets or measures the runtime of the result
|
446
474
|
#
|
447
|
-
#
|
448
|
-
# If called with a block, executes and measures the execution
|
449
|
-
# time using monotonic clock.
|
475
|
+
# @param block [Proc] optional block to measure runtime for
|
450
476
|
#
|
451
|
-
# @
|
452
|
-
# @return [Float] Runtime in seconds
|
477
|
+
# @return [Float, nil] the runtime in seconds, or nil if not measured
|
453
478
|
#
|
454
|
-
# @example
|
455
|
-
# result.runtime
|
479
|
+
# @example Get existing runtime
|
480
|
+
# result.runtime #=> 1.234
|
456
481
|
#
|
457
|
-
# @example
|
458
|
-
# result.runtime
|
459
|
-
# # Task execution logic
|
460
|
-
# perform_work
|
461
|
-
# end # => 0.5 (and stores the runtime)
|
482
|
+
# @example Measure runtime with block
|
483
|
+
# result.runtime { sleep 1 } #=> 1.0
|
462
484
|
def runtime(&)
|
463
485
|
return @runtime unless block_given?
|
464
486
|
|
465
487
|
@runtime = Utils::MonotonicRuntime.call(&)
|
466
488
|
end
|
467
489
|
|
468
|
-
# Converts the result to a hash representation
|
469
|
-
#
|
470
|
-
# @return [Hash]
|
471
|
-
#
|
472
|
-
# @example
|
473
|
-
# result.to_h
|
474
|
-
# # => {
|
475
|
-
# # class: "ProcessOrderTask",
|
476
|
-
# # type: "Task",
|
477
|
-
# # index: 0,
|
478
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
479
|
-
# # state: "complete",
|
480
|
-
# # status: "success",
|
481
|
-
# # outcome: "success",
|
482
|
-
# # metadata: {},
|
483
|
-
# # runtime: 0.5
|
484
|
-
# # }
|
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", ... }
|
485
496
|
def to_h
|
486
497
|
ResultSerializer.call(self)
|
487
498
|
end
|
488
499
|
|
489
|
-
#
|
500
|
+
# Returns a string representation of the result
|
490
501
|
#
|
491
|
-
# @return [String]
|
502
|
+
# @return [String] formatted string representation
|
492
503
|
#
|
493
|
-
# @example
|
494
|
-
# result.to_s
|
495
|
-
# # => "ProcessOrderTask: type=Task index=0 id=018c2b95... state=complete status=success outcome=success runtime=0.5"
|
504
|
+
# @example Convert to string
|
505
|
+
# result.to_s #=> "Result[complete/success]"
|
496
506
|
def to_s
|
497
507
|
ResultInspector.call(to_h)
|
498
508
|
end
|
499
509
|
|
500
|
-
# Deconstructs the result for
|
501
|
-
#
|
502
|
-
# Enables pattern matching with array syntax to match against
|
503
|
-
# state and status in order.
|
510
|
+
# Deconstructs the result for pattern matching
|
504
511
|
#
|
505
|
-
# @return [Array<String>]
|
512
|
+
# @return [Array<String>] array containing state and status
|
506
513
|
#
|
507
|
-
# @example
|
508
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
514
|
+
# @example Pattern matching with deconstruct
|
509
515
|
# case result
|
510
516
|
# in ["complete", "success"]
|
511
517
|
# puts "Task completed successfully"
|
512
|
-
# in ["interrupted", "failed"]
|
513
|
-
# puts "Task failed"
|
514
518
|
# end
|
515
519
|
def deconstruct
|
516
520
|
[state, status]
|
517
521
|
end
|
518
522
|
|
519
|
-
# Deconstructs the result for
|
523
|
+
# Deconstructs the result with keys for pattern matching
|
520
524
|
#
|
521
|
-
#
|
522
|
-
# specific result attributes.
|
525
|
+
# @param keys [Array<Symbol>, nil] specific keys to include in deconstruction
|
523
526
|
#
|
524
|
-
# @
|
525
|
-
# @return [Hash] Hash containing result attributes
|
527
|
+
# @return [Hash] hash with requested attributes
|
526
528
|
#
|
527
|
-
# @example
|
528
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
529
|
+
# @example Pattern matching with specific keys
|
529
530
|
# case result
|
530
|
-
# in { state: "complete",
|
531
|
-
# puts "
|
532
|
-
# in { state: "interrupted", status: "failed", metadata: { reason: String => reason } }
|
533
|
-
# puts "Failed: #{reason}"
|
531
|
+
# in { state: "complete", good: true }
|
532
|
+
# puts "Task finished well"
|
534
533
|
# end
|
535
|
-
#
|
536
|
-
# @example Specific key extraction
|
537
|
-
# result.deconstruct_keys([:state, :status])
|
538
|
-
# # => { state: "complete", status: "success" }
|
539
534
|
def deconstruct_keys(keys)
|
540
535
|
attributes = {
|
541
536
|
state: state,
|