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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +31 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +203 -31
  23. data/docs/outcomes/statuses.md +275 -30
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +367 -25
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +405 -37
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -62
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -35
  121. data/lib/cmdx/run.rb +0 -39
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -20
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,312 +1,337 @@
1
1
  # Parameters - Validations
2
2
 
3
- Parameter values can be validated using one of the built-in validators. Build custom validators
4
- to check parameter values against your own business logic. `i18n` internalization is supported
5
- out of the box.
3
+ Parameter values can be validated using built-in validators or custom validation logic. All validators support internationalization (i18n) and integrate seamlessly with CMDx's error handling system.
6
4
 
7
- Built-in validators are: [Presence](#presence), [Format](#format), [Exclusion](#exclusion),
8
- [Inclusion](#inclusion), [Length](#length), [Numeric](#numeric), [Custom](#custom)
5
+ ## Table of Contents
9
6
 
10
- All validators support the following common options:
7
+ - [Common Options](#common-options)
8
+ - [Presence](#presence)
9
+ - [Format](#format)
10
+ - [Exclusion](#exclusion)
11
+ - [Inclusion](#inclusion)
12
+ - [Length](#length)
13
+ - [Numeric](#numeric)
14
+ - [Custom](#custom)
15
+ - [Validation Results](#validation-results)
16
+ - [Internationalization (i18n)](#internationalization-i18n)
17
+
18
+ ## Common Options
19
+
20
+ All validators support these common options:
11
21
 
12
22
  | Option | Description |
13
23
  | ------------ | ----------- |
14
- | `:allow_nil` | Skip validation if the parameter value is `nil`. |
15
- | `:if` | Specifies a callable method, proc or string to determine if the validation should occur. |
16
- | `:unless` | Specifies a callable method, proc, or string to determine if the validation should not occur. |
17
- | `:message` | The error message to use for a violation. Fallback for any error key below that's not provided. |
24
+ | `:allow_nil` | Skip validation if the parameter value is `nil` |
25
+ | `:if` | Callable method, proc or string to determine if validation should occur |
26
+ | `:unless` | Callable method, proc, or string to determine if validation should not occur |
27
+ | `:message` | Error message for violations. Fallback for specific error keys not provided |
18
28
 
19
29
  > [!NOTE]
20
- > Validators on `optional` parameters will only be executed if they are supplied as
21
- > call arguments.
30
+ > Validators on `optional` parameters only execute when arguments are supplied.
22
31
 
23
32
  ## Presence
24
33
 
25
- Validates that the specified parameter value is not empty. If you want to validate the
26
- presence of a boolean field (where the real values are true and false), you will
27
- want to use `inclusion: { in: [true, false] }`.
34
+ Validates that parameter values are not empty using intelligent type checking:
35
+ - **Strings**: Must contain non-whitespace characters
36
+ - **Collections**: Must not be empty (arrays, hashes, etc.)
37
+ - **Other objects**: Must not be `nil`
28
38
 
29
- ```ruby
30
- class UpdateUserDetailsTask < CMDx::Task
39
+ > [!TIP]
40
+ > For boolean fields where valid values are `true` and `false`, use `inclusion: { in: [true, false] }` instead of presence validation.
31
41
 
32
- # Boolean
33
- required :username, presence: true
34
-
35
- # With custom error message
36
- optional :email, presence: { message: "must not be empty" }
42
+ ```ruby
43
+ class CreateUserTask < CMDx::Task
44
+ required :email, presence: true
45
+ optional :phone, presence: { message: "cannot be blank" }
46
+ required :active, inclusion: { in: [true, false] }
37
47
 
38
48
  def call
39
- # Do work...
49
+ User.create!(email: email, phone: phone, active: active)
40
50
  end
41
-
42
51
  end
43
52
  ```
44
53
 
45
54
  ## Format
46
55
 
47
- Validates whether the specified parameter value is of the correct form,
48
- going by the regular expression provided.
56
+ Validates parameter values against regular expression patterns. Supports positive matching (`with`), negative matching (`without`), or both.
49
57
 
50
58
  ```ruby
51
- class UpdateUserDetailsTask < CMDx::Task
52
-
53
- # With
54
- required :username, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
55
-
56
- # Without (with proc if conditional)
57
- optional :email, format: { without: /NOSPAM/, if: proc { Current.account.spam_check? } }
59
+ class RegisterUserTask < CMDx::Task
60
+ required :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
61
+ required :username, format: { without: /\A(admin|root|system)\z/i }
62
+ optional :password, format: {
63
+ with: /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/,
64
+ without: /password|123456/i,
65
+ if: :strong_password_required?
66
+ }
58
67
 
59
68
  def call
60
- # Do work...
69
+ create_user_account
61
70
  end
62
71
 
72
+ private
73
+
74
+ def strong_password_required?
75
+ context.account.security_policy.strong_passwords?
76
+ end
63
77
  end
64
78
  ```
65
79
 
66
- Constraint options:
80
+ **Options:**
67
81
 
68
82
  | Option | Description |
69
83
  | ---------- | ----------- |
70
- | `:with` | Regular expression that if the parameter value matches will result in a successful validation. |
71
- | `:without` | Regular expression that if the parameter value does not match will result in a successful validation. |
84
+ | `:with` | Regular expression that value must match |
85
+ | `:without` | Regular expression that value must not match |
72
86
 
73
87
  ## Exclusion
74
88
 
75
- Validates that the specified parameter is not in a particular enumerable object.
89
+ Validates that parameter values are not in a specific enumerable (array, range, etc.).
76
90
 
77
91
  ```ruby
78
- class DetermineBoxSizeTask < CMDx::Task
79
-
80
- # Array
81
- required :width, exclusion: { in: [12, 24, 36] }
82
-
83
- # Range (with allow nil)
84
- optional :length, exclusion: { in: 12..36, allow_nil: true }
92
+ class ProcessPaymentTask < CMDx::Task
93
+ required :payment_method, exclusion: { in: %w[cash check] }
94
+ required :amount, exclusion: { in: 0.0..0.99, in_message: "must be at least $1.00" }
95
+ optional :discount_percent, exclusion: { in: 90..100 }
85
96
 
86
97
  def call
87
- # Do work...
98
+ charge_payment
88
99
  end
89
-
90
100
  end
91
101
  ```
92
102
 
93
- Constraint options:
103
+ **Options:**
94
104
 
95
105
  | Option | Description |
96
106
  | ------------ | ----------- |
97
- | `:in` | An enumerable object of unavailable items such as an array or range. |
98
- | `:within` | A synonym (or alias) for `:in` |
107
+ | `:in` | Enumerable of forbidden values |
108
+ | `:within` | Alias for `:in` |
99
109
 
100
- Other options:
110
+ **Error Messages:**
101
111
 
102
112
  | Option | Description |
103
113
  | ----------------- | ----------- |
104
- | `:of_message` | The error message if the parameter value is in array. (default: "must not be one of: %{values}") |
105
- | `:in_message` | The error message if the parameter value is in range. (default: "must not be within %{min} and %{max}") |
106
- | `:within_message` | A synonym (or alias) for `:in_message` |
114
+ | `:of_message` | Error when value is in array (default: "must not be one of: %{values}") |
115
+ | `:in_message` | Error when value is in range (default: "must not be within %{min} and %{max}") |
116
+ | `:within_message` | Alias for `:in_message` |
107
117
 
108
118
  ## Inclusion
109
119
 
110
- Validates that the specified parameter value is in a particular enumerable object.
120
+ Validates that parameter values are in a specific enumerable (array, range, etc.).
111
121
 
112
122
  ```ruby
113
- class DetermineBoxSizeTask < CMDx::Task
114
-
115
- # Array
116
- required :width, inclusion: { in: [12, 24, 36] }
117
-
118
- # Range (with custom error message)
119
- optional :length, inclusion: { in: 12..36, unless: :length_check? }
123
+ class UpdateOrderTask < CMDx::Task
124
+ required :status, inclusion: { in: %w[pending processing shipped delivered] }
125
+ required :priority, inclusion: { in: 1..5 }
126
+ optional :shipping_method, inclusion: {
127
+ in: %w[standard express overnight],
128
+ unless: :digital_order?
129
+ }
120
130
 
121
131
  def call
122
- # Do work...
132
+ update_order_status
123
133
  end
124
134
 
125
135
  private
126
136
 
127
- def skip_length_check?
128
- false
137
+ def digital_order?
138
+ context.order.digital_items_only?
129
139
  end
130
-
131
140
  end
132
141
  ```
133
142
 
134
- Constraint options:
143
+ **Options:**
135
144
 
136
145
  | Option | Description |
137
146
  | ------------ | ----------- |
138
- | `:in` | An enumerable object of available items such as an array or range. |
139
- | `:within` | A synonym (or alias) for `:in` |
147
+ | `:in` | Enumerable of allowed values |
148
+ | `:within` | Alias for `:in` |
140
149
 
141
- Other options:
150
+ **Error Messages:**
142
151
 
143
152
  | Option | Description |
144
153
  | ----------------- | ----------- |
145
- | `:of_message` | The error message if the parameter value is not in array. (default: "must be one of: %{values}") |
146
- | `:in_message` | The error message if the parameter value is not in range. (default: "must be within %{min} and %{max}") |
147
- | `:within_message` | A synonym (or alias) for `:in_message` |
154
+ | `:of_message` | Error when value not in array (default: "must be one of: %{values}") |
155
+ | `:in_message` | Error when value not in range (default: "must be within %{min} and %{max}") |
156
+ | `:within_message` | Alias for `:in_message` |
148
157
 
149
158
  ## Length
150
159
 
151
- Validates that the specified parameter value matches the length restrictions supplied.
152
- Only one constraint option can be used at a time apart from `:min` and `:max`
153
- that can be combined together:
160
+ Validates parameter length/size. Works with any object responding to `#size` or `#length`. Only one constraint option can be used at a time, except `:min` and `:max` which can be combined.
154
161
 
155
162
  ```ruby
156
- class UpdateUserDetailsTask < CMDx::Task
157
-
158
- # Range (with custom error message)
159
- required :email, length: { within: 12..36, message: "must be within range" }
160
- required :username, length: { not_within: 48..96 }
161
-
162
- # Boundary
163
- optional :first_name, length: { min: 24 }
164
- optional :middle_name, length: { max: 48 }
165
- optional :last_name, length: { min: 24, max: 48 }
166
-
167
- # Exact
168
- required :title, length: { is: 24 }
169
- required :count, length: { is_not: 48 }
163
+ class CreatePostTask < CMDx::Task
164
+ required :title, length: { within: 5..100 }
165
+ required :body, length: { min: 20 }
166
+ optional :summary, length: { max: 200 }
167
+ required :slug, length: { min: 3, max: 50 }
168
+ required :category_code, length: { is: 3 }
170
169
 
171
170
  def call
172
- # Do work...
171
+ create_blog_post
173
172
  end
174
-
175
173
  end
176
174
  ```
177
175
 
178
- Constraint options:
176
+ **Options:**
179
177
 
180
178
  | Option | Description |
181
179
  | ------------- | ----------- |
182
- | `:within` | A range specifying the minimum and maximum size of the parameter value. |
183
- | `:not_within` | A range specifying the minimum and maximum size of the parameter value it's not to be. |
184
- | `:in` | A synonym (or alias) for `:within` |
185
- | `:not_in` | A synonym (or alias) for `:not_within` |
186
- | `:min` | The minimum size of the parameter value. |
187
- | `:max` | The maximum size of the parameter value. |
188
- | `:is` | The exact size of the parameter value. |
189
- | `:is_not` | The exact size of the parameter value it's not to be. |
180
+ | `:within` | Range specifying min and max size |
181
+ | `:not_within` | Range specifying forbidden size range |
182
+ | `:in` | Alias for `:within` |
183
+ | `:not_in` | Alias for `:not_within` |
184
+ | `:min` | Minimum size required |
185
+ | `:max` | Maximum size allowed |
186
+ | `:is` | Exact size required |
187
+ | `:is_not` | Size that is forbidden |
190
188
 
191
- Other options:
189
+ **Error Messages:**
192
190
 
193
191
  | Option | Description |
194
192
  | --------------------- | ----------- |
195
- | `:within_message` | The error message if the parameter value is within the value range. (default: "length must not be within %{min} and %{max}") |
196
- | `:not_within_message` | The error message if the parameter value is not within the value range. (default: "length must be within %{min} and %{max}") |
197
- | `:in_message` | A synonym (or alias) for `:within_message` |
198
- | `:not_in_message` | A synonym (or alias) for `:not_within_message` |
199
- | `:min_message` | The error message if the parameter value is below the min value. (default: "length must be at least %{min}") |
200
- | `:max_message` | The error message if the parameter value is above the min value. (default: "length must be at most %{max}") |
201
- | `:is_message` | The error message if the parameter value is the exact value. (default: "length must be %{is}") |
202
- | `:is_not_message` | The error message if the parameter value is not the exact value. (default: "length must not be %{is_not}") |
193
+ | `:within_message` | "length must be within %{min} and %{max}" |
194
+ | `:not_within_message` | "length must not be within %{min} and %{max}" |
195
+ | `:min_message` | "length must be at least %{min}" |
196
+ | `:max_message` | "length must be at most %{max}" |
197
+ | `:is_message` | "length must be %{is}" |
198
+ | `:is_not_message` | "length must not be %{is_not}" |
203
199
 
204
200
  ## Numeric
205
201
 
206
- Validates that the specified parameter value matches the numeric restrictions supplied.
207
- Only one constraint option can be used at a time apart from `:min` and `:max`
208
- that can be combined together:
202
+ Validates numeric values against constraints. Works with any numeric type. Only one constraint option can be used at a time, except `:min` and `:max` which can be combined.
209
203
 
210
204
  ```ruby
211
- class UpdateUserDetailsTask < CMDx::Task
212
-
213
- # Range (with custom error message)
214
- required :height, numeric: { within: 36..196 }
215
- required :weight, numeric: { not_within: 0..5 }
216
-
217
- # Boundary
218
- optional :dob_year, numeric: { min: 1900 }
219
- optional :dob_day, numeric: { max: 31 }
220
- optional :dob_month, numeric: { min: 1, max: 12 }
221
-
222
- # Exact
223
- required :age, numeric: { is: 18 }
224
- required :parents, numeric: { is_not: 0 }
205
+ class ProcessOrderTask < CMDx::Task
206
+ required :quantity, numeric: { within: 1..100 }
207
+ required :price, numeric: { min: 0.01 }
208
+ optional :discount_percent, numeric: { max: 50 }
209
+ required :tax_rate, numeric: { min: 0, max: 0.15 }
210
+ required :api_version, numeric: { is: 2 }
225
211
 
226
212
  def call
227
- # Do work...
213
+ calculate_order_total
228
214
  end
229
-
230
215
  end
231
216
  ```
232
217
 
233
- Constraint options:
218
+ **Options:**
234
219
 
235
220
  | Option | Description |
236
221
  | ------------- | ----------- |
237
- | `:within` | A range specifying the minimum and maximum size of the parameter value. |
238
- | `:not_within` | A range specifying the minimum and maximum size of the parameter value it's not to be. |
239
- | `:in` | A synonym (or alias) for `:within` |
240
- | `:not_in` | A synonym (or alias) for `:not_within` |
241
- | `:min` | The minimum size of the parameter value. |
242
- | `:max` | The maximum size of the parameter value. |
243
- | `:is` | The exact size of the parameter value. |
244
- | `:is_not` | The exact size of the parameter value it's not to be. |
222
+ | `:within` | Range specifying min and max value |
223
+ | `:not_within` | Range specifying forbidden value range |
224
+ | `:in` | Alias for `:within` |
225
+ | `:not_in` | Alias for `:not_within` |
226
+ | `:min` | Minimum value required |
227
+ | `:max` | Maximum value allowed |
228
+ | `:is` | Exact value required |
229
+ | `:is_not` | Value that is forbidden |
245
230
 
246
- Other options:
231
+ **Error Messages:**
247
232
 
248
233
  | Option | Description |
249
234
  | --------------------- | ----------- |
250
- | `:within_message` | The error message if the parameter value is within the value range. (default: "must not be within %{min} and %{max}") |
251
- | `:not_within_message` | The error message if the parameter value is not within the value range. (default: "must be within %{min} and %{max}") |
252
- | `:in_message` | A synonym (or alias) for `:within_message` |
253
- | `:not_in_message` | A synonym (or alias) for `:not_within_message` |
254
- | `:min_message` | The error message if the parameter value is below the min value. (default: "must be at least %{min}") |
255
- | `:max_message` | The error message if the parameter value is above the min value. (default: "must be at most %{max}") |
256
- | `:is_message` | The error message if the parameter value is the exact value. (default: "must be %{is}") |
257
- | `:is_not_message` | The error message if the parameter value is not the exact value. (default: "must not be %{is_not}") |
235
+ | `:within_message` | "must be within %{min} and %{max}" |
236
+ | `:not_within_message` | "must not be within %{min} and %{max}" |
237
+ | `:min_message` | "must be at least %{min}" |
238
+ | `:max_message` | "must be at most %{max}" |
239
+ | `:is_message` | "must be %{is}" |
240
+ | `:is_not_message` | "must not be %{is_not}" |
258
241
 
259
242
  ## Custom
260
243
 
261
- Validate the specified parameter value using custom validators.
244
+ Validates using custom logic. Accepts any callable object (class, proc, lambda) implementing a `call` method that returns truthy for valid values.
262
245
 
263
246
  ```ruby
264
- class TLDValidator
247
+ class EmailDomainValidator
265
248
  def self.call(value, options)
266
- tld = options.dig(:custom, :tld) || %w[com]
267
- value.ends_with?(tld)
249
+ allowed_domains = options.dig(:custom, :allowed_domains) || ['example.com']
250
+ domain = value.split('@').last
251
+ allowed_domains.include?(domain)
268
252
  end
269
253
  end
270
254
 
271
- class UpdateUserDetailsTask < CMDx::Task
255
+ class CreateAccountTask < CMDx::Task
256
+ required :work_email, custom: {
257
+ validator: EmailDomainValidator,
258
+ allowed_domains: ['company.com', 'partner.org'],
259
+ message: "must be from an approved domain"
260
+ }
272
261
 
273
- # Basic
274
- required :unconfirmed_email, custom: { validator: TLDValidator }
275
-
276
- # Passable options (with custom error message)
277
- optional :confirmed_email, custom: { validator: TLDValidator, tld: %w[com net org], message: "is not valid" }
262
+ required :age, custom: {
263
+ validator: ->(value, options) { value.between?(18, 120) },
264
+ message: "must be a valid age"
265
+ }
278
266
 
279
267
  def call
280
- # Do work...
268
+ create_user_account
281
269
  end
282
-
283
270
  end
284
271
  ```
285
272
 
286
- Constraint options:
273
+ **Options:**
287
274
 
288
275
  | Option | Description |
289
276
  | ------------ | ----------- |
290
- | `:validator` | Callable class that returns true or false. |
277
+ | `:validator` | Callable object returning true/false. Receives value and options as parameters |
291
278
 
292
- ## Results
279
+ ## Validation Results
293
280
 
294
- The following represents a result output example of a failed validation.
281
+ When validation fails, tasks enter a failed state with detailed error information:
295
282
 
296
283
  ```ruby
297
- result = DetermineBoxSizeTask.call
284
+ class CreateUserTask < CMDx::Task
285
+ required :email, format: { with: /@/, message: "format is invalid" }
286
+ required :username, presence: { message: "cannot be empty" }
287
+ end
288
+
289
+ result = CreateUserTask.call(email: "invalid", username: "")
290
+
298
291
  result.state #=> "interrupted"
299
292
  result.status #=> "failed"
300
293
  result.metadata #=> {
301
- #=> reason: "email format is not valid. username cannot be empty.",
294
+ #=> reason: "email format is invalid. username cannot be empty.",
302
295
  #=> messages: {
303
- #=> email: ["format is not valid"],
296
+ #=> email: ["format is invalid"],
304
297
  #=> username: ["cannot be empty"]
305
298
  #=> }
306
299
  #=> }
300
+
301
+ # Accessing individual error messages
302
+ result.metadata[:messages][:email] #=> ["format is invalid"]
303
+ result.metadata[:messages][:username] #=> ["cannot be empty"]
304
+ ```
305
+
306
+ ## Internationalization (i18n)
307
+
308
+ All validators support internationalization through Rails i18n. Customize error messages in your locale files:
309
+
310
+ ```yaml
311
+ # config/locales/en.yml
312
+ en:
313
+ cmdx:
314
+ validators:
315
+ presence: "is required"
316
+ format: "has invalid format"
317
+ inclusion:
318
+ of: "must be one of: %{values}"
319
+ in: "must be within %{min} and %{max}"
320
+ exclusion:
321
+ of: "must not be one of: %{values}"
322
+ in: "must not be within %{min} and %{max}"
323
+ length:
324
+ within: "must be between %{min} and %{max} characters"
325
+ min: "must be at least %{min} characters"
326
+ max: "must be at most %{max} characters"
327
+ numeric:
328
+ within: "must be between %{min} and %{max}"
329
+ min: "must be at least %{min}"
330
+ max: "must be at most %{max}"
331
+ custom: "is invalid"
307
332
  ```
308
333
 
309
334
  ---
310
335
 
311
- - **Prev:** [Coercions](https://github.com/drexed/cmdx/blob/main/docs/parameters/coercions.md)
312
- - **Next:** [Defaults](https://github.com/drexed/cmdx/blob/main/docs/parameters/defaults.md)
336
+ - **Prev:** [Parameters - Coercions](coercions.md)
337
+ - **Next:** [Parameters - Defaults](defaults.md)