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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. 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
- # Result object representing the outcome of task execution.
4
+ # Represents the execution result of a task, tracking state, status, and metadata.
5
5
  #
6
- # The Result class encapsulates all information about a task's execution,
7
- # including its state, status, metadata, and runtime information. It provides
8
- # a comprehensive interface for tracking task lifecycle, handling failures,
9
- # and chaining execution outcomes.
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
- __cmdx_attr_delegator :context, :chain,
39
- to: :task
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 :task, :state, :status, :metadata
38
+ attr_reader :metadata
46
39
 
47
- # Initializes a new Result instance.
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
- # Creates a result object for tracking task execution outcomes.
50
- # Results start in initialized state with success status.
44
+ # @return [CMDx::Result] a new result instance
51
45
  #
52
- # @param task [CMDx::Task] The task instance this result belongs to
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 Creating a result
56
- # task = ProcessOrderTask.new
57
- # result = Result.new(task)
58
- # result.initialized? # => true
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
- # eg: executing?
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
- # eg: on_interrupted { ... }
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 based on current status.
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 Successful execution
111
+ # @example Mark successful task as executed
106
112
  # result.executed!
107
- # result.complete? # => true (if status was success)
113
+ # result.complete? #=> true
108
114
  #
109
- # @example Failed execution
110
- # result.fail!(reason: "Something went wrong")
115
+ # @example Mark failed task as executed
116
+ # result.fail!
111
117
  # result.executed!
112
- # result.interrupted? # => true
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 (completed or interrupted).
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? # => true if complete? || interrupted?
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 a callback if the result has been executed.
135
+ # Executes the provided block if the result has been executed
128
136
  #
129
- # @yield [Result] The result instance
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
- # @example
134
- # result.on_executed { |r| logger.info "Task finished: #{r.status}" }
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
- # @example
156
+ # @raise [RuntimeError] if not transitioning from initialized state
157
+ #
158
+ # @example Start task execution
148
159
  # result.executing!
149
- # result.executing? # => true
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
- # @example
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? # => true
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
- # @example
191
+ # @raise [RuntimeError] if transitioning from complete state
192
+ #
193
+ # @example Interrupt task execution
194
+ # result.executing!
180
195
  # result.interrupt!
181
- # result.interrupted? # => true
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
- # eg: skipped?
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
- # eg: on_failed { ... }
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 represents a good outcome (success or skipped).
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? # => true if success? || skipped?
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 a callback if the result has a good outcome.
256
+ # Executes the provided block if the result has a good outcome
228
257
  #
229
- # @yield [Result] The result instance
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
- # @example
234
- # result.on_good { |r| logger.info "Task completed successfully" }
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 represents a bad outcome (skipped or failed).
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? # => true if skipped? || failed?
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 a callback if the result has a bad outcome.
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
- # @yield [Result] The result instance
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
- # @example
259
- # result.on_bad { |r| logger.error "Task had issues: #{r.status}" }
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
- # Marks the result as skipped with optional metadata.
302
+ # Transitions the result to skipped status and sets metadata
268
303
  #
269
- # Transitions from success to skipped status and halts execution
270
- # unless the skip was caused by an original exception.
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
- # @example Basic skip
278
- # result.skip!(reason: "Order already processed")
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 exception context
282
- # result.skip!(original_exception: StandardError.new("DB unavailable"))
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
- # Marks the result as failed with optional metadata.
328
+ # Transitions the result to failed status and sets metadata
295
329
  #
296
- # Transitions from success to failed status and halts execution
297
- # unless the failure was caused by an original exception.
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
- # @example Basic failure
305
- # result.fail!(reason: "Invalid order data", code: 422)
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 Failure with exception context
309
- # result.fail!(original_exception: StandardError.new("Validation failed"))
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
- # @example
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! # Raises CMDx::Fault
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
- # Propagates another result's failure status to this result.
369
+ # Throws the status and metadata from another result to this result
336
370
  #
337
- # Copies the failure or skip status from another result, merging
338
- # metadata and preserving failure chain information.
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
- # @example Propagating failure
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 Propagating with additional context
351
- # result.throw!(other_result, context: "During order processing")
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 execution chain.
392
+ # Finds the result that originally caused a failure in the chain
362
393
  #
363
- # @return [CMDx::Result, nil] The result that first failed, or nil if not failed
394
+ # @return [CMDx::Result, nil] the result that caused the failure, or nil if not failed
364
395
  #
365
- # @example
366
- # failed_result = result.caused_failure
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 was the original cause of failure.
404
+ # Checks if this result caused a failure in the chain
375
405
  #
376
- # @return [Boolean] true if this result caused the failure chain
406
+ # @return [Boolean] true if this result caused the failure
377
407
  #
378
- # @example
379
- # result.caused_failure? # => true if this result started the failure chain
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 threw/propagated the failure to this result.
416
+ # Finds the result that this failure was thrown to
387
417
  #
388
- # @return [CMDx::Result, nil] The result that threw the failure, or nil if not failed
418
+ # @return [CMDx::Result, nil] the result that received the thrown failure, or nil if not failed
389
419
  #
390
- # @example
391
- # throwing_result = result.threw_failure
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/propagated a failure.
429
+ # Checks if this result threw a failure to another result
401
430
  #
402
- # @return [Boolean] true if this result threw a failure to another result
431
+ # @return [Boolean] true if this result threw a failure
403
432
  #
404
- # @example
405
- # result.threw_failure? # => true if this result propagated 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 (not the original cause).
441
+ # Checks if this result received a thrown failure from another result
413
442
  #
414
- # @return [Boolean] true if failed but not the original cause
443
+ # @return [Boolean] true if this result received a thrown failure
415
444
  #
416
- # @example
417
- # result.thrown_failure? # => true if failed due to propagated 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
- # Gets the index of this result within the execution chain.
451
+ # Returns the index of this result in the chain
423
452
  #
424
- # @return [Integer] The zero-based index of this result in the chain
453
+ # @return [Integer] the zero-based index of this result
425
454
  #
426
- # @example
427
- # result.index # => 0 for first result, 1 for second, etc.
455
+ # @example Get result index
456
+ # result.index #=> 2
428
457
  def index
429
458
  chain.index(self)
430
459
  end
431
460
 
432
- # Gets the outcome of the result based on state and status.
461
+ # Returns the outcome of the result (state for certain cases, status otherwise)
433
462
  #
434
- # Returns state for initialized results or thrown failures,
435
- # otherwise returns the status.
463
+ # @return [String] the outcome (state or status)
436
464
  #
437
- # @return [String] The result outcome (state or status)
438
- #
439
- # @example
440
- # result.outcome # => "success", "failed", "interrupted", etc.
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
- # Measures and returns the runtime of a block execution.
473
+ # Gets or measures the runtime of the result
446
474
  #
447
- # If called without a block, returns the stored runtime value.
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
- # @yield Block to execute and measure
452
- # @return [Float] Runtime in seconds
477
+ # @return [Float, nil] the runtime in seconds, or nil if not measured
453
478
  #
454
- # @example Getting stored runtime
455
- # result.runtime # => 0.5
479
+ # @example Get existing runtime
480
+ # result.runtime #=> 1.234
456
481
  #
457
- # @example Measuring block execution
458
- # result.runtime do
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] Serialized result data including task info, state, status, and metadata
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
- # Converts the result to a string representation for inspection.
500
+ # Returns a string representation of the result
490
501
  #
491
- # @return [String] Human-readable result description
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 array pattern matching.
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>] Array containing [state, status]
512
+ # @return [Array<String>] array containing state and status
506
513
  #
507
- # @example Array pattern matching
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 hash pattern matching.
523
+ # Deconstructs the result with keys for pattern matching
520
524
  #
521
- # Enables pattern matching with hash syntax to match against
522
- # specific result attributes.
525
+ # @param keys [Array<Symbol>, nil] specific keys to include in deconstruction
523
526
  #
524
- # @param keys [Array<Symbol>] Specific keys to extract (optional)
525
- # @return [Hash] Hash containing result attributes
527
+ # @return [Hash] hash with requested attributes
526
528
  #
527
- # @example Hash pattern matching
528
- # result = ProcessOrderTask.call(order_id: 123)
529
+ # @example Pattern matching with specific keys
529
530
  # case result
530
- # in { state: "complete", status: "success" }
531
- # puts "Success!"
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,