cmdx 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +101 -162
- data/lib/cmdx/context.rb +34 -166
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +58 -330
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +36 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/lib/cmdx/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
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
]
|
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
|
-
#
|
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
|
-
#
|
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
|
-
# @
|
171
|
-
#
|
172
|
-
# task
|
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
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
#
|
218
|
-
#
|
219
|
-
#
|
220
|
-
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
|
224
|
-
|
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
|
-
# @
|
231
|
-
#
|
232
|
-
|
233
|
-
|
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
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# @return [
|
241
|
-
#
|
242
|
-
#
|
243
|
-
|
244
|
-
|
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
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
# @
|
255
|
-
#
|
256
|
-
# @
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
#
|
261
|
-
def
|
262
|
-
|
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
|
-
#
|
267
|
-
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
# @param
|
272
|
-
# @param
|
273
|
-
#
|
274
|
-
# @
|
275
|
-
#
|
276
|
-
# @
|
277
|
-
#
|
278
|
-
# @example
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
|
283
|
-
|
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
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
# @
|
293
|
-
# @
|
294
|
-
#
|
295
|
-
#
|
296
|
-
#
|
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
|
-
#
|
305
|
-
#
|
306
|
-
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
# @
|
310
|
-
# @
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
314
|
-
#
|
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
|
-
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
# @
|
330
|
-
#
|
331
|
-
# @example
|
332
|
-
# result =
|
333
|
-
# result.
|
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.
|
322
|
+
instance.process
|
344
323
|
instance.result
|
345
324
|
end
|
346
325
|
|
347
|
-
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
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
|
-
# @
|
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
|
-
#
|
368
|
-
#
|
341
|
+
# result = MyTask.call!(user_id: 123)
|
342
|
+
# puts "Success: #{result.status}"
|
369
343
|
# rescue CMDx::Failed => e
|
370
|
-
#
|
344
|
+
# puts "Task failed: #{e.message}"
|
371
345
|
# end
|
372
346
|
def call!(...)
|
373
347
|
instance = new(...)
|
374
|
-
instance.
|
348
|
+
instance.process!
|
375
349
|
instance.result
|
376
350
|
end
|
377
351
|
|
378
352
|
end
|
379
353
|
|
380
|
-
|
381
|
-
#
|
382
|
-
# This method contains the
|
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
|
-
#
|
393
|
-
#
|
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
|
-
#
|
404
|
-
|
405
|
-
|
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
|
-
# @
|
428
|
-
#
|
429
|
-
|
430
|
-
|
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
|
-
#
|
438
|
-
#
|
439
|
-
|
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
|
-
# @
|
471
|
-
# @
|
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
|
-
# @
|
481
|
-
#
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
-
#
|
504
|
-
#
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
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
|