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