cmdx 1.1.1 → 1.5.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/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/.ruby-version +1 -1
- data/CHANGELOG.md +6 -128
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -60
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -52
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- data/lib/locales/zh.yml +0 -35
@@ -0,0 +1,78 @@
|
|
1
|
+
# Attributes - Naming
|
2
|
+
|
3
|
+
Attribute naming provides method name customization to prevent conflicts and enable flexible attribute access patterns. When attributes share names with existing methods or when multiple attributes from different sources have the same name, affixing ensures clean method resolution within tasks.
|
4
|
+
|
5
|
+
> [!NOTE]
|
6
|
+
> Affixing modifies only the generated accessor method names within tasks.
|
7
|
+
|
8
|
+
## Table of Contents
|
9
|
+
|
10
|
+
- [Prefix](#prefix)
|
11
|
+
- [Suffix](#suffix)
|
12
|
+
- [As](#as)
|
13
|
+
|
14
|
+
## Prefix
|
15
|
+
|
16
|
+
Adds a prefix to the generated accessor method name.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class GenerateReport < CMDx::Task
|
20
|
+
# Dynamic from attribute source
|
21
|
+
attribute :template, prefix: true
|
22
|
+
|
23
|
+
# Static
|
24
|
+
attribute :format, prefix: "report_"
|
25
|
+
|
26
|
+
def work
|
27
|
+
context_template #=> "monthly_sales"
|
28
|
+
report_format #=> "pdf"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Attributes passed as original attribute names
|
33
|
+
GenerateReport.execute(template: "monthly_sales", format: "pdf")
|
34
|
+
```
|
35
|
+
|
36
|
+
## Suffix
|
37
|
+
|
38
|
+
Adds a suffix to the generated accessor method name.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class DeployApplication < CMDx::Task
|
42
|
+
# Dynamic from attribute source
|
43
|
+
attribute :branch, suffix: true
|
44
|
+
|
45
|
+
# Static
|
46
|
+
attribute :version, suffix: "_tag"
|
47
|
+
|
48
|
+
def work
|
49
|
+
branch_context #=> "main"
|
50
|
+
version_tag #=> "v1.2.3"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attributes passed as original attribute names
|
55
|
+
DeployApplication.execute(branch: "main", version: "v1.2.3")
|
56
|
+
```
|
57
|
+
|
58
|
+
## As
|
59
|
+
|
60
|
+
Completely renames the generated accessor method.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
class ScheduleMaintenance < CMDx::Task
|
64
|
+
attribute :scheduled_at, as: :when
|
65
|
+
|
66
|
+
def work
|
67
|
+
when #=> <DateTime>
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Attributes passed as original attribute names
|
72
|
+
ScheduleMaintenance.execute(scheduled_at: DateTime.new(2024, 12, 15, 2, 0, 0))
|
73
|
+
```
|
74
|
+
|
75
|
+
---
|
76
|
+
|
77
|
+
- **Prev:** [Attributes - Definitions](definitions.md)
|
78
|
+
- **Next:** [Attributes - Coercions](coercions.md)
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# Attributes - Validations
|
2
|
+
|
3
|
+
Attribute validations ensure task arguments meet specified requirements before execution begins. Validations run after coercions and provide declarative rules for data integrity, supporting both built-in validators and custom validation logic.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Usage](#usage)
|
8
|
+
- [Built-in Validators](#built-in-validators)
|
9
|
+
- [Common Options](#common-options)
|
10
|
+
- [Exclusion](#exclusion)
|
11
|
+
- [Format](#format)
|
12
|
+
- [Inclusion](#inclusion)
|
13
|
+
- [Length](#length)
|
14
|
+
- [Numeric](#numeric)
|
15
|
+
- [Presence](#presence)
|
16
|
+
- [Declarations](#declarations)
|
17
|
+
- [Proc or Lambda](#proc-or-lambda)
|
18
|
+
- [Class or Module](#class-or-module)
|
19
|
+
- [Removals](#removals)
|
20
|
+
- [Error Handling](#error-handling)
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Define validation rules on attributes to enforce data requirements:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class ProcessSubscription < CMDx::Task
|
28
|
+
# Required field with presence validation
|
29
|
+
attribute :user_id, presence: true
|
30
|
+
|
31
|
+
# String with length constraints
|
32
|
+
attribute :preferences, length: { minimum: 10, maximum: 500 }
|
33
|
+
|
34
|
+
# Numeric range validation
|
35
|
+
attribute :tier_level, inclusion: { in: 1..5 }
|
36
|
+
|
37
|
+
# Format validation for email
|
38
|
+
attribute :contact_email, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
39
|
+
|
40
|
+
def work
|
41
|
+
user_id #=> "98765"
|
42
|
+
preferences #=> "Send weekly digest emails"
|
43
|
+
tier_level #=> 3
|
44
|
+
contact_email #=> "user@company.com"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
ProcessSubscription.execute(
|
49
|
+
user_id: "98765",
|
50
|
+
preferences: "Send weekly digest emails",
|
51
|
+
tier_level: 3,
|
52
|
+
contact_email: "user@company.com"
|
53
|
+
)
|
54
|
+
```
|
55
|
+
|
56
|
+
> [!TIP]
|
57
|
+
> Validations run after coercions, so you can validate the final coerced values rather than raw input.
|
58
|
+
|
59
|
+
## Built-in Validators
|
60
|
+
|
61
|
+
### Common Options
|
62
|
+
|
63
|
+
This list of options is available to all validators:
|
64
|
+
|
65
|
+
| Option | Description |
|
66
|
+
|--------|-------------|
|
67
|
+
| `:allow_nil` | Skip validation when value is `nil` |
|
68
|
+
| `:if` | Symbol, proc, lambda, or callable determining when to validate |
|
69
|
+
| `:unless` | Symbol, proc, lambda, or callable determining when to skip validation |
|
70
|
+
| `:message` | Custom error message for validation failures |
|
71
|
+
|
72
|
+
### Exclusion
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
class ProcessProduct < CMDx::Task
|
76
|
+
attribute :status, exclusion: { in: %w[recalled archived] }
|
77
|
+
|
78
|
+
def work
|
79
|
+
# Your logic here...
|
80
|
+
end
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
| Options | Description |
|
85
|
+
|---------|-------------|
|
86
|
+
| `:in` | The collection of forbidden values or range |
|
87
|
+
| `:within` | Alias for :in option |
|
88
|
+
| `:of_message` | Custom message for discrete value exclusions |
|
89
|
+
| `:in_message` | Custom message for range-based exclusions |
|
90
|
+
| `:within_message` | Alias for :in_message option |
|
91
|
+
|
92
|
+
### Format
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class ProcessProduct < CMDx::Task
|
96
|
+
attribute :sku, format: /\A[A-Z]{3}-[0-9]{4}\z/
|
97
|
+
|
98
|
+
attribute :sku, format: { with: /\A[A-Z]{3}-[0-9]{4}\z/ }
|
99
|
+
|
100
|
+
def work
|
101
|
+
# Your logic here...
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
| Options | Description |
|
107
|
+
|---------|-------------|
|
108
|
+
| `regexp` | Alias for :with option |
|
109
|
+
| `:with` | Regex pattern that the value must match |
|
110
|
+
| `:without` | Regex pattern that the value must not match |
|
111
|
+
|
112
|
+
### Inclusion
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class ProcessProduct < CMDx::Task
|
116
|
+
attribute :availability, inclusion: { in: %w[available limited] }
|
117
|
+
|
118
|
+
def work
|
119
|
+
# Your logic here...
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
| Options | Description |
|
125
|
+
|---------|-------------|
|
126
|
+
| `:in` | The collection of allowed values or range |
|
127
|
+
| `:within` | Alias for :in option |
|
128
|
+
| `:of_message` | Custom message for discrete value inclusions |
|
129
|
+
| `:in_message` | Custom message for range-based inclusions |
|
130
|
+
| `:within_message` | Alias for :in_message option |
|
131
|
+
|
132
|
+
### Length
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class CreateBlogPost < CMDx::Task
|
136
|
+
attribute :title, length: { within: 5..100 }
|
137
|
+
|
138
|
+
def work
|
139
|
+
# Your logic here...
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
| Options | Description |
|
145
|
+
|---------|-------------|
|
146
|
+
| `:within` | Range that the length must fall within (inclusive) |
|
147
|
+
| `:not_within` | Range that the length must not fall within |
|
148
|
+
| `:in` | Alias for :within |
|
149
|
+
| `:not_in` | Range that the length must not fall within |
|
150
|
+
| `:min` | Minimum allowed length |
|
151
|
+
| `:max` | Maximum allowed length |
|
152
|
+
| `:is` | Exact required length |
|
153
|
+
| `:is_not` | Length that is not allowed |
|
154
|
+
| `:within_message` | Custom message for within/range validations |
|
155
|
+
| `:in_message` | Custom message for :in validation |
|
156
|
+
| `:not_within_message` | Custom message for not_within validation |
|
157
|
+
| `:not_in_message` | Custom message for not_in validation |
|
158
|
+
| `:min_message` | Custom message for minimum length validation |
|
159
|
+
| `:max_message` | Custom message for maximum length validation |
|
160
|
+
| `:is_message` | Custom message for exact length validation |
|
161
|
+
| `:is_not_message` | Custom message for is_not validation |
|
162
|
+
|
163
|
+
### Numeric
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class CreateBlogPost < CMDx::Task
|
167
|
+
attribute :word_count, numeric: { min: 100 }
|
168
|
+
|
169
|
+
def work
|
170
|
+
# Your logic here...
|
171
|
+
end
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
| Options | Description |
|
176
|
+
|---------|-------------|
|
177
|
+
| `:within` | Range that the value must fall within (inclusive) |
|
178
|
+
| `:not_within` | Range that the value must not fall within |
|
179
|
+
| `:in` | Alias for :within option |
|
180
|
+
| `:not_in` | Alias for :not_within option |
|
181
|
+
| `:min` | Minimum allowed value (inclusive, >=) |
|
182
|
+
| `:max` | Maximum allowed value (inclusive, <=) |
|
183
|
+
| `:is` | Exact value that must match |
|
184
|
+
| `:is_not` | Value that must not match |
|
185
|
+
| `:within_message` | Custom message for range validations |
|
186
|
+
| `:not_within_message` | Custom message for exclusion validations |
|
187
|
+
| `:min_message` | Custom message for minimum validation |
|
188
|
+
| `:max_message` | Custom message for maximum validation |
|
189
|
+
| `:is_message` | Custom message for exact match validation |
|
190
|
+
| `:is_not_message` | Custom message for exclusion validation |
|
191
|
+
|
192
|
+
### Presence
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
class CreateBlogPost < CMDx::Task
|
196
|
+
attribute :content, presence: true
|
197
|
+
|
198
|
+
attribute :content, presence: { message: "cannot be blank" }
|
199
|
+
|
200
|
+
def work
|
201
|
+
# Your logic here...
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
| Options | Description |
|
207
|
+
|---------|-------------|
|
208
|
+
| `true` | Ensures value is not nil, empty string, or whitespace |
|
209
|
+
|
210
|
+
## Declarations
|
211
|
+
|
212
|
+
> [!IMPORTANT]
|
213
|
+
> Custom validators must raise a `CMDx::ValidationError` and its message is used as part of the fault reason and metadata.
|
214
|
+
|
215
|
+
### Proc or Lambda
|
216
|
+
|
217
|
+
Use anonymous functions for simple validation logic:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class SetupApplication < CMDx::Task
|
221
|
+
# Proc
|
222
|
+
register :validator, :api_key, proc do |value, options = {}|
|
223
|
+
unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
|
224
|
+
raise CMDx::ValidationError, "invalid API key format"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Lambda
|
229
|
+
register :validator, :api_key, ->(value, options = {}) {
|
230
|
+
unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
|
231
|
+
raise CMDx::ValidationError, "invalid API key format"
|
232
|
+
end
|
233
|
+
}
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
### Class or Module
|
238
|
+
|
239
|
+
Register custom validation logic for specialized requirements:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
class ApiKeyValidator
|
243
|
+
def self.call(value, options = {})
|
244
|
+
unless value.match?(/\A[a-zA-Z0-9]{32}\z/)
|
245
|
+
raise CMDx::ValidationError, "invalid API key format"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
class SetupApplication < CMDx::Task
|
251
|
+
register :validator, :api_key, ApiKeyValidator
|
252
|
+
|
253
|
+
attribute :access_key, api_key: true
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
## Removals
|
258
|
+
|
259
|
+
Remove custom validators when no longer needed:
|
260
|
+
|
261
|
+
> [!WARNING]
|
262
|
+
> Only one removal operation is allowed per `deregister` call. Multiple removals require separate calls.
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
class SetupApplication < CMDx::Task
|
266
|
+
deregister :validator, :api_key
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
## Error Handling
|
271
|
+
|
272
|
+
Validation failures provide detailed error information including attribute paths, validation rules, and specific failure reasons:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
class CreateProject < CMDx::Task
|
276
|
+
attribute :project_name, presence: true, length: { minimum: 3, maximum: 50 }
|
277
|
+
attribute :budget, numeric: { greater_than: 1000, less_than: 1000000 }
|
278
|
+
attribute :priority, inclusion: { in: [:low, :medium, :high] }
|
279
|
+
attribute :contact_email, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
280
|
+
|
281
|
+
def work
|
282
|
+
# Your logic here...
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
result = CreateProject.execute(
|
287
|
+
project_name: "AB", # Too short
|
288
|
+
budget: 500, # Too low
|
289
|
+
priority: :urgent, # Not in allowed list
|
290
|
+
contact_email: "invalid-email" # Invalid format
|
291
|
+
)
|
292
|
+
|
293
|
+
result.state #=> "interrupted"
|
294
|
+
result.status #=> "failed"
|
295
|
+
result.reason #=> "project_name is too short (minimum is 3 characters). budget must be greater than 1000. priority is not included in the list. contact_email is invalid."
|
296
|
+
result.metadata #=> {
|
297
|
+
# messages: {
|
298
|
+
# project_name: ["is too short (minimum is 3 characters)"],
|
299
|
+
# budget: ["must be greater than 1000"],
|
300
|
+
# priority: ["is not included in the list"],
|
301
|
+
# contact_email: ["is invalid"]
|
302
|
+
# }
|
303
|
+
# }
|
304
|
+
```
|
305
|
+
|
306
|
+
---
|
307
|
+
|
308
|
+
- **Prev:** [Attributes - Coercions](coercions.md)
|
309
|
+
- **Next:** [Attributes - Defaults](defaults.md)
|