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/task.rb CHANGED
@@ -1,157 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- ##
5
- # Task is the base class for all command objects in CMDx, providing a framework
6
- # for encapsulating business logic with parameter validation, callbacks, and result tracking.
4
+ # Core task implementation providing executable units of work with parameter management.
7
5
  #
8
- # Tasks follow a single-use pattern where each instance can only be executed once,
9
- # after which it becomes frozen and immutable. This ensures predictable execution
10
- # and prevents side effects from multiple calls.
11
- #
12
- # @example Basic task definition
13
- # class ProcessOrderTask < CMDx::Task
14
- # required :order_id, type: :integer
15
- # optional :notify_user, type: :boolean, default: true
16
- #
17
- # def call
18
- # # Business logic here
19
- # context.order = Order.find(order_id)
20
- # skip!(reason: "Order already processed") if context.order.processed?
21
- #
22
- # context.order.process!
23
- # NotificationService.call(order_id: order_id) if notify_user
24
- # end
25
- # end
26
- #
27
- # @example Task execution
28
- # result = ProcessOrderTask.call(order_id: 123, notify_user: false)
29
- # result.success? #=> true
30
- # result.context.order #=> <Order id: 123>
31
- #
32
- # @example Task chaining with Result objects
33
- # # First task extracts data
34
- # class ExtractDataTask < CMDx::Task
35
- # required :source_id, type: :integer
36
- #
37
- # def call
38
- # context.extracted_data = DataSource.extract(source_id)
39
- # context.extraction_time = Time.now
40
- # end
41
- # end
42
- #
43
- # # Second task processes the extracted data
44
- # class ProcessDataTask < CMDx::Task
45
- # def call
46
- # # Access data from previous task's context
47
- # fail!(reason: "No data to process") unless context.extracted_data
48
- #
49
- # context.processed_data = DataProcessor.process(context.extracted_data)
50
- # context.processing_time = Time.now
51
- # end
52
- # end
53
- #
54
- # # Chain tasks by passing Result objects
55
- # extraction_result = ExtractDataTask.call(source_id: 123)
56
- # processing_result = ProcessDataTask.call(extraction_result)
57
- #
58
- # # Result object context is automatically extracted
59
- # processing_result.context.extracted_data #=> data from first task
60
- # processing_result.context.processed_data #=> data from second task
61
- #
62
- # @example Using callbacks
63
- # class ProcessOrderTask < CMDx::Task
64
- # before_validation :log_start
65
- # after_execution :cleanup_resources
66
- # on_success :send_confirmation
67
- # on_failure :alert_support, if: :critical_order?
68
- #
69
- # def call
70
- # # Implementation
71
- # end
72
- #
73
- # private
74
- #
75
- # def critical_order?
76
- # context.order.value > 10_000
77
- # end
78
- # end
79
- #
80
- # == Task Chaining and Data Flow
81
- #
82
- # Tasks can be seamlessly chained by passing Result objects between them.
83
- # This enables powerful workflows where the output of one task becomes the
84
- # input for the next, maintaining data consistency and enabling complex
85
- # business logic composition.
86
- #
87
- # Benefits of Result object chaining:
88
- # - Automatic context extraction and data flow
89
- # - Preserves all context data including custom attributes
90
- # - Maintains execution chain relationships
91
- # - Enables conditional task execution based on previous results
92
- # - Simplifies error handling and rollback scenarios
93
- #
94
- # @see Result Result object for execution outcomes
95
- # @see Context Context object for parameter access
96
- # @see ParameterRegistry Parameter definition and validation
97
- # @see Workflow Workflow for executing multiple tasks
98
- # @since 1.0.0
6
+ # Task is the fundamental building block of the CMDx framework, providing a structured
7
+ # approach to implementing business logic with built-in parameter validation, middleware
8
+ # support, callback handling, and comprehensive result tracking. Tasks encapsulate
9
+ # discrete units of work that can be chained together into workflows or executed
10
+ # independently with rich execution context and error handling.
99
11
  class Task
100
12
 
101
- ##
102
- # Available callback types for task lifecycle events.
103
- # Callbacks are executed in a specific order during task execution.
104
- #
105
- # @return [Array<Symbol>] frozen array of available callback names
106
- CALLBACKS = [
107
- :before_validation,
108
- :after_validation,
109
- :before_execution,
110
- :after_execution,
111
- :on_executed,
112
- :on_good,
113
- :on_bad,
114
- *Result::STATUSES.map { |s| :"on_#{s}" },
115
- *Result::STATES.map { |s| :"on_#{s}" }
116
- ].freeze
117
-
118
- __cmdx_attr_setting :task_settings,
119
- default: -> { CMDx.configuration.to_h.merge(tags: []) }
120
- __cmdx_attr_setting :cmd_middlewares,
121
- default: -> { MiddlewareRegistry.new(CMDx.configuration.middlewares) }
122
- __cmdx_attr_setting :cmd_callbacks,
123
- default: -> { CallbackRegistry.new(CMDx.configuration.callbacks) }
124
- __cmdx_attr_setting :cmd_parameters,
125
- default: -> { ParameterRegistry.new }
126
-
127
- __cmdx_attr_delegator :cmd_middlewares, :cmd_callbacks, :cmd_parameters, :task_setting, :task_setting?,
128
- to: :class
129
- __cmdx_attr_delegator :skip!, :fail!, :throw!,
130
- to: :result
131
-
132
- ##
133
- # @!attribute [r] context
134
- # @return [Context] parameter context for this task execution
13
+ cmdx_attr_setting :cmd_settings,
14
+ default: -> { CMDx.configuration.to_h.slice(:logger, :task_halt, :workflow_halt).merge(tags: []) }
15
+ cmdx_attr_setting :cmd_middlewares,
16
+ default: -> { MiddlewareRegistry.new(CMDx.configuration.middlewares) }
17
+ cmdx_attr_setting :cmd_callbacks,
18
+ default: -> { CallbackRegistry.new(CMDx.configuration.callbacks) }
19
+ cmdx_attr_setting :cmd_parameters,
20
+ default: -> { ParameterRegistry.new }
21
+
22
+ cmdx_attr_delegator :cmd_middlewares, :cmd_callbacks, :cmd_parameters,
23
+ :cmd_settings, :cmd_setting, :cmd_setting?,
24
+ to: :class
25
+ cmdx_attr_delegator :skip!, :fail!, :throw!,
26
+ to: :result
27
+
28
+ # @return [Context] parameter context for this task execution
135
29
  attr_reader :context
136
30
 
137
- ##
138
- # @!attribute [r] errors
139
- # @return [Errors] collection of validation and execution errors
31
+ # @return [Errors] collection of validation and execution errors
140
32
  attr_reader :errors
141
33
 
142
- ##
143
- # @!attribute [r] id
144
- # @return [String] unique identifier for this task instance
34
+ # @return [String] unique identifier for this task instance
145
35
  attr_reader :id
146
36
 
147
- ##
148
- # @!attribute [r] result
149
- # @return [Result] execution result tracking state and status
37
+ # @return [Result] execution result tracking state and status
150
38
  attr_reader :result
151
39
 
152
- ##
153
- # @!attribute [r] chain
154
- # @return [Chain] execution chain containing this task and related executions
40
+ # @return [Chain] execution chain containing this task and related executions
155
41
  attr_reader :chain
156
42
 
157
43
  # @return [Context] alias for context
@@ -160,21 +46,28 @@ module CMDx
160
46
  # @return [Result] alias for result
161
47
  alias res result
162
48
 
163
- ##
164
- # Initializes a new task instance with the given context parameters.
49
+ # Creates a new task instance with the given execution context.
50
+ #
51
+ # Initializes all internal state including context, errors, unique identifier,
52
+ # result tracking, and execution chain. The context parameter supports various
53
+ # input formats and will be normalized into a Context instance.
54
+ #
55
+ # @param context [Hash, Context, Object] initial execution context and parameters
165
56
  #
166
- # The context can be provided as a Hash, Context object, or Result object.
167
- # When a Result object is passed, its context is automatically extracted,
168
- # enabling seamless task chaining and data flow between tasks.
57
+ # @return [Task] the newly created task instance
169
58
  #
170
- # @param context [Hash, Context, Result] parameters and configuration for task execution
171
- # @example With hash parameters
172
- # task = ProcessOrderTask.new(order_id: 123, notify_user: true)
59
+ # @example Create task with hash context
60
+ # task = MyTask.new(user_id: 123, action: "process")
61
+ # task.context.user_id #=> 123
173
62
  #
174
- # @example With Result object (task chaining)
175
- # extraction_result = ExtractDataTask.call(source_id: 456)
176
- # processing_task = ProcessDataTask.new(extraction_result)
177
- # # Context from extraction_result is automatically extracted
63
+ # @example Create task with existing context
64
+ # existing_context = OtherTask.call(status: "active")
65
+ # task = MyTask.new(existing_context)
66
+ # task.context.status #=> "active"
67
+ #
68
+ # @example Create task with empty context
69
+ # task = MyTask.new
70
+ # task.context #=> empty Context instance
178
71
  def initialize(context = {})
179
72
  context = context.context if context.respond_to?(:context)
180
73
 
@@ -183,347 +76,363 @@ module CMDx
183
76
  @id = CMDx::Correlator.generate
184
77
  @result = Result.new(self)
185
78
  @chain = Chain.build(@result)
79
+
80
+ TaskDeprecator.call(self)
186
81
  end
187
82
 
188
83
  class << self
189
84
 
190
- ##
191
- # Dynamically defines callback methods for each available callback type.
192
- # Each callback method accepts callables and options for conditional execution.
193
- #
194
- # @example Callback with method name
195
- # before_validation :validate_permissions
196
- #
197
- # @example Callback with proc
198
- # on_success -> { logger.info "Task completed successfully" }
199
- #
200
- # @example Callback with conditions
201
- # on_failure :alert_support, if: :critical_error?
202
- # after_execution :cleanup, unless: :skip_cleanup?
203
- #
204
- # @param callables [Array<Symbol, Proc, #call>] methods or callables to execute
205
- # @param options [Hash] conditions for callback execution
206
- # @option options [Symbol, Proc, #call] :if condition that must be truthy
207
- # @option options [Symbol, Proc, #call] :unless condition that must be falsy
208
- # @param block [Proc] block to execute as part of the callback
209
- # @return [Array] updated callbacks array
210
- CALLBACKS.each do |callback|
85
+ CallbackRegistry::TYPES.each do |callback|
86
+ # Registers a callback for the specified lifecycle event.
87
+ #
88
+ # This method is dynamically defined for each callback type supported by
89
+ # CallbackRegistry, allowing tasks to register callbacks for various
90
+ # execution lifecycle events.
91
+ #
92
+ # @param callables [Array<Object>] callback objects or procs to register
93
+ # @param options [Hash] options for callback registration
94
+ # @param block [Proc] optional block to use as callback
95
+ #
96
+ # @return [void]
97
+ #
98
+ # @example Register before_execution callback with symbol
99
+ # class MyTask < CMDx::Task
100
+ # before_execution :setup_database
101
+ # end
102
+ #
103
+ # @example Register before_execution callback with proc
104
+ # class MyTask < CMDx::Task
105
+ # before_execution -> { puts "Starting task execution" }
106
+ # end
107
+ #
108
+ # @example Register before_execution callback with class
109
+ # class MyTask < CMDx::Task
110
+ # before_execution SetupCallback
111
+ # end
112
+ #
113
+ # @example Register before_execution callback with block
114
+ # class MyTask < CMDx::Task
115
+ # before_execution { |task| task.context.started_at = Time.now }
116
+ # end
117
+ #
118
+ # @example Register on_success callback with conditional options
119
+ # class MyTask < CMDx::Task
120
+ # on_success :send_notification, if: -> { Rails.env.production? }
121
+ # end
122
+ #
123
+ # @example Register on_success callback with multiple callables
124
+ # class MyTask < CMDx::Task
125
+ # on_success :log_success, :send_email, :update_metrics
126
+ # end
211
127
  define_method(callback) do |*callables, **options, &block|
212
128
  cmd_callbacks.register(callback, *callables, **options, &block)
213
129
  end
214
130
  end
215
131
 
216
- ##
217
- # Retrieves a task setting value, evaluating it if it's a callable.
218
- #
219
- # @param key [Symbol, String] setting key to retrieve
220
- # @return [Object] the setting value
221
- # @example
222
- # task_setting(:timeout) #=> 30
223
- def task_setting(key)
224
- __cmdx_yield(task_settings[key])
225
- end
226
-
227
- ##
228
- # Checks if a task setting exists.
132
+ # Retrieves a configuration setting value by key.
133
+ #
134
+ # Provides access to task-specific configuration settings that control
135
+ # various aspects of task execution including logging, halt conditions,
136
+ # and custom settings.
137
+ #
138
+ # @param key [Symbol, String] the configuration setting key to retrieve
139
+ #
140
+ # @return [Object] the configuration value, or nil if key doesn't exist
229
141
  #
230
- # @param key [Symbol, String] setting key to check
231
- # @return [Boolean] true if setting exists
232
- def task_setting?(key)
233
- task_settings.key?(key)
142
+ # @example Get logger setting
143
+ # MyTask.cmd_setting(:logger) #=> Logger instance
144
+ #
145
+ # @example Get custom setting
146
+ # MyTask.cmd_settings!(timeout: 30)
147
+ # MyTask.cmd_setting(:timeout) #=> 30
148
+ def cmd_setting(key)
149
+ cmdx_yield(cmd_settings[key])
234
150
  end
235
151
 
236
- ##
237
- # Updates task settings with new options.
238
- #
239
- # @param options [Hash] settings to merge
240
- # @return [Hash] updated settings
241
- # @example
242
- # task_settings!(timeout: 60, retries: 3)
243
- def task_settings!(**options)
244
- task_settings.merge!(options)
152
+ # Checks if a configuration setting exists.
153
+ #
154
+ # @param key [Symbol, String] the configuration setting key to check
155
+ #
156
+ # @return [Boolean] true if the setting key exists, false otherwise
157
+ #
158
+ # @example Check for existing setting
159
+ # MyTask.cmd_setting?(:logger) #=> true
160
+ #
161
+ # @example Check for non-existing setting
162
+ # MyTask.cmd_setting?(:nonexistent) #=> false
163
+ def cmd_setting?(key)
164
+ cmd_settings.key?(key)
245
165
  end
246
166
 
247
- ##
248
- # Adds middleware to the task execution stack.
249
- #
250
- # Middleware can wrap task execution to provide cross-cutting concerns
251
- # like logging, authentication, caching, or error handling.
252
- #
253
- # @param middleware [Class, Object, Proc] middleware to add
254
- # @param args [Array] arguments for middleware instantiation
255
- # @param block [Proc] block for middleware instantiation
256
- # @return [MiddlewareRegistry] updated middleware registry
257
- # @example
258
- # use LoggingMiddleware
259
- # use AuthenticationMiddleware, "admin"
260
- # use CachingMiddleware.new(ttl: 300)
261
- def use(middleware, ...)
262
- cmd_middlewares.use(middleware, ...)
167
+ # Updates task configuration settings with the provided options.
168
+ #
169
+ # Merges the given options into the existing configuration settings,
170
+ # allowing tasks to customize their execution behavior.
171
+ #
172
+ # @param options [Hash] configuration options to merge
173
+ #
174
+ # @return [Hash] the updated settings hash
175
+ #
176
+ # @example Set custom timeout
177
+ # MyTask.cmd_settings!(timeout: 60, retries: 3)
178
+ #
179
+ # @example Override halt condition
180
+ # MyTask.cmd_settings!(task_halt: ["failed", "error"])
181
+ def cmd_settings!(**options)
182
+ cmd_settings.merge!(options)
263
183
  end
264
184
 
265
- ##
266
- # Registers callbacks for the task execution lifecycle.
267
- #
268
- # Callbacks can observe or modify task execution at specific lifecycle
269
- # points like before validation, on success, after execution, etc.
270
- #
271
- # @param callback [Symbol] The callback type to register for
272
- # @param callables [Array<Symbol, Proc, Callback, #call>] Methods, callables, or Callback instances to execute
273
- # @param options [Hash] Conditions for callback execution
274
- # @option options [Symbol, Proc, #call] :if condition that must be truthy
275
- # @option options [Symbol, Proc, #call] :unless condition that must be falsy
276
- # @param block [Proc] Block to execute as part of the callback
277
- # @return [CallbackRegistry] updated callback registry
278
- # @example
279
- # register :before_execution, LoggingCallback.new(:debug)
280
- # register :on_success, NotificationCallback.new([:email, :slack])
281
- # register :on_failure, :alert_admin, if: :critical?
282
- def register(callback, ...)
283
- cmd_callbacks.register(callback, ...)
185
+ # Registers middleware, callbacks, validators, or coercions with the task.
186
+ #
187
+ # Provides a unified interface for registering various types of task
188
+ # extensions that modify or enhance task execution behavior.
189
+ #
190
+ # @param type [Symbol] the type of extension to register (:middleware, :callback, :validator, :coercion)
191
+ # @param object [Object] the extension object to register
192
+ # @param args [Array] additional arguments for registration
193
+ #
194
+ # @return [void]
195
+ #
196
+ # @raise [ArgumentError] if an unsupported type is provided
197
+ #
198
+ # @example Register coercion
199
+ # class MyTask < CMDx::Task
200
+ # use :coercion, TemperatureCoercion
201
+ # end
202
+ #
203
+ # @example Register validator
204
+ # class MyTask < CMDx::Task
205
+ # use :validator, ZipcodeValidator, country: "US"
206
+ # end
207
+ #
208
+ # @example Register middleware
209
+ # class MyTask < CMDx::Task
210
+ # use :middleware, CMDx::Middlewares::Timeout.new(seconds: 30)
211
+ # end
212
+ #
213
+ # @example Register callback
214
+ # class MyTask < CMDx::Task
215
+ # use :callback, :before, LogCallback.new
216
+ # end
217
+ def use(type, object, ...)
218
+ case type
219
+ when :middleware
220
+ cmd_middlewares.register(object, ...)
221
+ when :callback
222
+ cmd_callbacks.register(type, object, ...)
223
+ when :validator
224
+ cmd_validators.register(type, object, ...)
225
+ when :coercion
226
+ cmd_coercions.register(type, object, ...)
227
+ end
284
228
  end
285
229
 
286
- ##
287
- # Defines optional parameters for the task.
288
- #
289
- # @param attributes [Array<Symbol>] parameter names
290
- # @param options [Hash] parameter options (type, default, validations, etc.)
291
- # @param block [Proc] block for nested parameter definitions
292
- # @return [ParameterRegistry] updated parameters collection
293
- # @example
294
- # optional :timeout, type: :integer, default: 30
295
- # optional :options, type: :hash do
296
- # required :api_key, type: :string
230
+ # Defines optional parameters for the task with validation and coercion.
231
+ #
232
+ # Creates parameter definitions that are not required for task execution
233
+ # but will be validated and coerced if provided. Supports nested parameter
234
+ # structures through block syntax.
235
+ #
236
+ # @param attributes [Array<Symbol>] parameter names to define as optional
237
+ # @param options [Hash] parameter configuration options
238
+ # @option options [Symbol, Array<Symbol>] :type parameter type(s) for coercion
239
+ # @option options [Object] :default default value if parameter not provided
240
+ # @option options [Hash] :validates validation rules to apply
241
+ # @param block [Proc] optional block for defining nested parameters
242
+ #
243
+ # @return [Array<Parameter>] the created parameter definitions
244
+ #
245
+ # @example Define simple optional parameters
246
+ # class MyTask < CMDx::Task
247
+ # optional :name, :email, type: :string
248
+ # optional :age, type: :integer, default: 0
249
+ # end
250
+ #
251
+ # @example Define optional parameter with validation
252
+ # class MyTask < CMDx::Task
253
+ # optional :score, type: :integer, validates: { numeric: { greater_than: 0 } }
254
+ # end
255
+ #
256
+ # @example Define nested optional parameters
257
+ # class MyTask < CMDx::Task
258
+ # optional :user, type: :hash do
259
+ # required :name, type: :string
260
+ # optional :age, type: :integer
261
+ # end
297
262
  # end
298
263
  def optional(*attributes, **options, &)
299
264
  parameters = Parameter.optional(*attributes, **options.merge(klass: self), &)
300
- cmd_parameters.concat(parameters)
265
+ cmd_parameters.registry.concat(parameters)
301
266
  end
302
267
 
303
- ##
304
- # Defines required parameters for the task.
305
- #
306
- # @param attributes [Array<Symbol>] parameter names
307
- # @param options [Hash] parameter options (type, validations, etc.)
308
- # @param block [Proc] block for nested parameter definitions
309
- # @return [ParameterRegistry] updated parameters collection
310
- # @example
311
- # required :user_id, type: :integer
312
- # required :profile, type: :hash do
313
- # required :name, type: :string
314
- # optional :age, type: :integer
268
+ # Defines required parameters for the task with validation and coercion.
269
+ #
270
+ # Creates parameter definitions that must be provided for successful task
271
+ # execution. Missing required parameters will cause task validation to fail.
272
+ # Supports nested parameter structures through block syntax.
273
+ #
274
+ # @param attributes [Array<Symbol>] parameter names to define as required
275
+ # @param options [Hash] parameter configuration options
276
+ # @option options [Symbol, Array<Symbol>] :type parameter type(s) for coercion
277
+ # @option options [Object] :default default value if parameter not provided
278
+ # @option options [Hash] :validates validation rules to apply
279
+ # @param block [Proc] optional block for defining nested parameters
280
+ #
281
+ # @return [Array<Parameter>] the created parameter definitions
282
+ #
283
+ # @example Define simple required parameters
284
+ # class MyTask < CMDx::Task
285
+ # required :user_id, type: :integer
286
+ # required :action, type: :string
287
+ # end
288
+ #
289
+ # @example Define required parameter with validation
290
+ # class MyTask < CMDx::Task
291
+ # required :email, type: :string, validates: { format: /@/ }
292
+ # end
293
+ #
294
+ # @example Define nested required parameters
295
+ # class MyTask < CMDx::Task
296
+ # required :payment, type: :hash do
297
+ # required :amount, type: :big_decimal
298
+ # required :currency, type: :string
299
+ # optional :description, type: :string
300
+ # end
315
301
  # end
316
302
  def required(*attributes, **options, &)
317
303
  parameters = Parameter.required(*attributes, **options.merge(klass: self), &)
318
- cmd_parameters.concat(parameters)
304
+ cmd_parameters.registry.concat(parameters)
319
305
  end
320
306
 
321
- ##
322
- # Executes the task with the given parameters, returning a result object.
323
- # This method handles all exceptions and ensures the task completes properly.
324
- #
325
- # Parameters can be provided as a Hash, Context object, or Result object.
326
- # When a Result object is passed, its context is automatically extracted,
327
- # enabling seamless task chaining.
328
- #
329
- # @param args [Array] arguments passed to task initialization
330
- # @return [Result] execution result with state and status information
331
- # @example With hash parameters
332
- # result = ProcessOrderTask.call(order_id: 123)
333
- # result.success? #=> true or false
334
- # result.context.order #=> processed order
335
- #
336
- # @example With Result object (task chaining)
337
- # extraction_result = ExtractDataTask.call(source_id: 456)
338
- # processing_result = ProcessDataTask.call(extraction_result)
339
- # # Context from extraction_result is automatically used
340
- # processing_result.context.source_id #=> 456
307
+ # Executes a task instance and returns the result without raising exceptions.
308
+ #
309
+ # Creates a new task instance with the provided context, processes it through
310
+ # the complete execution pipeline, and returns the result. This method will
311
+ # not raise exceptions for task failures but will capture them in the result.
312
+ #
313
+ # @param args [Array] arguments passed to task constructor
314
+ #
315
+ # @return [Result] the execution result containing state, status, and metadata
316
+ #
317
+ # @example Execute task
318
+ # result = MyTask.call(user_id: 123, action: "process")
319
+ # puts result.status #=> "success" or "failed" or "skipped"
341
320
  def call(...)
342
321
  instance = new(...)
343
- instance.perform
322
+ instance.process
344
323
  instance.result
345
324
  end
346
325
 
347
- ##
348
- # Executes the task with the given parameters, raising exceptions for failures.
349
- # This method is useful in background jobs where retries are handled via exceptions.
350
- #
351
- # Parameters can be provided as a Hash, Context object, or Result object.
352
- # When a Result object is passed, its context is automatically extracted,
353
- # enabling seamless task chaining with exception propagation.
354
- #
355
- # @param args [Array] arguments passed to task initialization
356
- # @return [Result] execution result if successful
357
- # @raise [Fault] if task fails and task_halt includes the failure status
358
- # @example With hash parameters
359
- # begin
360
- # result = ProcessOrderTask.call!(order_id: 123)
361
- # rescue CMDx::Failed => e
362
- # # Handle failure
363
- # end
326
+ # Executes a task instance and returns the result, raising exceptions on failure.
327
+ #
328
+ # Creates a new task instance with the provided context, processes it through
329
+ # the complete execution pipeline, and returns the result. This method will
330
+ # raise appropriate fault exceptions if the task fails or is skipped.
364
331
  #
365
- # @example With Result object (task chaining)
332
+ # @param args [Array] arguments passed to task constructor
333
+ #
334
+ # @return [Result] the execution result containing state, status, and metadata
335
+ #
336
+ # @raise [Failed] when task execution fails
337
+ # @raise [Skipped] when task execution is skipped
338
+ #
339
+ # @example Execute task
366
340
  # begin
367
- # extraction_result = ExtractDataTask.call!(source_id: 456)
368
- # processing_result = ProcessDataTask.call!(extraction_result)
341
+ # result = MyTask.call!(user_id: 123)
342
+ # puts "Success: #{result.status}"
369
343
  # rescue CMDx::Failed => e
370
- # # Handle failure from either task
344
+ # puts "Task failed: #{e.message}"
371
345
  # end
372
346
  def call!(...)
373
347
  instance = new(...)
374
- instance.perform!
348
+ instance.process!
375
349
  instance.result
376
350
  end
377
351
 
378
352
  end
379
353
 
380
- ##
381
- # The main execution method that must be implemented by subclasses.
382
- # This method contains the core business logic of the task.
354
+ # Abstract method that must be implemented by task subclasses.
355
+ #
356
+ # This method contains the actual business logic for the task. Subclasses
357
+ # must override this method to provide their specific implementation.
358
+ # The method has access to the task's context, can modify it, and can
359
+ # use skip!, fail!, or throw! to control execution flow.
383
360
  #
384
- # @abstract Subclasses must implement this method
385
361
  # @return [void]
386
- # @raise [UndefinedCallError] if not implemented in subclass
387
- # @example
388
- # def call
389
- # context.user = User.find(user_id)
390
- # fail!(reason: "User not found") unless context.user
391
362
  #
392
- # context.user.activate!
393
- # context.activation_date = Time.now
363
+ # @raise [UndefinedCallError] always raised in the base Task class
364
+ #
365
+ # @example Implement in a subclass
366
+ # class ProcessUserTask < CMDx::Task
367
+ # required :user_id, type: :integer
368
+ #
369
+ # def call
370
+ # user = User.find(context.user_id)
371
+ # skip!(reason: "User already processed") if user.processed?
372
+ #
373
+ # user.process!
374
+ # context.processed_at = Time.now
375
+ # end
394
376
  # end
395
377
  def call
396
378
  raise UndefinedCallError, "call method not defined in #{self.class.name}"
397
379
  end
398
380
 
399
- ##
400
- # Executes the task with full exception handling for the non-bang call method.
401
- # Captures all exceptions and converts them to appropriate result states.
381
+ # Executes the task through the middleware pipeline without raising exceptions.
402
382
  #
403
- # @return [void]
404
- def perform
405
- return execute_call if cmd_middlewares.empty?
406
-
407
- cmd_middlewares.call(self) { |task| task.send(:execute_call) }
408
- end
409
-
410
- ##
411
- # Executes the task with exception propagation for the bang call method.
412
- # Allows exceptions to bubble up for external handling.
383
+ # Processes the task by running it through all registered middleware and
384
+ # the TaskProcessor. This method captures exceptions and converts them
385
+ # into result states rather than propagating them.
413
386
  #
414
387
  # @return [void]
415
- # @raise [Fault] if task fails and task_halt includes the failure status
416
- def perform!
417
- return execute_call! if cmd_middlewares.empty?
418
-
419
- cmd_middlewares.call(self) { |task| task.send(:execute_call!) }
420
- end
421
-
422
- private
423
-
424
- ##
425
- # Returns the logger instance for this task.
426
388
  #
427
- # @return [Logger] configured logger instance
428
- # @api private
429
- def logger
430
- Logger.call(self)
389
+ # @example Process a task instance
390
+ # task = MyTask.new(data: "input")
391
+ # task.process
392
+ # puts task.result.status #=> "success", "failed", or "skipped"
393
+ def process
394
+ cmd_middlewares.call(self) { |task| TaskProcessor.call(task) }
431
395
  end
432
396
 
433
- ##
434
- # Executes before-call callbacks and validations.
435
- # Sets up the execution context and validates parameters.
397
+ # Executes the task through the middleware pipeline, raising exceptions on failure.
436
398
  #
437
- # @return [void]
438
- # @api private
439
- def before_call
440
- cmd_callbacks.call(self, :before_execution)
441
-
442
- result.executing!
443
- cmd_callbacks.call(self, :on_executing)
444
-
445
- cmd_callbacks.call(self, :before_validation)
446
- ParameterValidator.call(self)
447
- cmd_callbacks.call(self, :after_validation)
448
- end
449
-
450
- ##
451
- # Executes after-call callbacks based on execution results.
452
- # Handles state and status transitions with appropriate callbacks.
399
+ # Processes the task by running it through all registered middleware and
400
+ # the TaskProcessor. This method will raise appropriate fault exceptions
401
+ # if the task fails or is skipped.
453
402
  #
454
403
  # @return [void]
455
- # @api private
456
- def after_call
457
- cmd_callbacks.call(self, :"on_#{result.state}")
458
- cmd_callbacks.call(self, :on_executed) if result.executed?
459
-
460
- cmd_callbacks.call(self, :"on_#{result.status}")
461
- cmd_callbacks.call(self, :on_good) if result.good?
462
- cmd_callbacks.call(self, :on_bad) if result.bad?
463
-
464
- cmd_callbacks.call(self, :after_execution)
465
- end
466
-
467
- ##
468
- # Finalizes task execution by freezing the task and logging results.
469
404
  #
470
- # @return [void]
471
- # @api private
472
- def terminate_call
473
- Immutator.call(self)
474
- ResultLogger.call(result)
475
- end
476
-
477
- ##
478
- # Executes the task directly without middleware for the non-bang call method.
405
+ # @raise [Failed] when task execution fails
406
+ # @raise [Skipped] when task execution is skipped
479
407
  #
480
- # @return [void]
481
- # @api private
482
- def execute_call
483
- result.runtime do
484
- before_call
485
- call
486
- rescue UndefinedCallError => e
487
- raise(e)
488
- rescue Fault => e
489
- throw!(e.result, original_exception: e) if Array(task_setting(:task_halt)).include?(e.result.status)
490
- rescue StandardError => e
491
- fail!(reason: "[#{e.class}] #{e.message}", original_exception: e)
492
- ensure
493
- result.executed!
494
- after_call
495
- end
496
-
497
- terminate_call
408
+ # @example Process a task instance with exception handling
409
+ # task = RiskyTask.new(data: "input")
410
+ # begin
411
+ # task.process!
412
+ # puts "Task completed successfully"
413
+ # rescue CMDx::Failed => e
414
+ # puts "Task failed: #{e.message}"
415
+ # end
416
+ def process!
417
+ cmd_middlewares.call(self) { |task| TaskProcessor.call!(task) }
498
418
  end
499
419
 
500
- ##
501
- # Executes the task directly without middleware for the bang call method.
420
+ # Creates a logger instance configured for this task.
502
421
  #
503
- # @return [void]
504
- # @api private
505
- def execute_call!
506
- result.runtime do
507
- before_call
508
- call
509
- rescue UndefinedCallError => e
510
- Chain.clear
511
- raise(e)
512
- rescue Fault => e
513
- result.executed!
514
-
515
- if Array(task_setting(:task_halt)).include?(e.result.status)
516
- Chain.clear
517
- raise(e)
518
- end
519
-
520
- after_call # HACK: treat as NO-OP
521
- else
522
- result.executed!
523
- after_call # ELSE: treat as success
524
- end
525
-
526
- terminate_call
422
+ # Returns a logger instance that is pre-configured with the task's
423
+ # settings and context information for consistent logging throughout
424
+ # task execution.
425
+ #
426
+ # @return [Logger] configured logger instance for this task
427
+ #
428
+ # @example Log task execution
429
+ # def call
430
+ # logger.info "Starting user processing"
431
+ # # ... task logic ...
432
+ # logger.info "User processing completed"
433
+ # end
434
+ def logger
435
+ Logger.call(self)
527
436
  end
528
437
 
529
438
  end