cmdx 1.1.0 → 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 +13 -12
- data/.cursor/prompts/yardoc.md +11 -6
- data/CHANGELOG.md +13 -2
- data/README.md +1 -0
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +124 -58
- 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 +390 -94
- data/docs/configuration.md +181 -65
- 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 +150 -125
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +181 -118
- data/docs/middlewares.md +150 -377
- 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 +232 -281
- 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 +260 -133
- data/docs/testing.md +191 -197
- data/docs/workflows.md +143 -98
- data/lib/cmdx/callback.rb +23 -19
- data/lib/cmdx/callback_registry.rb +1 -3
- data/lib/cmdx/chain_inspector.rb +23 -23
- data/lib/cmdx/chain_serializer.rb +38 -19
- data/lib/cmdx/coercion.rb +20 -12
- data/lib/cmdx/coercion_registry.rb +51 -32
- data/lib/cmdx/configuration.rb +84 -31
- data/lib/cmdx/context.rb +32 -21
- data/lib/cmdx/core_ext/hash.rb +13 -13
- data/lib/cmdx/core_ext/module.rb +1 -1
- data/lib/cmdx/core_ext/object.rb +12 -12
- data/lib/cmdx/correlator.rb +60 -39
- data/lib/cmdx/errors.rb +105 -131
- data/lib/cmdx/fault.rb +66 -45
- data/lib/cmdx/immutator.rb +20 -21
- data/lib/cmdx/lazy_struct.rb +78 -70
- data/lib/cmdx/log_formatters/json.rb +1 -1
- data/lib/cmdx/log_formatters/key_value.rb +1 -1
- data/lib/cmdx/log_formatters/line.rb +1 -1
- data/lib/cmdx/log_formatters/logstash.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
- data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
- data/lib/cmdx/log_formatters/raw.rb +2 -2
- data/lib/cmdx/logger.rb +19 -14
- data/lib/cmdx/logger_ansi.rb +33 -17
- data/lib/cmdx/logger_serializer.rb +85 -24
- data/lib/cmdx/middleware.rb +39 -21
- data/lib/cmdx/middleware_registry.rb +4 -3
- data/lib/cmdx/parameter.rb +151 -89
- data/lib/cmdx/parameter_inspector.rb +34 -21
- data/lib/cmdx/parameter_registry.rb +36 -30
- data/lib/cmdx/parameter_serializer.rb +21 -14
- data/lib/cmdx/result.rb +136 -135
- data/lib/cmdx/result_ansi.rb +31 -17
- data/lib/cmdx/result_inspector.rb +32 -27
- data/lib/cmdx/result_logger.rb +23 -14
- data/lib/cmdx/result_serializer.rb +65 -27
- data/lib/cmdx/task.rb +234 -113
- data/lib/cmdx/task_deprecator.rb +22 -25
- data/lib/cmdx/task_processor.rb +89 -88
- data/lib/cmdx/task_serializer.rb +27 -14
- data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
- data/lib/cmdx/validator.rb +25 -16
- data/lib/cmdx/validator_registry.rb +53 -31
- data/lib/cmdx/validators/exclusion.rb +1 -1
- data/lib/cmdx/validators/format.rb +2 -2
- data/lib/cmdx/validators/inclusion.rb +2 -2
- data/lib/cmdx/validators/length.rb +2 -2
- data/lib/cmdx/validators/numeric.rb +3 -3
- data/lib/cmdx/validators/presence.rb +2 -2
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/workflow.rb +54 -33
- data/lib/generators/cmdx/task_generator.rb +6 -6
- data/lib/generators/cmdx/workflow_generator.rb +6 -6
- metadata +3 -1
data/lib/cmdx/task_processor.rb
CHANGED
@@ -1,82 +1,72 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Core task execution processor handling the complete task lifecycle.
|
5
5
|
#
|
6
|
-
# TaskProcessor
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# TaskProcessor manages the execution pipeline for individual tasks, coordinating
|
7
|
+
# parameter validation, callback invocation, error handling, and result state
|
8
|
+
# management. It provides both safe execution (capturing exceptions) and unsafe
|
9
|
+
# execution (re-raising exceptions) modes through call and call! methods respectively.
|
10
|
+
# The processor ensures proper state transitions, handles fault propagation, and
|
11
|
+
# maintains execution context throughout the task lifecycle.
|
10
12
|
class TaskProcessor
|
11
13
|
|
12
14
|
# @return [CMDx::Task] The task instance being executed
|
13
15
|
attr_reader :task
|
14
16
|
|
15
|
-
# Creates a new
|
17
|
+
# Creates a new task processor for the specified task instance.
|
16
18
|
#
|
17
|
-
# @param task [CMDx::Task] the task instance to
|
19
|
+
# @param task [CMDx::Task] the task instance to process
|
18
20
|
#
|
19
|
-
# @return [TaskProcessor] a new
|
21
|
+
# @return [TaskProcessor] a new processor instance for the task
|
20
22
|
#
|
21
|
-
# @example Create processor for a task
|
23
|
+
# @example Create a processor for a task
|
22
24
|
# task = MyTask.new(user_id: 123)
|
23
25
|
# processor = TaskProcessor.new(task)
|
24
|
-
# processor.task # => #<MyTask:...>
|
25
26
|
def initialize(task)
|
26
27
|
@task = task
|
27
28
|
end
|
28
29
|
|
29
30
|
class << self
|
30
31
|
|
31
|
-
# Executes
|
32
|
+
# Executes the specified task and returns the result without raising exceptions.
|
32
33
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# and StandardErrors and converting them to failed results.
|
34
|
+
# Creates a new processor instance and executes the task through the complete
|
35
|
+
# lifecycle including validation, callbacks, and error handling. Exceptions
|
36
|
+
# are captured in the result rather than being raised to the caller.
|
37
37
|
#
|
38
38
|
# @param task [CMDx::Task] the task instance to execute
|
39
39
|
#
|
40
|
-
# @return [Result] the
|
40
|
+
# @return [CMDx::Result] the execution result containing state and status information
|
41
41
|
#
|
42
|
-
# @
|
43
|
-
#
|
44
|
-
# @example Execute a task safely using class method
|
45
|
-
# task = MyTask.new(name: "test")
|
46
|
-
# result = TaskProcessor.call(task)
|
47
|
-
# result.success? # => true or false
|
48
|
-
#
|
49
|
-
# @example Handle task with validation errors
|
50
|
-
# task = MyTask.new # missing required parameters
|
42
|
+
# @example Execute a task safely
|
43
|
+
# task = ProcessDataTask.new(data: raw_data)
|
51
44
|
# result = TaskProcessor.call(task)
|
52
|
-
# result.failed
|
45
|
+
# puts result.status #=> "success", "failed", or "skipped"
|
53
46
|
def call(task)
|
54
47
|
new(task).call
|
55
48
|
end
|
56
49
|
|
57
|
-
# Executes
|
50
|
+
# Executes the specified task and raises exceptions on failure.
|
58
51
|
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# after proper cleanup and chain clearing.
|
52
|
+
# Creates a new processor instance and executes the task through the complete
|
53
|
+
# lifecycle. Unlike call, this method will re-raise exceptions including
|
54
|
+
# Fault exceptions when their status matches the task's halt configuration.
|
63
55
|
#
|
64
56
|
# @param task [CMDx::Task] the task instance to execute
|
65
57
|
#
|
66
|
-
# @return [Result] the
|
58
|
+
# @return [CMDx::Result] the execution result on success
|
67
59
|
#
|
68
|
-
# @raise [
|
69
|
-
# @raise [
|
60
|
+
# @raise [CMDx::Fault] when a fault occurs with status matching task halt configuration
|
61
|
+
# @raise [StandardError] when unexpected errors occur during execution
|
70
62
|
#
|
71
|
-
# @example Execute task with
|
72
|
-
# task =
|
73
|
-
# result = TaskProcessor.call!(task) # raises on failure
|
74
|
-
#
|
75
|
-
# @example Handle exceptions in bang mode
|
63
|
+
# @example Execute a task with exception raising
|
64
|
+
# task = CriticalTask.new(operation: "delete")
|
76
65
|
# begin
|
77
|
-
# TaskProcessor.call!(task)
|
66
|
+
# result = TaskProcessor.call!(task)
|
67
|
+
# puts "Success: #{result.status}"
|
78
68
|
# rescue CMDx::Fault => e
|
79
|
-
# puts "Task failed: #{e.
|
69
|
+
# puts "Task failed: #{e.message}"
|
80
70
|
# end
|
81
71
|
def call!(task)
|
82
72
|
new(task).call!
|
@@ -84,28 +74,24 @@ module CMDx
|
|
84
74
|
|
85
75
|
end
|
86
76
|
|
87
|
-
# Executes the task with
|
88
|
-
#
|
89
|
-
# This method provides safe task execution with comprehensive error handling,
|
90
|
-
# automatic result state management, and callback execution. Faults are caught
|
91
|
-
# and processed according to task halt settings, while StandardErrors are
|
92
|
-
# converted to failed results.
|
77
|
+
# Executes the task with safe error handling and returns the result.
|
93
78
|
#
|
94
|
-
#
|
79
|
+
# Runs the complete task execution pipeline including parameter validation,
|
80
|
+
# callback invocation, and the task's call method. Captures all exceptions
|
81
|
+
# as result status rather than raising them, ensuring the chain continues
|
82
|
+
# execution. Handles both standard errors and Fault exceptions according
|
83
|
+
# to the task's halt configuration.
|
95
84
|
#
|
96
|
-
# @
|
85
|
+
# @return [CMDx::Result] the execution result with captured state and status
|
97
86
|
#
|
98
|
-
# @example
|
99
|
-
# task = MyTask.new(name: "test")
|
87
|
+
# @example Safe task execution
|
100
88
|
# processor = TaskProcessor.new(task)
|
101
89
|
# result = processor.call
|
102
|
-
# result.success?
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
# result = processor.call
|
108
|
-
# result.failed? # => true
|
90
|
+
# if result.success?
|
91
|
+
# puts "Task completed successfully"
|
92
|
+
# else
|
93
|
+
# puts "Task failed: #{result.metadata[:reason]}"
|
94
|
+
# end
|
109
95
|
def call
|
110
96
|
task.result.runtime do
|
111
97
|
before_call
|
@@ -128,27 +114,27 @@ module CMDx
|
|
128
114
|
terminate_call
|
129
115
|
end
|
130
116
|
|
131
|
-
# Executes the task with
|
117
|
+
# Executes the task with exception raising on halt conditions.
|
132
118
|
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
119
|
+
# Runs the complete task execution pipeline including parameter validation,
|
120
|
+
# callback invocation, and the task's call method. Unlike call, this method
|
121
|
+
# will re-raise Fault exceptions when their status matches the task's halt
|
122
|
+
# configuration, and clears the execution chain before raising.
|
136
123
|
#
|
137
|
-
# @return [Result] the
|
124
|
+
# @return [CMDx::Result] the execution result on successful completion
|
138
125
|
#
|
139
|
-
# @raise [
|
140
|
-
# @raise [
|
126
|
+
# @raise [CMDx::Fault] when a fault occurs with status matching task halt configuration
|
127
|
+
# @raise [CMDx::UndefinedCallError] when the task's call method is not implemented
|
128
|
+
# @raise [StandardError] when unexpected errors occur during execution
|
141
129
|
#
|
142
|
-
# @example
|
143
|
-
#
|
144
|
-
# processor = TaskProcessor.new(task)
|
145
|
-
# result = processor.call! # raises on failure
|
146
|
-
#
|
147
|
-
# @example Handle exceptions in bang mode
|
130
|
+
# @example Task execution with exception raising
|
131
|
+
# processor = TaskProcessor.new(critical_task)
|
148
132
|
# begin
|
149
|
-
# processor.call!
|
133
|
+
# result = processor.call!
|
134
|
+
# puts "Task succeeded"
|
150
135
|
# rescue CMDx::Fault => e
|
151
|
-
# puts "
|
136
|
+
# puts "Critical failure: #{e.message}"
|
137
|
+
# # Chain is cleared, execution stops
|
152
138
|
# end
|
153
139
|
def call!
|
154
140
|
task.result.runtime do
|
@@ -173,7 +159,11 @@ module CMDx
|
|
173
159
|
|
174
160
|
private
|
175
161
|
|
176
|
-
# Executes pre-execution callbacks and
|
162
|
+
# Executes pre-execution callbacks and sets the task to executing state.
|
163
|
+
#
|
164
|
+
# Invokes before_execution callbacks, transitions the result to executing
|
165
|
+
# state, and triggers on_executing callbacks. This method prepares the
|
166
|
+
# task for execution and notifies registered callbacks about the state change.
|
177
167
|
#
|
178
168
|
# @return [void]
|
179
169
|
def before_call
|
@@ -183,16 +173,14 @@ module CMDx
|
|
183
173
|
task.cmd_callbacks.call(task, :on_executing)
|
184
174
|
end
|
185
175
|
|
186
|
-
# Validates task parameters and handles validation
|
176
|
+
# Validates task parameters and handles validation failures.
|
187
177
|
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
178
|
+
# Executes parameter validation callbacks, validates all task parameters
|
179
|
+
# against their defined rules, and sets the task result to failed if
|
180
|
+
# validation errors are found. Collects all validation messages into
|
181
|
+
# the result metadata.
|
192
182
|
#
|
193
183
|
# @return [void]
|
194
|
-
#
|
195
|
-
# @raise [Exception] Validations, coercions, or exceptions
|
196
184
|
def validate_parameters
|
197
185
|
task.cmd_callbacks.call(task, :before_validation)
|
198
186
|
|
@@ -207,19 +195,28 @@ module CMDx
|
|
207
195
|
task.cmd_callbacks.call(task, :after_validation)
|
208
196
|
end
|
209
197
|
|
210
|
-
# Clears the execution chain and
|
198
|
+
# Clears the execution chain and raises the specified exception.
|
211
199
|
#
|
212
|
-
#
|
200
|
+
# This method is used to clean up the execution context before
|
201
|
+
# re-raising exceptions, ensuring that the chain state is properly
|
202
|
+
# reset when execution cannot continue.
|
213
203
|
#
|
214
|
-
# @
|
204
|
+
# @param exception [Exception] the exception to raise after clearing the chain
|
215
205
|
#
|
216
|
-
# @
|
206
|
+
# @return [void]
|
207
|
+
#
|
208
|
+
# @raise [Exception] the provided exception after chain cleanup
|
217
209
|
def raise!(exception)
|
218
210
|
Chain.clear
|
219
211
|
raise(exception)
|
220
212
|
end
|
221
213
|
|
222
|
-
# Executes post-execution callbacks based on result state and status.
|
214
|
+
# Executes post-execution callbacks based on task result state and status.
|
215
|
+
#
|
216
|
+
# Invokes appropriate callbacks based on the task's final execution state
|
217
|
+
# (success, failure, etc.) and status. Handles both state-specific and
|
218
|
+
# status-specific callback invocation, as well as general execution
|
219
|
+
# completion callbacks.
|
223
220
|
#
|
224
221
|
# @return [void]
|
225
222
|
def after_call
|
@@ -233,9 +230,13 @@ module CMDx
|
|
233
230
|
task.cmd_callbacks.call(task, :after_execution)
|
234
231
|
end
|
235
232
|
|
236
|
-
# Finalizes task execution
|
233
|
+
# Finalizes task execution by freezing state and logging results.
|
237
234
|
#
|
238
|
-
#
|
235
|
+
# Applies immutability to the task instance and logs the execution
|
236
|
+
# result. This method ensures that the task state cannot be modified
|
237
|
+
# after execution and provides visibility into the execution outcome.
|
238
|
+
#
|
239
|
+
# @return [void]
|
239
240
|
def terminate_call
|
240
241
|
Immutator.call(task)
|
241
242
|
ResultLogger.call(task.result)
|
data/lib/cmdx/task_serializer.rb
CHANGED
@@ -1,33 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# Task serialization module for converting task objects to hash format.
|
5
|
+
#
|
6
|
+
# TaskSerializer provides functionality to serialize task objects into a
|
7
|
+
# standardized hash representation that includes essential metadata about
|
8
|
+
# the task such as its index, chain ID, type, class, ID, and tags. The
|
9
|
+
# serialized format is commonly used for debugging, logging, and introspection
|
10
|
+
# purposes throughout the task execution pipeline.
|
7
11
|
module TaskSerializer
|
8
12
|
|
9
13
|
module_function
|
10
14
|
|
11
|
-
#
|
12
|
-
# Extracts key task attributes including execution index, chain association,
|
13
|
-
# type classification, and associated tags for complete task identification.
|
15
|
+
# Serializes a task object into a hash representation.
|
14
16
|
#
|
15
|
-
#
|
17
|
+
# Converts a task instance into a standardized hash format containing
|
18
|
+
# key metadata about the task's execution context and classification.
|
19
|
+
# The serialization includes information from the task's result, chain,
|
20
|
+
# and command settings to provide comprehensive task identification.
|
16
21
|
#
|
17
|
-
# @
|
22
|
+
# @param task [CMDx::Task, CMDx::Workflow] the task or workflow object to serialize
|
18
23
|
#
|
19
|
-
# @
|
24
|
+
# @return [Hash] a hash containing the task's metadata
|
25
|
+
# @option return [Integer] :index the task's position index in the execution chain
|
26
|
+
# @option return [String] :chain_id the unique identifier of the task's execution chain
|
27
|
+
# @option return [String] :type the task type, either "Task" or "Workflow"
|
28
|
+
# @option return [String] :class the full class name of the task
|
29
|
+
# @option return [String] :id the unique identifier of the task instance
|
30
|
+
# @option return [Array] :tags the tags associated with the task from cmd settings
|
20
31
|
#
|
21
|
-
# @
|
22
|
-
#
|
32
|
+
# @raise [NoMethodError] if the task doesn't respond to required methods
|
33
|
+
#
|
34
|
+
# @example Serialize a basic task
|
35
|
+
# task = ProcessDataTask.new
|
23
36
|
# TaskSerializer.call(task)
|
24
|
-
#
|
37
|
+
# #=> {
|
25
38
|
# # index: 0,
|
26
39
|
# # chain_id: "abc123",
|
27
40
|
# # type: "Task",
|
28
|
-
# # class: "
|
41
|
+
# # class: "ProcessDataTask",
|
29
42
|
# # id: "def456",
|
30
|
-
# # tags: [
|
43
|
+
# # tags: []
|
31
44
|
# # }
|
32
45
|
def call(task)
|
33
46
|
{
|
@@ -19,12 +19,10 @@ module CMDx
|
|
19
19
|
# @return [Integer] the execution time in milliseconds
|
20
20
|
#
|
21
21
|
# @example Basic usage
|
22
|
-
#
|
23
|
-
# # => 100 (approximately)
|
22
|
+
# MonotonicRuntime.call { sleep(0.1) } #=> 100 (approximately)
|
24
23
|
#
|
25
24
|
# @example Measuring database query time
|
26
|
-
#
|
27
|
-
# # => 15 (milliseconds)
|
25
|
+
# MonotonicRuntime.call { User.find(1) } #=> 15 (milliseconds)
|
28
26
|
def call(&)
|
29
27
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
30
28
|
yield
|
data/lib/cmdx/validator.rb
CHANGED
@@ -1,46 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Base class for implementing validation functionality in
|
4
|
+
# Base class for implementing parameter validation functionality in task processing.
|
5
5
|
#
|
6
|
-
# Validators are used to validate parameter values
|
7
|
-
#
|
8
|
-
# must inherit from this class and implement the abstract call method.
|
6
|
+
# Validators are used to validate parameter values against specific rules and constraints,
|
7
|
+
# supporting both built-in validation types and custom validation logic. All validator
|
8
|
+
# implementations must inherit from this class and implement the abstract call method.
|
9
9
|
class Validator
|
10
10
|
|
11
11
|
# Executes a validator by creating a new instance and calling it.
|
12
12
|
#
|
13
13
|
# @param value [Object] the value to be validated
|
14
|
-
# @param options [Hash]
|
14
|
+
# @param options [Hash] additional options for the validation
|
15
15
|
#
|
16
|
-
# @return [Object] the
|
16
|
+
# @return [Object] the validated value if validation passes
|
17
17
|
#
|
18
18
|
# @raise [UndefinedCallError] when the validator subclass doesn't implement call
|
19
|
+
# @raise [ValidationError] when validation fails
|
19
20
|
#
|
20
21
|
# @example Execute a validator on a value
|
21
|
-
#
|
22
|
+
# PresenceValidator.call("some_value") #=> "some_value"
|
23
|
+
#
|
24
|
+
# @example Execute with options
|
25
|
+
# NumericValidator.call(42, greater_than: 10) #=> 42
|
22
26
|
def self.call(value, options = {})
|
23
27
|
new.call(value, options)
|
24
28
|
end
|
25
29
|
|
26
30
|
# Abstract method that must be implemented by validator subclasses.
|
27
31
|
#
|
28
|
-
# This method contains the actual validation logic to
|
29
|
-
# Subclasses must override this method
|
30
|
-
# validation implementation.
|
32
|
+
# This method contains the actual validation logic to verify the input
|
33
|
+
# value meets the specified criteria. Subclasses must override this method
|
34
|
+
# to provide their specific validation implementation.
|
31
35
|
#
|
32
|
-
# @param
|
33
|
-
# @param
|
36
|
+
# @param value [Object] the value to be validated
|
37
|
+
# @param options [Hash] additional options for the validation
|
34
38
|
#
|
35
|
-
# @return [Object] the
|
39
|
+
# @return [Object] the validated value if validation passes
|
36
40
|
#
|
37
41
|
# @raise [UndefinedCallError] always raised in the base class
|
42
|
+
# @raise [ValidationError] when validation fails in subclass implementations
|
38
43
|
#
|
39
44
|
# @example Implement in a subclass
|
40
|
-
#
|
41
|
-
#
|
45
|
+
# class BlankValidator < CMDx::Validator
|
46
|
+
# def call(value, options = {})
|
47
|
+
# if value.nil? || value.empty?
|
48
|
+
# raise ValidationError, options[:message] || "Value cannot be blank"
|
49
|
+
# end
|
50
|
+
# end
|
42
51
|
# end
|
43
|
-
def call(
|
52
|
+
def call(value, options = {}) # rubocop:disable Lint/UnusedMethodArgument
|
44
53
|
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
45
54
|
end
|
46
55
|
|
@@ -1,24 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
# Registry for
|
4
|
+
# Registry for parameter validation handlers in the CMDx framework.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# parameter validation
|
8
|
-
#
|
6
|
+
# ValidatorRegistry manages the collection of validator implementations
|
7
|
+
# that can be used for parameter validation in tasks. It provides a
|
8
|
+
# centralized registry where validators can be registered by type and
|
9
|
+
# invoked during parameter processing. The registry comes pre-loaded
|
10
|
+
# with built-in validators for common validation scenarios.
|
9
11
|
class ValidatorRegistry
|
10
12
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# @return [Hash] hash containing validator type keys and validator class values
|
13
|
+
# @return [Hash] internal hash storing validator implementations by type
|
14
14
|
attr_reader :registry
|
15
15
|
|
16
|
-
#
|
16
|
+
# Creates a new validator registry with built-in validators.
|
17
|
+
#
|
18
|
+
# The registry is initialized with standard validators including
|
19
|
+
# exclusion, format, inclusion, length, numeric, and presence validation.
|
20
|
+
# These built-in validators provide common validation functionality
|
21
|
+
# that can be immediately used without additional registration.
|
17
22
|
#
|
18
|
-
# @return [ValidatorRegistry] a new
|
23
|
+
# @return [ValidatorRegistry] a new registry instance with built-in validators
|
19
24
|
#
|
20
|
-
# @example
|
21
|
-
# ValidatorRegistry.new
|
25
|
+
# @example Create a new validator registry
|
26
|
+
# registry = ValidatorRegistry.new
|
27
|
+
# registry.registry.keys #=> [:exclusion, :format, :inclusion, :length, :numeric, :presence]
|
22
28
|
def initialize
|
23
29
|
@registry = {
|
24
30
|
exclusion: Validators::Exclusion,
|
@@ -30,46 +36,62 @@ module CMDx
|
|
30
36
|
}
|
31
37
|
end
|
32
38
|
|
33
|
-
# Registers a
|
39
|
+
# Registers a new validator implementation for the specified type.
|
40
|
+
#
|
41
|
+
# This method allows custom validators to be added to the registry,
|
42
|
+
# enabling extended validation functionality beyond the built-in
|
43
|
+
# validators. The validator can be a class, symbol, string, or proc
|
44
|
+
# that implements the validation logic.
|
34
45
|
#
|
35
|
-
# @param type [Symbol] the validator type
|
36
|
-
# @param validator [Class,
|
46
|
+
# @param type [Symbol] the validator type identifier
|
47
|
+
# @param validator [Class, Symbol, String, Proc] the validator implementation
|
37
48
|
#
|
38
49
|
# @return [ValidatorRegistry] returns self for method chaining
|
39
50
|
#
|
40
|
-
# @example
|
51
|
+
# @example Register a custom validator class
|
41
52
|
# registry.register(:email, EmailValidator)
|
42
53
|
#
|
43
|
-
# @example
|
44
|
-
# registry.register(:
|
54
|
+
# @example Register a symbol validator
|
55
|
+
# registry.register(:zipcode, :validate_zipcode)
|
45
56
|
#
|
46
|
-
# @example
|
47
|
-
# registry.register(:
|
57
|
+
# @example Register a proc validator
|
58
|
+
# registry.register(:positive, ->(value, options) { value > 0 })
|
48
59
|
#
|
49
|
-
# @example
|
50
|
-
# registry.register(:
|
51
|
-
# .register(:
|
60
|
+
# @example Method chaining
|
61
|
+
# registry.register(:email, EmailValidator)
|
62
|
+
# .register(:phone, PhoneValidator)
|
52
63
|
def register(type, validator)
|
53
64
|
registry[type] = validator
|
54
65
|
self
|
55
66
|
end
|
56
67
|
|
57
|
-
# Executes validation for a
|
68
|
+
# Executes validation for a parameter value using the specified validator type.
|
69
|
+
#
|
70
|
+
# This method performs validation by looking up the registered validator
|
71
|
+
# for the given type and executing it with the provided value and options.
|
72
|
+
# The validation is only performed if the task's evaluation of the options
|
73
|
+
# returns a truthy value, allowing for conditional validation.
|
58
74
|
#
|
59
|
-
# @param task [Task] the task instance
|
60
|
-
# @param type [Symbol] the validator type to
|
75
|
+
# @param task [Task] the task instance performing validation
|
76
|
+
# @param type [Symbol] the validator type to use
|
61
77
|
# @param value [Object] the value to validate
|
62
|
-
# @param options [Hash] options
|
78
|
+
# @param options [Hash] validation options and configuration
|
63
79
|
#
|
64
|
-
# @return [Object, nil]
|
80
|
+
# @return [Object, nil] the validation result or nil if validation was skipped
|
65
81
|
#
|
66
|
-
# @raise [UnknownValidatorError]
|
82
|
+
# @raise [UnknownValidatorError] if the specified validator type is not registered
|
67
83
|
#
|
68
|
-
# @example
|
84
|
+
# @example Validate with a built-in validator
|
69
85
|
# registry.call(task, :presence, "", {})
|
86
|
+
# #=> may raise ValidationError if value is blank
|
87
|
+
#
|
88
|
+
# @example Validate with options
|
89
|
+
# registry.call(task, :length, "hello", minimum: 3, maximum: 10)
|
90
|
+
# #=> validates string length is between 3 and 10 characters
|
70
91
|
#
|
71
|
-
# @example
|
72
|
-
# registry.call(task, :
|
92
|
+
# @example Conditional validation that gets skipped
|
93
|
+
# registry.call(task, :presence, "", if: -> { false })
|
94
|
+
# #=> returns nil without performing validation
|
73
95
|
def call(task, type, value, options = {})
|
74
96
|
raise UnknownValidatorError, "unknown validator #{type}" unless registry.key?(type)
|
75
97
|
return unless task.cmdx_eval(options)
|
@@ -35,7 +35,7 @@ module CMDx
|
|
35
35
|
#
|
36
36
|
# @example Valid exclusion
|
37
37
|
# Validators::Exclusion.call("user", exclusion: { in: ["admin", "root"] })
|
38
|
-
#
|
38
|
+
# #=> nil (no error raised)
|
39
39
|
#
|
40
40
|
# @example Using a custom message
|
41
41
|
# Validators::Exclusion.call("admin", exclusion: { in: ["admin", "root"], message: "Reserved username not allowed" })
|
@@ -25,7 +25,7 @@ module CMDx
|
|
25
25
|
#
|
26
26
|
# @example Validating with a positive pattern
|
27
27
|
# Validators::Format.call("user123", format: { with: /\A[a-z]+\d+\z/ })
|
28
|
-
#
|
28
|
+
# #=> nil (no error raised)
|
29
29
|
#
|
30
30
|
# @example Validating with a negative pattern
|
31
31
|
# Validators::Format.call("admin", format: { without: /admin|root/ })
|
@@ -33,7 +33,7 @@ module CMDx
|
|
33
33
|
#
|
34
34
|
# @example Validating with both patterns
|
35
35
|
# Validators::Format.call("user123", format: { with: /\A[a-z]+\d+\z/, without: /admin|root/ })
|
36
|
-
#
|
36
|
+
# #=> nil (no error raised)
|
37
37
|
#
|
38
38
|
# @example Invalid format with positive pattern
|
39
39
|
# Validators::Format.call("123abc", format: { with: /\A[a-z]+\d+\z/ })
|
@@ -27,11 +27,11 @@ module CMDx
|
|
27
27
|
#
|
28
28
|
# @example Including from an array
|
29
29
|
# Validators::Inclusion.call("user", inclusion: { in: ["user", "admin"] })
|
30
|
-
#
|
30
|
+
# #=> nil (no error raised)
|
31
31
|
#
|
32
32
|
# @example Including from a range
|
33
33
|
# Validators::Inclusion.call(5, inclusion: { in: 1..10 })
|
34
|
-
#
|
34
|
+
# #=> nil (no error raised)
|
35
35
|
#
|
36
36
|
# @example Invalid inclusion from array
|
37
37
|
# Validators::Inclusion.call("guest", inclusion: { in: ["user", "admin"] })
|
@@ -39,7 +39,7 @@ module CMDx
|
|
39
39
|
#
|
40
40
|
# @example Validating within a range
|
41
41
|
# Validators::Length.call("hello", length: { within: 1..10 })
|
42
|
-
#
|
42
|
+
# #=> nil (no error raised)
|
43
43
|
#
|
44
44
|
# @example Validating minimum length
|
45
45
|
# Validators::Length.call("hi", length: { min: 5 })
|
@@ -47,7 +47,7 @@ module CMDx
|
|
47
47
|
#
|
48
48
|
# @example Validating exact length
|
49
49
|
# Validators::Length.call("test", length: { is: 4 })
|
50
|
-
#
|
50
|
+
# #=> nil (no error raised)
|
51
51
|
#
|
52
52
|
# @example Validating with custom message
|
53
53
|
# Validators::Length.call("", length: { min: 1, message: "cannot be empty" })
|