cmdx 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/rules/cursor-instructions.mdc +6 -0
- data/.rubocop.yml +16 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +42 -1
- data/README.md +72 -25
- data/docs/ai_prompts.md +309 -0
- data/docs/basics/call.md +225 -14
- data/docs/basics/chain.md +271 -0
- data/docs/basics/context.md +232 -33
- data/docs/basics/setup.md +76 -12
- data/docs/callbacks.md +273 -0
- data/docs/configuration.md +158 -28
- data/docs/getting_started.md +134 -22
- data/docs/interruptions/exceptions.md +189 -11
- data/docs/interruptions/faults.md +187 -44
- data/docs/interruptions/halt.md +179 -35
- data/docs/logging.md +194 -53
- data/docs/middlewares.md +735 -0
- data/docs/outcomes/result.md +296 -10
- data/docs/outcomes/states.md +212 -19
- data/docs/outcomes/statuses.md +284 -18
- data/docs/parameters/coercions.md +402 -29
- data/docs/parameters/defaults.md +249 -25
- data/docs/parameters/definitions.md +238 -72
- data/docs/parameters/namespacing.md +250 -27
- data/docs/parameters/validations.md +193 -168
- data/docs/testing.md +550 -0
- data/docs/tips_and_tricks.md +95 -43
- data/docs/workflows.md +319 -0
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +69 -0
- data/lib/cmdx/callback_registry.rb +106 -0
- data/lib/cmdx/chain.rb +190 -0
- data/lib/cmdx/chain_inspector.rb +149 -0
- data/lib/cmdx/chain_serializer.rb +175 -0
- data/lib/cmdx/coercions/array.rb +37 -0
- data/lib/cmdx/coercions/big_decimal.rb +33 -0
- data/lib/cmdx/coercions/boolean.rb +41 -1
- data/lib/cmdx/coercions/complex.rb +31 -0
- data/lib/cmdx/coercions/date.rb +39 -0
- data/lib/cmdx/coercions/date_time.rb +39 -0
- data/lib/cmdx/coercions/float.rb +31 -0
- data/lib/cmdx/coercions/hash.rb +42 -0
- data/lib/cmdx/coercions/integer.rb +32 -0
- data/lib/cmdx/coercions/rational.rb +31 -0
- data/lib/cmdx/coercions/string.rb +31 -0
- data/lib/cmdx/coercions/time.rb +39 -0
- data/lib/cmdx/coercions/virtual.rb +31 -0
- data/lib/cmdx/configuration.rb +217 -9
- data/lib/cmdx/context.rb +173 -2
- data/lib/cmdx/core_ext/hash.rb +72 -0
- data/lib/cmdx/core_ext/module.rb +94 -0
- data/lib/cmdx/core_ext/object.rb +105 -0
- data/lib/cmdx/correlator.rb +217 -0
- data/lib/cmdx/error.rb +210 -8
- data/lib/cmdx/errors.rb +256 -1
- data/lib/cmdx/fault.rb +177 -2
- data/lib/cmdx/faults.rb +158 -2
- data/lib/cmdx/immutator.rb +121 -2
- data/lib/cmdx/lazy_struct.rb +261 -18
- data/lib/cmdx/log_formatters/json.rb +46 -0
- data/lib/cmdx/log_formatters/key_value.rb +46 -0
- data/lib/cmdx/log_formatters/line.rb +54 -0
- data/lib/cmdx/log_formatters/logstash.rb +64 -0
- data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
- data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
- data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
- data/lib/cmdx/log_formatters/raw.rb +54 -0
- data/lib/cmdx/logger.rb +85 -0
- data/lib/cmdx/logger_ansi.rb +93 -7
- data/lib/cmdx/logger_serializer.rb +116 -0
- data/lib/cmdx/middleware.rb +74 -0
- data/lib/cmdx/middleware_registry.rb +106 -0
- data/lib/cmdx/middlewares/correlate.rb +266 -0
- data/lib/cmdx/middlewares/timeout.rb +232 -0
- data/lib/cmdx/parameter.rb +228 -1
- data/lib/cmdx/parameter_inspector.rb +61 -0
- data/lib/cmdx/parameter_registry.rb +125 -0
- data/lib/cmdx/parameter_serializer.rb +83 -0
- data/lib/cmdx/parameter_validator.rb +62 -0
- data/lib/cmdx/parameter_value.rb +109 -1
- data/lib/cmdx/parameters_inspector.rb +59 -0
- data/lib/cmdx/parameters_serializer.rb +102 -0
- data/lib/cmdx/railtie.rb +123 -3
- data/lib/cmdx/result.rb +399 -20
- data/lib/cmdx/result_ansi.rb +105 -9
- data/lib/cmdx/result_inspector.rb +76 -0
- data/lib/cmdx/result_logger.rb +90 -3
- data/lib/cmdx/result_serializer.rb +137 -0
- data/lib/cmdx/rspec/result_matchers.rb +917 -0
- data/lib/cmdx/rspec/task_matchers.rb +570 -0
- data/lib/cmdx/task.rb +409 -34
- data/lib/cmdx/task_serializer.rb +74 -2
- data/lib/cmdx/utils/ansi_color.rb +95 -0
- data/lib/cmdx/utils/log_timestamp.rb +48 -0
- data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
- data/lib/cmdx/utils/name_affix.rb +78 -0
- data/lib/cmdx/validators/custom.rb +82 -0
- data/lib/cmdx/validators/exclusion.rb +94 -0
- data/lib/cmdx/validators/format.rb +102 -8
- data/lib/cmdx/validators/inclusion.rb +104 -0
- data/lib/cmdx/validators/length.rb +128 -0
- data/lib/cmdx/validators/numeric.rb +128 -0
- data/lib/cmdx/validators/presence.rb +93 -7
- data/lib/cmdx/version.rb +7 -1
- data/lib/cmdx/workflow.rb +394 -0
- data/lib/cmdx.rb +25 -64
- data/lib/generators/cmdx/install_generator.rb +37 -1
- data/lib/generators/cmdx/task_generator.rb +69 -1
- data/lib/generators/cmdx/templates/install.rb +8 -12
- data/lib/generators/cmdx/workflow_generator.rb +109 -0
- metadata +54 -15
- data/docs/basics/run.md +0 -34
- data/docs/batch.md +0 -53
- data/docs/example.md +0 -82
- data/docs/hooks.md +0 -59
- data/lib/cmdx/batch.rb +0 -43
- data/lib/cmdx/parameters.rb +0 -34
- data/lib/cmdx/run.rb +0 -38
- data/lib/cmdx/run_inspector.rb +0 -26
- data/lib/cmdx/run_serializer.rb +0 -16
- data/lib/cmdx/task_hook.rb +0 -18
- data/lib/generators/cmdx/batch_generator.rb +0 -30
- /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Parameter collection class for managing multiple parameter definitions.
|
5
|
+
#
|
6
|
+
# The ParameterRegistry class extends Array to provide specialized functionality for
|
7
|
+
# managing collections of Parameter instances within CMDx tasks. It handles
|
8
|
+
# validation coordination, serialization, and inspection of parameter groups.
|
9
|
+
#
|
10
|
+
# @example Basic parameter collection usage
|
11
|
+
# parameter_registry = ParameterRegistry.new
|
12
|
+
# parameter_registry << Parameter.new(:user_id, klass: Task, type: :integer)
|
13
|
+
# parameter_registry << Parameter.new(:email, klass: Task, type: :string)
|
14
|
+
# parameter_registry.valid? # => true (if all parameters are valid)
|
15
|
+
#
|
16
|
+
# @example Parameter collection validation
|
17
|
+
# parameter_registry.validate!(task_instance) # Validates all parameters
|
18
|
+
# parameter_registry.invalid? # => true if any parameter failed validation
|
19
|
+
#
|
20
|
+
# @example Parameter collection serialization
|
21
|
+
# parameter_registry.to_h # => Array of parameter hash representations
|
22
|
+
# parameter_registry.to_s # => Human-readable parameter descriptions
|
23
|
+
#
|
24
|
+
# @see CMDx::Parameter Individual parameter definitions
|
25
|
+
# @see CMDx::Task Task parameter integration
|
26
|
+
class ParameterRegistry < Array
|
27
|
+
|
28
|
+
# Checks if any parameters in the collection are invalid.
|
29
|
+
#
|
30
|
+
# @return [Boolean] true if any parameter has validation errors, false otherwise
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# parameter_registry.invalid? # => true if validation errors exist
|
34
|
+
def invalid?
|
35
|
+
!valid?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Checks if all parameters in the collection are valid.
|
39
|
+
#
|
40
|
+
# @return [Boolean] true if all parameters are valid, false otherwise
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# parameter_registry.valid? # => true if no validation errors exist
|
44
|
+
def valid?
|
45
|
+
all?(&:valid?)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validates all parameters in the collection against a task instance.
|
49
|
+
#
|
50
|
+
# Recursively validates each parameter and its children by calling the
|
51
|
+
# parameter accessor methods on the task instance, which triggers
|
52
|
+
# value resolution, coercion, and validation.
|
53
|
+
#
|
54
|
+
# @param task [CMDx::Task] The task instance to validate parameters against
|
55
|
+
# @return [void]
|
56
|
+
#
|
57
|
+
# @example Validating parameters
|
58
|
+
# task = ProcessOrderTask.new
|
59
|
+
# parameter_registry.validate!(task) # Validates all parameters
|
60
|
+
#
|
61
|
+
# @example Validation with nested parameters
|
62
|
+
# # Validates parent parameters and all nested child parameters
|
63
|
+
# parameter_registry.validate!(task_with_nested_params)
|
64
|
+
def validate!(task)
|
65
|
+
each { |p| recursive_validate!(task, p) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Converts the parameter collection to a hash representation.
|
69
|
+
#
|
70
|
+
# Serializes all parameters in the collection to their hash representations
|
71
|
+
# using the ParametersSerializer.
|
72
|
+
#
|
73
|
+
# @return [Array<Hash>] Array of serialized parameter data
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# parameter_registry.to_h
|
77
|
+
# # => [
|
78
|
+
# # {
|
79
|
+
# # source: :context,
|
80
|
+
# # name: :user_id,
|
81
|
+
# # type: :integer,
|
82
|
+
# # required: true,
|
83
|
+
# # options: {},
|
84
|
+
# # children: []
|
85
|
+
# # },
|
86
|
+
# # { ... }
|
87
|
+
# # ]
|
88
|
+
def to_h
|
89
|
+
ParametersSerializer.call(self)
|
90
|
+
end
|
91
|
+
alias to_a to_h
|
92
|
+
|
93
|
+
# Converts the parameter collection to a string representation.
|
94
|
+
#
|
95
|
+
# Creates a human-readable string representation of all parameters
|
96
|
+
# in the collection using the ParametersInspector.
|
97
|
+
#
|
98
|
+
# @return [String] Multi-line parameter descriptions
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# parameter_registry.to_s
|
102
|
+
# # => "Parameter: name=user_id type=integer source=context required=true
|
103
|
+
# # Parameter: name=email type=string source=context required=false"
|
104
|
+
def to_s
|
105
|
+
ParametersInspector.call(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Recursively validates a parameter and all its children.
|
111
|
+
#
|
112
|
+
# Calls the parameter accessor method on the task to trigger validation,
|
113
|
+
# then recursively validates all child parameters for nested parameter
|
114
|
+
# structures.
|
115
|
+
#
|
116
|
+
# @param task [CMDx::Task] The task instance to validate against
|
117
|
+
# @param parameter [CMDx::Parameter] The parameter to validate
|
118
|
+
# @return [void]
|
119
|
+
def recursive_validate!(task, parameter)
|
120
|
+
task.send(parameter.method_name)
|
121
|
+
parameter.children.each { |cp| recursive_validate!(task, cp) }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -1,10 +1,93 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
+
# Parameter serialization utility for converting Parameter objects to hash representations.
|
5
|
+
#
|
6
|
+
# The ParameterSerializer module provides functionality to serialize Parameter
|
7
|
+
# instances into structured hash representations suitable for inspection,
|
8
|
+
# logging, debugging, and data interchange.
|
9
|
+
#
|
10
|
+
# @example Basic parameter serialization
|
11
|
+
# parameter = Parameter.new(:user_id, klass: Task, type: :integer, required: true)
|
12
|
+
# ParameterSerializer.call(parameter)
|
13
|
+
# # => {
|
14
|
+
# # source: :context,
|
15
|
+
# # name: :user_id,
|
16
|
+
# # type: :integer,
|
17
|
+
# # required: true,
|
18
|
+
# # options: {},
|
19
|
+
# # children: []
|
20
|
+
# # }
|
21
|
+
#
|
22
|
+
# @example Parameter with validation options
|
23
|
+
# parameter = Parameter.new(:email, klass: Task, type: :string,
|
24
|
+
# format: { with: /@/ }, presence: true)
|
25
|
+
# ParameterSerializer.call(parameter)
|
26
|
+
# # => {
|
27
|
+
# # source: :context,
|
28
|
+
# # name: :email,
|
29
|
+
# # type: :string,
|
30
|
+
# # required: false,
|
31
|
+
# # options: { format: { with: /@/ }, presence: true },
|
32
|
+
# # children: []
|
33
|
+
# # }
|
34
|
+
#
|
35
|
+
# @example Nested parameter serialization
|
36
|
+
# parent = Parameter.new(:address, klass: Task) do
|
37
|
+
# required :street, :city
|
38
|
+
# end
|
39
|
+
# ParameterSerializer.call(parent)
|
40
|
+
# # => {
|
41
|
+
# # source: :context,
|
42
|
+
# # name: :address,
|
43
|
+
# # type: :virtual,
|
44
|
+
# # required: false,
|
45
|
+
# # options: {},
|
46
|
+
# # children: [
|
47
|
+
# # { source: :address, name: :street, type: :virtual, required: true, options: {}, children: [] },
|
48
|
+
# # { source: :address, name: :city, type: :virtual, required: true, options: {}, children: [] }
|
49
|
+
# # ]
|
50
|
+
# # }
|
51
|
+
#
|
52
|
+
# @see CMDx::Parameter Parameter object creation and configuration
|
53
|
+
# @see CMDx::ParameterInspector Human-readable parameter formatting
|
4
54
|
module ParameterSerializer
|
5
55
|
|
6
56
|
module_function
|
7
57
|
|
58
|
+
# Converts a Parameter object to a hash representation.
|
59
|
+
#
|
60
|
+
# Serializes a Parameter instance into a structured hash containing
|
61
|
+
# all relevant parameter information including source, name, type,
|
62
|
+
# requirement status, options, and recursively serialized children.
|
63
|
+
#
|
64
|
+
# @param parameter [CMDx::Parameter] The parameter object to serialize
|
65
|
+
# @return [Hash] Structured hash representation of the parameter
|
66
|
+
#
|
67
|
+
# @example Simple parameter serialization
|
68
|
+
# param = Parameter.new(:age, klass: Task, type: :integer, required: true)
|
69
|
+
# ParameterSerializer.call(param)
|
70
|
+
# # => {
|
71
|
+
# # source: :context,
|
72
|
+
# # name: :age,
|
73
|
+
# # type: :integer,
|
74
|
+
# # required: true,
|
75
|
+
# # options: {},
|
76
|
+
# # children: []
|
77
|
+
# # }
|
78
|
+
#
|
79
|
+
# @example Parameter with custom source and options
|
80
|
+
# param = Parameter.new(:name, klass: Task, source: :user,
|
81
|
+
# type: :string, length: { min: 2 })
|
82
|
+
# ParameterSerializer.call(param)
|
83
|
+
# # => {
|
84
|
+
# # source: :user,
|
85
|
+
# # name: :name,
|
86
|
+
# # type: :string,
|
87
|
+
# # required: false,
|
88
|
+
# # options: { length: { min: 2 } },
|
89
|
+
# # children: []
|
90
|
+
# # }
|
8
91
|
def call(parameter)
|
9
92
|
{
|
10
93
|
source: parameter.method_source,
|
@@ -1,10 +1,72 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
+
# Parameter validation orchestration module for CMDx tasks.
|
5
|
+
#
|
6
|
+
# The ParameterValidator module provides high-level parameter validation
|
7
|
+
# coordination for task instances. It triggers validation of all task
|
8
|
+
# parameters and handles validation failure by setting task failure state
|
9
|
+
# with appropriate error messages.
|
10
|
+
#
|
11
|
+
# @example Basic parameter validation
|
12
|
+
# class ProcessOrderTask < CMDx::Task
|
13
|
+
# required :order_id, type: :integer
|
14
|
+
# required :email, type: :string, format: { with: /@/ }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# task = ProcessOrderTask.new
|
18
|
+
# ParameterValidator.call(task) # Validates all parameters
|
19
|
+
# # If validation fails, task.failed? => true
|
20
|
+
#
|
21
|
+
# @example Validation with error handling
|
22
|
+
# task = ProcessOrderTask.new
|
23
|
+
# ParameterValidator.call(task)
|
24
|
+
#
|
25
|
+
# if task.failed?
|
26
|
+
# puts task.result.metadata[:reason] # => "order_id is a required parameter. email is invalid"
|
27
|
+
# puts task.errors.messages # => { order_id: ["is a required parameter"], email: ["is invalid"] }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @example Successful validation
|
31
|
+
# task = ProcessOrderTask.call(order_id: 123, email: "user@example.com")
|
32
|
+
# # ParameterValidator runs automatically and validation passes
|
33
|
+
# task.success? # => true
|
34
|
+
#
|
35
|
+
# @see CMDx::Parameters Parameter collection validation
|
36
|
+
# @see CMDx::Parameter Individual parameter definitions
|
37
|
+
# @see CMDx::Task Task execution and parameter integration
|
4
38
|
module ParameterValidator
|
5
39
|
|
6
40
|
module_function
|
7
41
|
|
42
|
+
# Validates all parameters for a task instance.
|
43
|
+
#
|
44
|
+
# Triggers validation of all task parameters through the Parameters collection.
|
45
|
+
# If any validation errors occur, sets the task to failed state with a
|
46
|
+
# comprehensive error message and detailed error information.
|
47
|
+
#
|
48
|
+
# @param task [CMDx::Task] The task instance to validate parameters for
|
49
|
+
# @return [void]
|
50
|
+
#
|
51
|
+
# @example Validating task parameters
|
52
|
+
# task = MyTask.new
|
53
|
+
# ParameterValidator.call(task)
|
54
|
+
#
|
55
|
+
# # If validation fails:
|
56
|
+
# task.failed? # => true
|
57
|
+
# task.result.metadata[:reason] # => "Combined error messages from all failed parameters"
|
58
|
+
# task.errors.empty? # => false
|
59
|
+
#
|
60
|
+
# @example Validation success
|
61
|
+
# task = MyTask.new # with valid parameters
|
62
|
+
# ParameterValidator.call(task)
|
63
|
+
#
|
64
|
+
# task.errors.empty? # => true
|
65
|
+
# # Task continues normal execution
|
66
|
+
#
|
67
|
+
# @note This method is typically called automatically during task execution
|
68
|
+
# before the main task logic runs, ensuring parameter validation occurs
|
69
|
+
# early in the task lifecycle.
|
8
70
|
def call(task)
|
9
71
|
task.class.cmd_parameters.validate!(task)
|
10
72
|
return if task.errors.empty?
|
data/lib/cmdx/parameter_value.rb
CHANGED
@@ -1,27 +1,95 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
+
# Parameter value resolution and processing class for CMDx tasks.
|
5
|
+
#
|
6
|
+
# The ParameterValue class handles the complete lifecycle of parameter value
|
7
|
+
# processing including source resolution, type coercion, validation, and error
|
8
|
+
# handling. It serves as the bridge between parameter definitions and their
|
9
|
+
# actual values during task execution.
|
10
|
+
#
|
11
|
+
# @example Basic parameter value processing
|
12
|
+
# task = ProcessOrderTask.new
|
13
|
+
# parameter = Parameter.new(:order_id, klass: ProcessOrderTask, type: :integer)
|
14
|
+
# value_processor = ParameterValue.new(task, parameter)
|
15
|
+
# processed_value = value_processor.call # Resolves, coerces, and validates
|
16
|
+
#
|
17
|
+
# @example Parameter value with validation
|
18
|
+
# parameter = Parameter.new(:email, klass: Task, type: :string,
|
19
|
+
# format: { with: /@/ }, presence: true)
|
20
|
+
# value_processor = ParameterValue.new(task, parameter)
|
21
|
+
# value_processor.call # Validates email format and presence
|
22
|
+
#
|
23
|
+
# @example Parameter value with default
|
24
|
+
# parameter = Parameter.new(:priority, klass: Task, default: "normal")
|
25
|
+
# value_processor = ParameterValue.new(task, parameter)
|
26
|
+
# value_processor.call # Returns "normal" if not provided
|
27
|
+
#
|
28
|
+
# @see CMDx::Parameter Parameter definition and configuration
|
29
|
+
# @see CMDx::Coercions Type coercion modules
|
30
|
+
# @see CMDx::Validators Parameter validation modules
|
4
31
|
class ParameterValue
|
5
32
|
|
6
|
-
__cmdx_attr_delegator :parent, :method_source, :name, :options, :required?, :optional?, :type,
|
33
|
+
__cmdx_attr_delegator :parent, :method_source, :name, :options, :required?, :optional?, :type,
|
34
|
+
to: :parameter,
|
35
|
+
private: true
|
7
36
|
|
37
|
+
# @return [CMDx::Task] The task instance being processed
|
38
|
+
# @return [CMDx::Parameter] The parameter definition being processed
|
8
39
|
attr_reader :task, :parameter
|
9
40
|
|
41
|
+
# Initializes a new ParameterValue processor.
|
42
|
+
#
|
43
|
+
# Creates a parameter value processor for resolving, coercing, and validating
|
44
|
+
# a specific parameter value within the context of a task instance.
|
45
|
+
#
|
46
|
+
# @param task [CMDx::Task] The task instance containing the parameter source
|
47
|
+
# @param parameter [CMDx::Parameter] The parameter definition to process
|
48
|
+
#
|
49
|
+
# @example Creating a parameter value processor
|
50
|
+
# processor = ParameterValue.new(task_instance, parameter_definition)
|
10
51
|
def initialize(task, parameter)
|
11
52
|
@task = task
|
12
53
|
@parameter = parameter
|
13
54
|
end
|
14
55
|
|
56
|
+
# Processes the parameter value through coercion and validation.
|
57
|
+
#
|
58
|
+
# Executes the complete parameter value processing pipeline:
|
59
|
+
# 1. Resolves the raw value from the source
|
60
|
+
# 2. Applies type coercion based on parameter type
|
61
|
+
# 3. Runs all configured validations
|
62
|
+
# 4. Returns the final processed value
|
63
|
+
#
|
64
|
+
# @return [Object] The processed and validated parameter value
|
65
|
+
# @raise [CoercionError] If type coercion fails
|
66
|
+
# @raise [ValidationError] If validation fails
|
67
|
+
#
|
68
|
+
# @example Processing a simple parameter
|
69
|
+
# processor.call # => 42 (after coercion and validation)
|
70
|
+
#
|
71
|
+
# @example Processing with validation failure
|
72
|
+
# processor.call # => raises ValidationError: "is not valid"
|
15
73
|
def call
|
16
74
|
coerce!.tap { validate! }
|
17
75
|
end
|
18
76
|
|
19
77
|
private
|
20
78
|
|
79
|
+
# Checks if the parameter source method is defined on the task.
|
80
|
+
#
|
81
|
+
# @return [Boolean] true if source method exists, false otherwise
|
21
82
|
def source_defined?
|
22
83
|
task.respond_to?(method_source, true) || task.__cmdx_try(method_source)
|
23
84
|
end
|
24
85
|
|
86
|
+
# Resolves the source object that contains the parameter value.
|
87
|
+
#
|
88
|
+
# Gets the source object by calling the method_source on the task instance.
|
89
|
+
# Raises ValidationError if the source method is not defined.
|
90
|
+
#
|
91
|
+
# @return [Object] The source object containing parameter values
|
92
|
+
# @raise [ValidationError] If source method is undefined
|
25
93
|
def source
|
26
94
|
return @source if defined?(@source)
|
27
95
|
|
@@ -36,18 +104,31 @@ module CMDx
|
|
36
104
|
@source = task.__cmdx_try(method_source)
|
37
105
|
end
|
38
106
|
|
107
|
+
# Checks if the source object has the parameter value.
|
108
|
+
#
|
109
|
+
# @return [Boolean] true if source responds to parameter name, false otherwise
|
39
110
|
def source_value?
|
40
111
|
return false if source.nil?
|
41
112
|
|
42
113
|
source.__cmdx_respond_to?(name, true)
|
43
114
|
end
|
44
115
|
|
116
|
+
# Checks if a required parameter value is missing from the source.
|
117
|
+
#
|
118
|
+
# @return [Boolean] true if required parameter is missing, false otherwise
|
45
119
|
def source_value_required?
|
46
120
|
return false if parent&.optional? && source.nil?
|
47
121
|
|
48
122
|
required? && !source_value?
|
49
123
|
end
|
50
124
|
|
125
|
+
# Resolves the raw parameter value from the source.
|
126
|
+
#
|
127
|
+
# Gets the parameter value from the source object, handling required
|
128
|
+
# parameter validation and default value resolution.
|
129
|
+
#
|
130
|
+
# @return [Object] The raw parameter value
|
131
|
+
# @raise [ValidationError] If required parameter is missing
|
51
132
|
def value
|
52
133
|
return @value if defined?(@value)
|
53
134
|
|
@@ -64,6 +145,14 @@ module CMDx
|
|
64
145
|
@value = task.__cmdx_yield(options[:default])
|
65
146
|
end
|
66
147
|
|
148
|
+
# Applies type coercion to the parameter value.
|
149
|
+
#
|
150
|
+
# Attempts to coerce the value to each specified type in order,
|
151
|
+
# supporting multiple type fallbacks for flexible coercion.
|
152
|
+
#
|
153
|
+
# @return [Object] The coerced parameter value
|
154
|
+
# @raise [CoercionError] If all coercion attempts fail
|
155
|
+
# @raise [UnknownCoercionError] If an unknown type is specified
|
67
156
|
def coerce!
|
68
157
|
types = Array(type)
|
69
158
|
tsize = types.size - 1
|
@@ -99,20 +188,39 @@ module CMDx
|
|
99
188
|
end
|
100
189
|
end
|
101
190
|
|
191
|
+
# Checks if validations should be skipped for optional missing arguments.
|
192
|
+
#
|
193
|
+
# @return [Boolean] true if validations should be skipped, false otherwise
|
102
194
|
def skip_validations_due_to_optional_missing_argument?
|
103
195
|
optional? && value.nil? && !source.nil? && !source.__cmdx_respond_to?(name, true)
|
104
196
|
end
|
105
197
|
|
198
|
+
# Checks if a specific validator should be skipped due to conditional logic.
|
199
|
+
#
|
200
|
+
# @param key [Symbol] The validator key to check
|
201
|
+
# @return [Boolean] true if validator should be skipped, false otherwise
|
106
202
|
def skip_validator_due_to_conditional?(key)
|
107
203
|
opts = options[key]
|
108
204
|
opts.is_a?(Hash) && !task.__cmdx_eval(opts)
|
109
205
|
end
|
110
206
|
|
207
|
+
# Checks if a specific validator should be skipped due to allow_nil option.
|
208
|
+
#
|
209
|
+
# @param key [Symbol] The validator key to check
|
210
|
+
# @return [Boolean] true if validator should be skipped, false otherwise
|
111
211
|
def skip_validator_due_to_allow_nil?(key)
|
112
212
|
opts = options[key]
|
113
213
|
opts.is_a?(Hash) && opts[:allow_nil] && value.nil?
|
114
214
|
end
|
115
215
|
|
216
|
+
# Runs all configured validations on the parameter value.
|
217
|
+
#
|
218
|
+
# Iterates through all validation options and applies the appropriate
|
219
|
+
# validators, respecting skip conditions for optional parameters,
|
220
|
+
# conditional validations, and allow_nil settings.
|
221
|
+
#
|
222
|
+
# @return [void]
|
223
|
+
# @raise [ValidationError] If any validation fails
|
116
224
|
def validate!
|
117
225
|
return if skip_validations_due_to_optional_missing_argument?
|
118
226
|
|
@@ -1,10 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
+
# Parameter collection inspection utility for generating human-readable descriptions.
|
5
|
+
#
|
6
|
+
# The ParametersInspector module provides functionality to convert collections
|
7
|
+
# of parameters into formatted, human-readable string representations. It
|
8
|
+
# coordinates with ParameterInspector to format individual parameters and
|
9
|
+
# combines them into a cohesive multi-parameter description.
|
10
|
+
#
|
11
|
+
# @example Basic parameters collection inspection
|
12
|
+
# parameter_registry = ParameterRegistry.new
|
13
|
+
# parameter_registry << Parameter.new(:user_id, klass: Task, type: :integer, required: true)
|
14
|
+
# parameter_registry << Parameter.new(:email, klass: Task, type: :string, required: false)
|
15
|
+
#
|
16
|
+
# ParametersInspector.call(parameter_registry)
|
17
|
+
# # => "Parameter: name=user_id type=integer source=context required=true options={}
|
18
|
+
# # Parameter: name=email type=string source=context required=false options={}"
|
19
|
+
#
|
20
|
+
# @example Empty parameters collection
|
21
|
+
# empty_parameter_registry = ParameterRegistry.new
|
22
|
+
# ParametersInspector.call(empty_parameter_registry)
|
23
|
+
# # => ""
|
24
|
+
#
|
25
|
+
# @example Parameters with validation options
|
26
|
+
# parameter_registry = ParameterRegistry.new
|
27
|
+
# parameter_registry << Parameter.new(:age, klass: Task, type: :integer,
|
28
|
+
# numeric: { within: 18..120 }, required: true)
|
29
|
+
# parameter_registry << Parameter.new(:website, klass: Task, type: :string,
|
30
|
+
# format: { with: /^https?:\/\// }, required: false)
|
31
|
+
#
|
32
|
+
# ParametersInspector.call(parameter_registry)
|
33
|
+
# # => "Parameter: name=age type=integer source=context required=true options={numeric: {within: 18..120}}
|
34
|
+
# # Parameter: name=website type=string source=context required=false options={format: {with: /^https?:\/\//}}"
|
35
|
+
#
|
36
|
+
# @see CMDx::ParameterRegistry Parameter collection management
|
37
|
+
# @see CMDx::ParameterInspector Individual parameter inspection
|
38
|
+
# @see CMDx::Parameter Parameter definition and configuration
|
4
39
|
module ParametersInspector
|
5
40
|
|
6
41
|
module_function
|
7
42
|
|
43
|
+
# Converts a Parameters collection to a human-readable string representation.
|
44
|
+
#
|
45
|
+
# Iterates through all parameters in the collection and formats each one
|
46
|
+
# using ParameterInspector, then joins them with newlines to create a
|
47
|
+
# comprehensive multi-parameter description.
|
48
|
+
#
|
49
|
+
# @param parameters [CMDx::ParameterRegistry] The parameters collection to inspect
|
50
|
+
# @return [String] Multi-line formatted parameter descriptions
|
51
|
+
#
|
52
|
+
# @example Inspecting multiple parameters
|
53
|
+
# ParametersInspector.call(parameters_collection)
|
54
|
+
# # => "Parameter: name=user_id type=integer source=context required=true
|
55
|
+
# # Parameter: name=email type=string source=context required=false
|
56
|
+
# # Parameter: name=age type=integer source=context required=true"
|
57
|
+
#
|
58
|
+
# @example Inspecting empty collection
|
59
|
+
# ParametersInspector.call(ParameterRegistry.new)
|
60
|
+
# # => ""
|
61
|
+
#
|
62
|
+
# @example Inspecting single parameter collection
|
63
|
+
# single_param_collection = ParameterRegistry.new
|
64
|
+
# single_param_collection << Parameter.new(:name, klass: Task)
|
65
|
+
# ParametersInspector.call(single_param_collection)
|
66
|
+
# # => "Parameter: name=name type=virtual source=context required=false options={}"
|
8
67
|
def call(parameters)
|
9
68
|
parameters.map(&:to_s).join("\n")
|
10
69
|
end
|
@@ -1,10 +1,112 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
+
# Parameter collection serialization utility for converting Parameters to hash arrays.
|
5
|
+
#
|
6
|
+
# The ParametersSerializer module provides functionality to serialize collections
|
7
|
+
# of Parameter instances into structured array representations. Each parameter
|
8
|
+
# in the collection is converted to its hash representation, creating a
|
9
|
+
# comprehensive data structure suitable for inspection, logging, and data interchange.
|
10
|
+
#
|
11
|
+
# @example Basic parameters collection serialization
|
12
|
+
# parameter_registry = ParameterRegistry.new
|
13
|
+
# parameter_registry << Parameter.new(:user_id, klass: Task, type: :integer, required: true)
|
14
|
+
# parameters << Parameter.new(:email, klass: Task, type: :string, required: false)
|
15
|
+
#
|
16
|
+
# ParametersSerializer.call(parameters)
|
17
|
+
# # => [
|
18
|
+
# # {
|
19
|
+
# # source: :context,
|
20
|
+
# # name: :user_id,
|
21
|
+
# # type: :integer,
|
22
|
+
# # required: true,
|
23
|
+
# # options: {},
|
24
|
+
# # children: []
|
25
|
+
# # },
|
26
|
+
# # {
|
27
|
+
# # source: :context,
|
28
|
+
# # name: :email,
|
29
|
+
# # type: :string,
|
30
|
+
# # required: false,
|
31
|
+
# # options: {},
|
32
|
+
# # children: []
|
33
|
+
# # }
|
34
|
+
# # ]
|
35
|
+
#
|
36
|
+
# @example Empty parameters collection
|
37
|
+
# empty_parameter_registry = ParameterRegistry.new
|
38
|
+
# ParametersSerializer.call(empty_parameter_registry)
|
39
|
+
# # => []
|
40
|
+
#
|
41
|
+
# @example Parameters with validation and nested structures
|
42
|
+
# parameter_registry = ParameterRegistry.new
|
43
|
+
# parameter_registry << Parameter.new(:age, klass: Task, type: :integer,
|
44
|
+
# numeric: { within: 18..120 }, required: true)
|
45
|
+
#
|
46
|
+
# address_param = Parameter.new(:address, klass: Task) do
|
47
|
+
# required :street, :city
|
48
|
+
# optional :apartment
|
49
|
+
# end
|
50
|
+
# parameter_registry << address_param
|
51
|
+
#
|
52
|
+
# ParametersSerializer.call(parameter_registry)
|
53
|
+
# # => [
|
54
|
+
# # {
|
55
|
+
# # source: :context,
|
56
|
+
# # name: :age,
|
57
|
+
# # type: :integer,
|
58
|
+
# # required: true,
|
59
|
+
# # options: { numeric: { within: 18..120 } },
|
60
|
+
# # children: []
|
61
|
+
# # },
|
62
|
+
# # {
|
63
|
+
# # source: :context,
|
64
|
+
# # name: :address,
|
65
|
+
# # type: :virtual,
|
66
|
+
# # required: false,
|
67
|
+
# # options: {},
|
68
|
+
# # children: [
|
69
|
+
# # { source: :address, name: :street, type: :virtual, required: true, options: {}, children: [] },
|
70
|
+
# # { source: :address, name: :city, type: :virtual, required: true, options: {}, children: [] },
|
71
|
+
# # { source: :address, name: :apartment, type: :virtual, required: false, options: {}, children: [] }
|
72
|
+
# # ]
|
73
|
+
# # }
|
74
|
+
# # ]
|
75
|
+
#
|
76
|
+
# @see CMDx::ParameterRegistry Parameter collection management
|
77
|
+
# @see CMDx::ParameterSerializer Individual parameter serialization
|
78
|
+
# @see CMDx::Parameter Parameter definition and configuration
|
4
79
|
module ParametersSerializer
|
5
80
|
|
6
81
|
module_function
|
7
82
|
|
83
|
+
# Converts a Parameters collection to an array of hash representations.
|
84
|
+
#
|
85
|
+
# Iterates through all parameters in the collection and converts each one
|
86
|
+
# to its hash representation using the Parameter#to_h method, which delegates
|
87
|
+
# to ParameterSerializer.
|
88
|
+
#
|
89
|
+
# @param parameters [CMDx::ParameterRegistry] The parameters collection to serialize
|
90
|
+
# @return [Array<Hash>] Array of serialized parameter data structures
|
91
|
+
#
|
92
|
+
# @example Serializing multiple parameters
|
93
|
+
# ParametersSerializer.call(parameters_collection)
|
94
|
+
# # => [
|
95
|
+
# # { source: :context, name: :user_id, type: :integer, required: true, options: {}, children: [] },
|
96
|
+
# # { source: :context, name: :email, type: :string, required: false, options: {}, children: [] }
|
97
|
+
# # ]
|
98
|
+
#
|
99
|
+
# @example Serializing empty collection
|
100
|
+
# ParametersSerializer.call(ParameterRegistry.new)
|
101
|
+
# # => []
|
102
|
+
#
|
103
|
+
# @example Serializing single parameter collection
|
104
|
+
# single_param_collection = ParameterRegistry.new
|
105
|
+
# single_param_collection << Parameter.new(:name, klass: Task, type: :string)
|
106
|
+
# ParametersSerializer.call(single_param_collection)
|
107
|
+
# # => [
|
108
|
+
# # { source: :context, name: :name, type: :string, required: false, options: {}, children: [] }
|
109
|
+
# # ]
|
8
110
|
def call(parameters)
|
9
111
|
parameters.map(&:to_h)
|
10
112
|
end
|