cmdx 0.5.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 +31 -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 +203 -31
- data/docs/outcomes/statuses.md +275 -30
- 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 +367 -25
- 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 +405 -37
- 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 -62
- data/lib/cmdx/batch.rb +0 -43
- data/lib/cmdx/parameters.rb +0 -35
- data/lib/cmdx/run.rb +0 -39
- data/lib/cmdx/run_inspector.rb +0 -26
- data/lib/cmdx/run_serializer.rb +0 -20
- 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
@@ -2,19 +2,113 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Format validator for parameter validation using regular expressions.
|
6
|
+
#
|
7
|
+
# The Format validator validates parameter values against regular expression
|
8
|
+
# patterns. It supports both positive matching (with) and negative matching
|
9
|
+
# (without) patterns, and can combine both for complex format validation.
|
10
|
+
#
|
11
|
+
# @example Basic format validation with positive pattern
|
12
|
+
# class ProcessUserTask < CMDx::Task
|
13
|
+
# required :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
|
14
|
+
# required :phone, format: { with: /\A\d{3}-\d{3}-\d{4}\z/ }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Format validation with negative pattern
|
18
|
+
# class ProcessContentTask < CMDx::Task
|
19
|
+
# required :username, format: { without: /\A(admin|root|system)\z/i }
|
20
|
+
# required :content, format: { without: /spam|viagra/i }
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Combined positive and negative patterns
|
24
|
+
# class ProcessUserTask < CMDx::Task
|
25
|
+
# required :password, format: {
|
26
|
+
# with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/, # Strong password
|
27
|
+
# without: /password|123456/i # Common weak patterns
|
28
|
+
# }
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example Custom error message
|
32
|
+
# class ProcessUserTask < CMDx::Task
|
33
|
+
# required :email, format: {
|
34
|
+
# with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
|
35
|
+
# message: "must be a valid email address"
|
36
|
+
# }
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @example Format validation behavior
|
40
|
+
# # Positive pattern matching
|
41
|
+
# Format.call("user@example.com", format: { with: /@/ }) # passes
|
42
|
+
# Format.call("invalid-email", format: { with: /@/ }) # raises ValidationError
|
43
|
+
#
|
44
|
+
# # Negative pattern matching
|
45
|
+
# Format.call("username", format: { without: /admin/ }) # passes
|
46
|
+
# Format.call("admin", format: { without: /admin/ }) # raises ValidationError
|
47
|
+
#
|
48
|
+
# @see CMDx::Parameter Parameter validation integration
|
49
|
+
# @see CMDx::ValidationError Raised when validation fails
|
5
50
|
module Format
|
6
51
|
|
7
52
|
module_function
|
8
53
|
|
54
|
+
# Validates that a parameter value matches the specified format patterns.
|
55
|
+
#
|
56
|
+
# Validates the value against the provided regular expression patterns.
|
57
|
+
# Supports positive matching (with), negative matching (without), or both.
|
58
|
+
# The value must match all specified conditions to pass validation.
|
59
|
+
#
|
60
|
+
# @param value [String] The parameter value to validate
|
61
|
+
# @param options [Hash] Validation configuration options
|
62
|
+
# @option options [Hash] :format Format validation configuration
|
63
|
+
# @option options [Regexp] :format.with Pattern the value must match
|
64
|
+
# @option options [Regexp] :format.without Pattern the value must not match
|
65
|
+
# @option options [String] :format.message Custom error message
|
66
|
+
#
|
67
|
+
# @return [void]
|
68
|
+
# @raise [ValidationError] If value doesn't match the format requirements
|
69
|
+
#
|
70
|
+
# @example Successful positive pattern validation
|
71
|
+
# Format.call("user@example.com", format: { with: /@/ })
|
72
|
+
# # => passes without error
|
73
|
+
#
|
74
|
+
# @example Failed positive pattern validation
|
75
|
+
# Format.call("invalid-email", format: { with: /@/ })
|
76
|
+
# # => raises ValidationError: "is an invalid format"
|
77
|
+
#
|
78
|
+
# @example Successful negative pattern validation
|
79
|
+
# Format.call("username", format: { without: /admin/ })
|
80
|
+
# # => passes without error
|
81
|
+
#
|
82
|
+
# @example Failed negative pattern validation
|
83
|
+
# Format.call("admin", format: { without: /admin/ })
|
84
|
+
# # => raises ValidationError: "is an invalid format"
|
85
|
+
#
|
86
|
+
# @example Combined pattern validation
|
87
|
+
# Format.call("StrongPass123", format: {
|
88
|
+
# with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/,
|
89
|
+
# without: /password/i
|
90
|
+
# })
|
91
|
+
# # => passes without error
|
92
|
+
#
|
93
|
+
# @example Custom error message
|
94
|
+
# Format.call("weak", format: {
|
95
|
+
# with: /\A.{8,}\z/,
|
96
|
+
# message: "must be at least 8 characters"
|
97
|
+
# })
|
98
|
+
# # => raises ValidationError: "must be at least 8 characters"
|
9
99
|
def call(value, options = {})
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
100
|
+
valid = case options[:format]
|
101
|
+
in { with: with, without: without }
|
102
|
+
value.match?(with) && !value.match?(without)
|
103
|
+
in { with: with }
|
104
|
+
value.match?(with)
|
105
|
+
in { without: without }
|
106
|
+
!value.match?(without)
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
|
111
|
+
return if valid
|
18
112
|
|
19
113
|
raise ValidationError, options.dig(:format, :message) || I18n.t(
|
20
114
|
"cmdx.validators.format",
|
@@ -2,10 +2,103 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Inclusion validator for parameter validation against allowed values.
|
6
|
+
#
|
7
|
+
# The Inclusion validator ensures that parameter values ARE within a
|
8
|
+
# specified set of allowed values. It supports both array-based inclusion
|
9
|
+
# (specific values) and range-based inclusion (value ranges).
|
10
|
+
#
|
11
|
+
# @example Basic inclusion validation with array
|
12
|
+
# class ProcessOrderTask < CMDx::Task
|
13
|
+
# required :status, inclusion: { in: ['pending', 'processing', 'completed'] }
|
14
|
+
# required :priority, inclusion: { in: [1, 2, 3, 4, 5] }
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Range-based inclusion
|
18
|
+
# class ProcessUserTask < CMDx::Task
|
19
|
+
# required :age, inclusion: { in: 18..120 } # Valid age range
|
20
|
+
# required :score, inclusion: { within: 0..100 } # Percentage score
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @example Custom error messages
|
24
|
+
# class ProcessOrderTask < CMDx::Task
|
25
|
+
# required :status, inclusion: {
|
26
|
+
# in: ['pending', 'processing', 'completed'],
|
27
|
+
# of_message: "must be a valid order status"
|
28
|
+
# }
|
29
|
+
# required :age, inclusion: {
|
30
|
+
# in: 18..120,
|
31
|
+
# in_message: "must be between %{min} and %{max} years old"
|
32
|
+
# }
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @example Boolean field validation
|
36
|
+
# class ProcessUserTask < CMDx::Task
|
37
|
+
# required :active, inclusion: { in: [true, false] } # Proper boolean validation
|
38
|
+
# required :role, inclusion: { in: ['admin', 'user', 'guest'] }
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @example Inclusion validation behavior
|
42
|
+
# # Array inclusion
|
43
|
+
# Inclusion.call("pending", inclusion: { in: ['pending', 'active'] }) # passes
|
44
|
+
# Inclusion.call("cancelled", inclusion: { in: ['pending', 'active'] }) # raises ValidationError
|
45
|
+
#
|
46
|
+
# # Range inclusion
|
47
|
+
# Inclusion.call(25, inclusion: { in: 18..65 }) # passes
|
48
|
+
# Inclusion.call(15, inclusion: { in: 18..65 }) # raises ValidationError
|
49
|
+
#
|
50
|
+
# @see CMDx::Validators::Exclusion For validating values must not be in a set
|
51
|
+
# @see CMDx::Parameter Parameter validation integration
|
52
|
+
# @see CMDx::ValidationError Raised when validation fails
|
5
53
|
module Inclusion
|
6
54
|
|
7
55
|
extend self
|
8
56
|
|
57
|
+
# Validates that a parameter value is in the allowed set.
|
58
|
+
#
|
59
|
+
# Checks that the value is present in the specified array or range
|
60
|
+
# of allowed values. Raises ValidationError if the value is not found
|
61
|
+
# in the inclusion set.
|
62
|
+
#
|
63
|
+
# @param value [Object] The parameter value to validate
|
64
|
+
# @param options [Hash] Validation configuration options
|
65
|
+
# @option options [Hash] :inclusion Inclusion validation configuration
|
66
|
+
# @option options [Array, Range] :inclusion.in Values/range to include
|
67
|
+
# @option options [Array, Range] :inclusion.within Alias for :in
|
68
|
+
# @option options [String] :inclusion.of_message Error message for array inclusion
|
69
|
+
# @option options [String] :inclusion.in_message Error message for range inclusion
|
70
|
+
# @option options [String] :inclusion.within_message Alias for :in_message
|
71
|
+
# @option options [String] :inclusion.message General error message override
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
# @raise [ValidationError] If value is not found in the inclusion set
|
75
|
+
#
|
76
|
+
# @example Array inclusion validation
|
77
|
+
# Inclusion.call("active", inclusion: { in: ['active', 'pending'] })
|
78
|
+
# # => passes without error
|
79
|
+
#
|
80
|
+
# @example Failed array inclusion
|
81
|
+
# Inclusion.call("cancelled", inclusion: { in: ['active', 'pending'] })
|
82
|
+
# # => raises ValidationError: "must be one of: \"active\", \"pending\""
|
83
|
+
#
|
84
|
+
# @example Range inclusion validation
|
85
|
+
# Inclusion.call(25, inclusion: { in: 18..65 })
|
86
|
+
# # => passes without error
|
87
|
+
#
|
88
|
+
# @example Failed range inclusion
|
89
|
+
# Inclusion.call(15, inclusion: { in: 18..65 })
|
90
|
+
# # => raises ValidationError: "must be within 18 and 65"
|
91
|
+
#
|
92
|
+
# @example Boolean validation
|
93
|
+
# Inclusion.call(true, inclusion: { in: [true, false] })
|
94
|
+
# # => passes without error
|
95
|
+
#
|
96
|
+
# @example Custom error messages
|
97
|
+
# Inclusion.call("invalid", inclusion: {
|
98
|
+
# in: ['valid', 'pending'],
|
99
|
+
# of_message: "status must be valid or pending"
|
100
|
+
# })
|
101
|
+
# # => raises ValidationError: "status must be valid or pending"
|
9
102
|
def call(value, options = {})
|
10
103
|
values = options.dig(:inclusion, :in) ||
|
11
104
|
options.dig(:inclusion, :within)
|
@@ -19,6 +112,11 @@ module CMDx
|
|
19
112
|
|
20
113
|
private
|
21
114
|
|
115
|
+
# Raises validation error for array-based inclusion violations.
|
116
|
+
#
|
117
|
+
# @param values [Array] The allowed values array
|
118
|
+
# @param options [Hash] Validation options containing error messages
|
119
|
+
# @raise [ValidationError] With formatted error message
|
22
120
|
def raise_of_validation_error!(values, options)
|
23
121
|
values = values.map(&:inspect).join(", ")
|
24
122
|
message = options.dig(:inclusion, :of_message) ||
|
@@ -32,6 +130,12 @@ module CMDx
|
|
32
130
|
)
|
33
131
|
end
|
34
132
|
|
133
|
+
# Raises validation error for range-based inclusion violations.
|
134
|
+
#
|
135
|
+
# @param min [Object] Range minimum value
|
136
|
+
# @param max [Object] Range maximum value
|
137
|
+
# @param options [Hash] Validation options containing error messages
|
138
|
+
# @raise [ValidationError] With formatted error message
|
35
139
|
def raise_within_validation_error!(min, max, options)
|
36
140
|
message = options.dig(:inclusion, :in_message) ||
|
37
141
|
options.dig(:inclusion, :within_message) ||
|
@@ -2,10 +2,106 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Length validator for parameter validation based on size constraints.
|
6
|
+
#
|
7
|
+
# The Length validator validates the length/size of parameter values such as
|
8
|
+
# strings, arrays, and other objects that respond to #length. It supports
|
9
|
+
# various constraint types including ranges, boundaries, and exact lengths.
|
10
|
+
#
|
11
|
+
# @example Range-based length validation
|
12
|
+
# class ProcessUserTask < CMDx::Task
|
13
|
+
# required :username, length: { within: 3..20 }
|
14
|
+
# required :password, length: { in: 8..128 }
|
15
|
+
# required :bio, length: { not_within: 500..1000 } # Avoid medium length
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Boundary length validation
|
19
|
+
# class ProcessContentTask < CMDx::Task
|
20
|
+
# required :title, length: { min: 5 }
|
21
|
+
# required :description, length: { max: 500 }
|
22
|
+
# required :slug, length: { min: 3, max: 50 } # Combined min/max
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example Exact length validation
|
26
|
+
# class ProcessCodeTask < CMDx::Task
|
27
|
+
# required :country_code, length: { is: 2 } # ISO country codes
|
28
|
+
# required :postal_code, length: { is_not: 4 } # Avoid 4-digit codes
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example Custom error messages
|
32
|
+
# class ProcessUserTask < CMDx::Task
|
33
|
+
# required :username, length: {
|
34
|
+
# within: 3..20,
|
35
|
+
# within_message: "must be between %{min} and %{max} characters"
|
36
|
+
# }
|
37
|
+
# required :password, length: {
|
38
|
+
# min: 8,
|
39
|
+
# min_message: "must be at least %{min} characters for security"
|
40
|
+
# }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# @example Length validation behavior
|
44
|
+
# # String length validation
|
45
|
+
# Length.call("hello", length: { min: 3 }) # passes (length: 5)
|
46
|
+
# Length.call("hi", length: { min: 3 }) # raises ValidationError
|
47
|
+
#
|
48
|
+
# # Array length validation
|
49
|
+
# Length.call([1, 2, 3], length: { is: 3 }) # passes
|
50
|
+
# Length.call([1, 2], length: { is: 3 }) # raises ValidationError
|
51
|
+
#
|
52
|
+
# @see CMDx::Validators::Numeric For numeric value validation
|
53
|
+
# @see CMDx::Parameter Parameter validation integration
|
54
|
+
# @see CMDx::ValidationError Raised when validation fails
|
5
55
|
module Length
|
6
56
|
|
7
57
|
extend self
|
8
58
|
|
59
|
+
# Validates that a parameter value meets the specified length constraints.
|
60
|
+
#
|
61
|
+
# Validates the length of the value using the specified constraint type.
|
62
|
+
# Only one constraint option can be used at a time, except for :min and :max
|
63
|
+
# which can be combined together.
|
64
|
+
#
|
65
|
+
# @param value [#length] The parameter value to validate (must respond to #length)
|
66
|
+
# @param options [Hash] Validation configuration options
|
67
|
+
# @option options [Hash] :length Length validation configuration
|
68
|
+
# @option options [Range] :length.within Allowed length range
|
69
|
+
# @option options [Range] :length.not_within Forbidden length range
|
70
|
+
# @option options [Range] :length.in Alias for :within
|
71
|
+
# @option options [Range] :length.not_in Alias for :not_within
|
72
|
+
# @option options [Integer] :length.min Minimum allowed length
|
73
|
+
# @option options [Integer] :length.max Maximum allowed length
|
74
|
+
# @option options [Integer] :length.is Exact required length
|
75
|
+
# @option options [Integer] :length.is_not Forbidden exact length
|
76
|
+
# @option options [String] :length.*_message Custom error messages for each constraint
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
# @raise [ValidationError] If value doesn't meet the length constraints
|
80
|
+
# @raise [ArgumentError] If no valid length constraint options are provided
|
81
|
+
#
|
82
|
+
# @example Range validation
|
83
|
+
# Length.call("hello", length: { within: 3..10 })
|
84
|
+
# # => passes without error
|
85
|
+
#
|
86
|
+
# @example Failed range validation
|
87
|
+
# Length.call("hi", length: { within: 3..10 })
|
88
|
+
# # => raises ValidationError: "length must be within 3 and 10"
|
89
|
+
#
|
90
|
+
# @example Minimum length validation
|
91
|
+
# Length.call("password123", length: { min: 8 })
|
92
|
+
# # => passes without error
|
93
|
+
#
|
94
|
+
# @example Combined min/max validation
|
95
|
+
# Length.call("username", length: { min: 3, max: 20 })
|
96
|
+
# # => passes without error
|
97
|
+
#
|
98
|
+
# @example Exact length validation
|
99
|
+
# Length.call("US", length: { is: 2 })
|
100
|
+
# # => passes without error (country code)
|
101
|
+
#
|
102
|
+
# @example Array length validation
|
103
|
+
# Length.call([1, 2, 3, 4], length: { max: 5 })
|
104
|
+
# # => passes without error
|
9
105
|
def call(value, options = {})
|
10
106
|
case options[:length]
|
11
107
|
in { within: within }
|
@@ -33,6 +129,12 @@ module CMDx
|
|
33
129
|
|
34
130
|
private
|
35
131
|
|
132
|
+
# Raises validation error for range-based length violations.
|
133
|
+
#
|
134
|
+
# @param min [Integer] Range minimum length
|
135
|
+
# @param max [Integer] Range maximum length
|
136
|
+
# @param options [Hash] Validation options containing error messages
|
137
|
+
# @raise [ValidationError] With formatted error message
|
36
138
|
def raise_within_validation_error!(min, max, options)
|
37
139
|
message = options.dig(:length, :within_message) ||
|
38
140
|
options.dig(:length, :in_message) ||
|
@@ -47,6 +149,12 @@ module CMDx
|
|
47
149
|
)
|
48
150
|
end
|
49
151
|
|
152
|
+
# Raises validation error for forbidden range violations.
|
153
|
+
#
|
154
|
+
# @param min [Integer] Range minimum length
|
155
|
+
# @param max [Integer] Range maximum length
|
156
|
+
# @param options [Hash] Validation options containing error messages
|
157
|
+
# @raise [ValidationError] With formatted error message
|
50
158
|
def raise_not_within_validation_error!(min, max, options)
|
51
159
|
message = options.dig(:length, :not_within_message) ||
|
52
160
|
options.dig(:length, :not_in_message) ||
|
@@ -61,6 +169,11 @@ module CMDx
|
|
61
169
|
)
|
62
170
|
end
|
63
171
|
|
172
|
+
# Raises validation error for minimum length violations.
|
173
|
+
#
|
174
|
+
# @param min [Integer] Minimum required length
|
175
|
+
# @param options [Hash] Validation options containing error messages
|
176
|
+
# @raise [ValidationError] With formatted error message
|
64
177
|
def raise_min_validation_error!(min, options)
|
65
178
|
message = options.dig(:length, :min_message) ||
|
66
179
|
options.dig(:length, :message)
|
@@ -73,6 +186,11 @@ module CMDx
|
|
73
186
|
)
|
74
187
|
end
|
75
188
|
|
189
|
+
# Raises validation error for maximum length violations.
|
190
|
+
#
|
191
|
+
# @param max [Integer] Maximum allowed length
|
192
|
+
# @param options [Hash] Validation options containing error messages
|
193
|
+
# @raise [ValidationError] With formatted error message
|
76
194
|
def raise_max_validation_error!(max, options)
|
77
195
|
message = options.dig(:length, :max_message) ||
|
78
196
|
options.dig(:length, :message)
|
@@ -85,6 +203,11 @@ module CMDx
|
|
85
203
|
)
|
86
204
|
end
|
87
205
|
|
206
|
+
# Raises validation error for exact length violations.
|
207
|
+
#
|
208
|
+
# @param is [Integer] Required exact length
|
209
|
+
# @param options [Hash] Validation options containing error messages
|
210
|
+
# @raise [ValidationError] With formatted error message
|
88
211
|
def raise_is_validation_error!(is, options)
|
89
212
|
message = options.dig(:length, :is_message) ||
|
90
213
|
options.dig(:length, :message)
|
@@ -97,6 +220,11 @@ module CMDx
|
|
97
220
|
)
|
98
221
|
end
|
99
222
|
|
223
|
+
# Raises validation error for forbidden exact length violations.
|
224
|
+
#
|
225
|
+
# @param is_not [Integer] Forbidden exact length
|
226
|
+
# @param options [Hash] Validation options containing error messages
|
227
|
+
# @raise [ValidationError] With formatted error message
|
100
228
|
def raise_is_not_validation_error!(is_not, options)
|
101
229
|
message = options.dig(:length, :is_not_message) ||
|
102
230
|
options.dig(:length, :message)
|
@@ -2,10 +2,106 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Validators
|
5
|
+
# Numeric validator for parameter validation based on numeric value constraints.
|
6
|
+
#
|
7
|
+
# The Numeric validator validates numeric parameter values against various
|
8
|
+
# constraints including ranges, boundaries, and exact values. It works with
|
9
|
+
# any numeric type including integers, floats, decimals, and other numeric objects.
|
10
|
+
#
|
11
|
+
# @example Range-based numeric validation
|
12
|
+
# class ProcessOrderTask < CMDx::Task
|
13
|
+
# required :quantity, numeric: { within: 1..100 }
|
14
|
+
# required :price, numeric: { in: 0.01..999.99 }
|
15
|
+
# required :discount, numeric: { not_within: 90..100 } # Avoid excessive discounts
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Boundary numeric validation
|
19
|
+
# class ProcessUserTask < CMDx::Task
|
20
|
+
# required :age, numeric: { min: 18 }
|
21
|
+
# required :score, numeric: { max: 100 }
|
22
|
+
# required :rating, numeric: { min: 1, max: 5 } # Combined min/max
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# @example Exact numeric validation
|
26
|
+
# class ProcessConfigTask < CMDx::Task
|
27
|
+
# required :version, numeric: { is: 2 } # Specific version required
|
28
|
+
# required :legacy_flag, numeric: { is_not: 0 } # Must not be zero
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example Custom error messages
|
32
|
+
# class ProcessOrderTask < CMDx::Task
|
33
|
+
# required :quantity, numeric: {
|
34
|
+
# within: 1..100,
|
35
|
+
# within_message: "must be between %{min} and %{max} items"
|
36
|
+
# }
|
37
|
+
# required :age, numeric: {
|
38
|
+
# min: 18,
|
39
|
+
# min_message: "must be at least %{min} years old"
|
40
|
+
# }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# @example Numeric validation behavior
|
44
|
+
# # Integer validation
|
45
|
+
# Numeric.call(25, numeric: { min: 18 }) # passes
|
46
|
+
# Numeric.call(15, numeric: { min: 18 }) # raises ValidationError
|
47
|
+
#
|
48
|
+
# # Float validation
|
49
|
+
# Numeric.call(99.99, numeric: { max: 100.0 }) # passes
|
50
|
+
# Numeric.call(101.5, numeric: { max: 100.0 }) # raises ValidationError
|
51
|
+
#
|
52
|
+
# @see CMDx::Validators::Length For length/size validation
|
53
|
+
# @see CMDx::Parameter Parameter validation integration
|
54
|
+
# @see CMDx::ValidationError Raised when validation fails
|
5
55
|
module Numeric
|
6
56
|
|
7
57
|
extend self
|
8
58
|
|
59
|
+
# Validates that a parameter value meets the specified numeric constraints.
|
60
|
+
#
|
61
|
+
# Validates the numeric value using the specified constraint type.
|
62
|
+
# Only one constraint option can be used at a time, except for :min and :max
|
63
|
+
# which can be combined together.
|
64
|
+
#
|
65
|
+
# @param value [Numeric] The parameter value to validate (must be numeric)
|
66
|
+
# @param options [Hash] Validation configuration options
|
67
|
+
# @option options [Hash] :numeric Numeric validation configuration
|
68
|
+
# @option options [Range] :numeric.within Allowed value range
|
69
|
+
# @option options [Range] :numeric.not_within Forbidden value range
|
70
|
+
# @option options [Range] :numeric.in Alias for :within
|
71
|
+
# @option options [Range] :numeric.not_in Alias for :not_within
|
72
|
+
# @option options [Numeric] :numeric.min Minimum allowed value
|
73
|
+
# @option options [Numeric] :numeric.max Maximum allowed value
|
74
|
+
# @option options [Numeric] :numeric.is Exact required value
|
75
|
+
# @option options [Numeric] :numeric.is_not Forbidden exact value
|
76
|
+
# @option options [String] :numeric.*_message Custom error messages for each constraint
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
# @raise [ValidationError] If value doesn't meet the numeric constraints
|
80
|
+
# @raise [ArgumentError] If no valid numeric constraint options are provided
|
81
|
+
#
|
82
|
+
# @example Range validation
|
83
|
+
# Numeric.call(50, numeric: { within: 1..100 })
|
84
|
+
# # => passes without error
|
85
|
+
#
|
86
|
+
# @example Failed range validation
|
87
|
+
# Numeric.call(150, numeric: { within: 1..100 })
|
88
|
+
# # => raises ValidationError: "must be within 1 and 100"
|
89
|
+
#
|
90
|
+
# @example Minimum value validation
|
91
|
+
# Numeric.call(25, numeric: { min: 18 })
|
92
|
+
# # => passes without error
|
93
|
+
#
|
94
|
+
# @example Combined min/max validation
|
95
|
+
# Numeric.call(3.5, numeric: { min: 1.0, max: 5.0 })
|
96
|
+
# # => passes without error
|
97
|
+
#
|
98
|
+
# @example Exact value validation
|
99
|
+
# Numeric.call(42, numeric: { is: 42 })
|
100
|
+
# # => passes without error
|
101
|
+
#
|
102
|
+
# @example Float validation
|
103
|
+
# Numeric.call(19.99, numeric: { max: 20.0 })
|
104
|
+
# # => passes without error
|
9
105
|
def call(value, options = {})
|
10
106
|
case options[:numeric]
|
11
107
|
in { within: within }
|
@@ -33,6 +129,12 @@ module CMDx
|
|
33
129
|
|
34
130
|
private
|
35
131
|
|
132
|
+
# Raises validation error for range-based numeric violations.
|
133
|
+
#
|
134
|
+
# @param min [Numeric] Range minimum value
|
135
|
+
# @param max [Numeric] Range maximum value
|
136
|
+
# @param options [Hash] Validation options containing error messages
|
137
|
+
# @raise [ValidationError] With formatted error message
|
36
138
|
def raise_within_validation_error!(min, max, options)
|
37
139
|
message = options.dig(:numeric, :within_message) ||
|
38
140
|
options.dig(:numeric, :in_message) ||
|
@@ -47,6 +149,12 @@ module CMDx
|
|
47
149
|
)
|
48
150
|
end
|
49
151
|
|
152
|
+
# Raises validation error for forbidden range violations.
|
153
|
+
#
|
154
|
+
# @param min [Numeric] Range minimum value
|
155
|
+
# @param max [Numeric] Range maximum value
|
156
|
+
# @param options [Hash] Validation options containing error messages
|
157
|
+
# @raise [ValidationError] With formatted error message
|
50
158
|
def raise_not_within_validation_error!(min, max, options)
|
51
159
|
message = options.dig(:numeric, :not_within_message) ||
|
52
160
|
options.dig(:numeric, :not_in_message) ||
|
@@ -61,6 +169,11 @@ module CMDx
|
|
61
169
|
)
|
62
170
|
end
|
63
171
|
|
172
|
+
# Raises validation error for minimum value violations.
|
173
|
+
#
|
174
|
+
# @param min [Numeric] Minimum required value
|
175
|
+
# @param options [Hash] Validation options containing error messages
|
176
|
+
# @raise [ValidationError] With formatted error message
|
64
177
|
def raise_min_validation_error!(min, options)
|
65
178
|
message = options.dig(:numeric, :min_message) ||
|
66
179
|
options.dig(:numeric, :message)
|
@@ -73,6 +186,11 @@ module CMDx
|
|
73
186
|
)
|
74
187
|
end
|
75
188
|
|
189
|
+
# Raises validation error for maximum value violations.
|
190
|
+
#
|
191
|
+
# @param max [Numeric] Maximum allowed value
|
192
|
+
# @param options [Hash] Validation options containing error messages
|
193
|
+
# @raise [ValidationError] With formatted error message
|
76
194
|
def raise_max_validation_error!(max, options)
|
77
195
|
message = options.dig(:numeric, :max_message) ||
|
78
196
|
options.dig(:numeric, :message)
|
@@ -85,6 +203,11 @@ module CMDx
|
|
85
203
|
)
|
86
204
|
end
|
87
205
|
|
206
|
+
# Raises validation error for exact value violations.
|
207
|
+
#
|
208
|
+
# @param is [Numeric] Required exact value
|
209
|
+
# @param options [Hash] Validation options containing error messages
|
210
|
+
# @raise [ValidationError] With formatted error message
|
88
211
|
def raise_is_validation_error!(is, options)
|
89
212
|
message = options.dig(:numeric, :is_message) ||
|
90
213
|
options.dig(:numeric, :message)
|
@@ -97,6 +220,11 @@ module CMDx
|
|
97
220
|
)
|
98
221
|
end
|
99
222
|
|
223
|
+
# Raises validation error for forbidden exact value violations.
|
224
|
+
#
|
225
|
+
# @param is_not [Numeric] Forbidden exact value
|
226
|
+
# @param options [Hash] Validation options containing error messages
|
227
|
+
# @raise [ValidationError] With formatted error message
|
100
228
|
def raise_is_not_validation_error!(is_not, options)
|
101
229
|
message = options.dig(:numeric, :is_not_message) ||
|
102
230
|
options.dig(:numeric, :message)
|