cmdx 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +13 -12
  4. data/.cursor/prompts/yardoc.md +11 -6
  5. data/CHANGELOG.md +13 -2
  6. data/README.md +1 -0
  7. data/docs/ai_prompts.md +269 -195
  8. data/docs/basics/call.md +124 -58
  9. data/docs/basics/chain.md +190 -160
  10. data/docs/basics/context.md +242 -154
  11. data/docs/basics/setup.md +302 -32
  12. data/docs/callbacks.md +390 -94
  13. data/docs/configuration.md +181 -65
  14. data/docs/deprecation.md +245 -0
  15. data/docs/getting_started.md +161 -39
  16. data/docs/internationalization.md +590 -70
  17. data/docs/interruptions/exceptions.md +135 -118
  18. data/docs/interruptions/faults.md +150 -125
  19. data/docs/interruptions/halt.md +134 -80
  20. data/docs/logging.md +181 -118
  21. data/docs/middlewares.md +150 -377
  22. data/docs/outcomes/result.md +140 -112
  23. data/docs/outcomes/states.md +134 -99
  24. data/docs/outcomes/statuses.md +204 -146
  25. data/docs/parameters/coercions.md +232 -281
  26. data/docs/parameters/defaults.md +224 -169
  27. data/docs/parameters/definitions.md +289 -141
  28. data/docs/parameters/namespacing.md +250 -161
  29. data/docs/parameters/validations.md +260 -133
  30. data/docs/testing.md +191 -197
  31. data/docs/workflows.md +143 -98
  32. data/lib/cmdx/callback.rb +23 -19
  33. data/lib/cmdx/callback_registry.rb +1 -3
  34. data/lib/cmdx/chain_inspector.rb +23 -23
  35. data/lib/cmdx/chain_serializer.rb +38 -19
  36. data/lib/cmdx/coercion.rb +20 -12
  37. data/lib/cmdx/coercion_registry.rb +51 -32
  38. data/lib/cmdx/configuration.rb +84 -31
  39. data/lib/cmdx/context.rb +32 -21
  40. data/lib/cmdx/core_ext/hash.rb +13 -13
  41. data/lib/cmdx/core_ext/module.rb +1 -1
  42. data/lib/cmdx/core_ext/object.rb +12 -12
  43. data/lib/cmdx/correlator.rb +60 -39
  44. data/lib/cmdx/errors.rb +105 -131
  45. data/lib/cmdx/fault.rb +66 -45
  46. data/lib/cmdx/immutator.rb +20 -21
  47. data/lib/cmdx/lazy_struct.rb +78 -70
  48. data/lib/cmdx/log_formatters/json.rb +1 -1
  49. data/lib/cmdx/log_formatters/key_value.rb +1 -1
  50. data/lib/cmdx/log_formatters/line.rb +1 -1
  51. data/lib/cmdx/log_formatters/logstash.rb +1 -1
  52. data/lib/cmdx/log_formatters/pretty_json.rb +1 -1
  53. data/lib/cmdx/log_formatters/pretty_key_value.rb +1 -1
  54. data/lib/cmdx/log_formatters/pretty_line.rb +1 -1
  55. data/lib/cmdx/log_formatters/raw.rb +2 -2
  56. data/lib/cmdx/logger.rb +19 -14
  57. data/lib/cmdx/logger_ansi.rb +33 -17
  58. data/lib/cmdx/logger_serializer.rb +85 -24
  59. data/lib/cmdx/middleware.rb +39 -21
  60. data/lib/cmdx/middleware_registry.rb +4 -3
  61. data/lib/cmdx/parameter.rb +151 -89
  62. data/lib/cmdx/parameter_inspector.rb +34 -21
  63. data/lib/cmdx/parameter_registry.rb +36 -30
  64. data/lib/cmdx/parameter_serializer.rb +21 -14
  65. data/lib/cmdx/result.rb +136 -135
  66. data/lib/cmdx/result_ansi.rb +31 -17
  67. data/lib/cmdx/result_inspector.rb +32 -27
  68. data/lib/cmdx/result_logger.rb +23 -14
  69. data/lib/cmdx/result_serializer.rb +65 -27
  70. data/lib/cmdx/task.rb +234 -113
  71. data/lib/cmdx/task_deprecator.rb +22 -25
  72. data/lib/cmdx/task_processor.rb +89 -88
  73. data/lib/cmdx/task_serializer.rb +27 -14
  74. data/lib/cmdx/utils/monotonic_runtime.rb +2 -4
  75. data/lib/cmdx/validator.rb +25 -16
  76. data/lib/cmdx/validator_registry.rb +53 -31
  77. data/lib/cmdx/validators/exclusion.rb +1 -1
  78. data/lib/cmdx/validators/format.rb +2 -2
  79. data/lib/cmdx/validators/inclusion.rb +2 -2
  80. data/lib/cmdx/validators/length.rb +2 -2
  81. data/lib/cmdx/validators/numeric.rb +3 -3
  82. data/lib/cmdx/validators/presence.rb +2 -2
  83. data/lib/cmdx/version.rb +1 -1
  84. data/lib/cmdx/workflow.rb +54 -33
  85. data/lib/generators/cmdx/task_generator.rb +6 -6
  86. data/lib/generators/cmdx/workflow_generator.rb +6 -6
  87. metadata +3 -1
@@ -1,60 +1,91 @@
1
1
  # Parameters - Definitions
2
2
 
3
- Parameters provide a contract to verify that task execution arguments match expected requirements and structure. They define the interface between task callers and task implementation, enabling automatic validation, type coercion, and method generation for clean parameter access within tasks.
3
+ Parameters define the interface between task callers and implementation, enabling automatic validation, type coercion, and method generation. They provide a contract to verify that task execution arguments match expected requirements and structure.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
7
  - [TLDR](#tldr)
8
- - [Parameter Fundamentals](#parameter-fundamentals)
8
+ - [Basic Parameter Definition](#basic-parameter-definition)
9
9
  - [Parameter Sources](#parameter-sources)
10
10
  - [Nested Parameters](#nested-parameters)
11
- - [Parameter Method Generation](#parameter-method-generation)
11
+ - [Advanced Features](#advanced-features)
12
12
  - [Error Handling](#error-handling)
13
13
 
14
14
  ## TLDR
15
15
 
16
- - **Required/Optional** - Define with `required :param` and `optional :param` class methods
17
- - **Method generation** - Parameters become instance methods for easy access
18
- - **Sources** - Default `:context` source, or custom with `source: :user`
19
- - **Nested params** - Complex structures with `required :address do ... end`
20
- - **Call interface** - Parameters passed as keyword arguments to `TaskClass.call(param: value)`
16
+ ```ruby
17
+ class ProcessOrderTask < CMDx::Task
18
+ # Required parameters - must be provided
19
+ required :order_id, :customer_id
21
20
 
22
- ## Parameter Fundamentals
21
+ # Optional parameters - can be nil
22
+ optional :notes, :priority
23
23
 
24
- Parameters are defined using `required` and `optional` class methods that automatically create accessor methods within task instances. Parameters are matched from call arguments and made available as instance methods.
24
+ # Custom sources
25
+ required :name, :email, source: :user
25
26
 
26
- > [!IMPORTANT]
27
- > Required parameters must be provided in call arguments or task execution will fail.
27
+ # Nested parameters
28
+ required :shipping_address do
29
+ required :street, :city, :state
30
+ optional :apartment
31
+ end
28
32
 
29
- ### Basic Parameter Definition
33
+ def call
34
+ order_id # → value from call arguments
35
+ name # → delegates to user.name
36
+ street # → delegates to shipping_address.street
37
+ end
38
+ end
30
39
 
31
- ```ruby
32
- class CreateOrderTask < CMDx::Task
33
- # Must be provided in call arguments
34
- required :order_id
40
+ # Usage
41
+ ProcessOrderTask.call(
42
+ order_id: 123,
43
+ customer_id: 456,
44
+ shipping_address: { street: "123 Main St", city: "Miami", state: "FL" }
45
+ )
46
+ ```
35
47
 
36
- # Optional - returns nil if not provided
37
- optional :priority
48
+ ## Basic Parameter Definition
49
+
50
+ > [!IMPORTANT]
51
+ > Required parameters must be provided in call arguments or task execution will fail. Optional parameters return `nil` when not provided.
52
+
53
+ ```ruby
54
+ class CreateUserTask < CMDx::Task
55
+ # Single parameter definitions
56
+ required :email
57
+ optional :name
38
58
 
39
59
  # Multiple parameters in one declaration
40
- required :customer_id, :product_id
41
- optional :notes, :shipping_method
60
+ required :age, :phone
61
+ optional :bio, :website
62
+
63
+ # Parameters with type coercion and validation
64
+ required :age, type: :integer, numeric: { min: 18 }
65
+ optional :tags, type: :array, default: []
42
66
 
43
67
  def call
44
- order_id #=> 123 (from call arguments)
45
- priority #=> "high" or nil
46
- customer_id #=> 456 (from call arguments)
47
- shipping_method #=> "express" or nil
68
+ # All parameters become instance methods
69
+ user = User.create!(
70
+ email: email, # Required - guaranteed to be present
71
+ name: name, # Optional - may be nil
72
+ age: age, # Required integer, validated >= 18
73
+ phone: phone, # Required - guaranteed to be present
74
+ bio: bio, # Optional - may be nil
75
+ tags: tags # Optional array with default []
76
+ )
77
+
78
+ user
48
79
  end
49
80
  end
50
81
 
51
82
  # Parameters passed as keyword arguments
52
- CreateOrderTask.call(
53
- order_id: 123,
54
- customer_id: 456,
55
- product_id: 789,
56
- priority: "high",
57
- shipping_method: "express"
83
+ CreateUserTask.call(
84
+ email: "user@example.com",
85
+ age: 25,
86
+ phone: "555-0123",
87
+ name: "John Doe",
88
+ tags: ["premium", "beta"]
58
89
  )
59
90
  ```
60
91
 
@@ -62,42 +93,49 @@ CreateOrderTask.call(
62
93
 
63
94
  Parameters delegate to source objects within the task context. The default source is `:context`, but any accessible method or object can serve as a parameter source.
64
95
 
96
+ > [!NOTE]
97
+ > Sources allow parameters to pull values from different objects instead of just call arguments.
98
+
65
99
  ### Default Context Source
66
100
 
67
101
  ```ruby
68
- class UpdateUserTask < CMDx::Task
69
- # Delegates to context.user_id (default source)
102
+ class UpdateProfileTask < CMDx::Task
103
+ # Default source is :context
70
104
  required :user_id
105
+ optional :avatar_url
71
106
 
72
- # Explicitly specified context source
107
+ # Explicitly specify context source
73
108
  required :email, source: :context
74
109
 
75
110
  def call
76
- user_id #=> delegates to context.user_id
77
- email #=> delegates to context.email
111
+ user = User.find(user_id) # From context.user_id
112
+ user.update!(
113
+ email: email, # From context.email
114
+ avatar_url: avatar_url # From context.avatar_url
115
+ )
78
116
  end
79
117
  end
80
-
81
- UpdateUserTask.call(user_id: 123, email: "user@example.com")
82
118
  ```
83
119
 
84
120
  ### Custom Object Sources
85
121
 
86
122
  ```ruby
87
- class ProcessUserOrderTask < CMDx::Task
123
+ class GenerateInvoiceTask < CMDx::Task
88
124
  # Delegate to user object
89
125
  required :name, :email, source: :user
90
126
 
91
127
  # Delegate to order object
92
- required :total, :status, source: :order
128
+ required :total, :items, source: :order
93
129
  optional :discount, source: :order
94
130
 
95
131
  def call
96
- name #=> delegates to user.name
97
- email #=> delegates to user.email
98
- total #=> delegates to order.total
99
- status #=> delegates to order.status
100
- discount #=> delegates to order.discount
132
+ Invoice.create!(
133
+ customer_name: name, # From user.name
134
+ customer_email: email, # From user.email
135
+ amount: total, # From order.total
136
+ line_items: items, # From order.items
137
+ discount_amount: discount # From order.discount
138
+ )
101
139
  end
102
140
 
103
141
  private
@@ -111,187 +149,297 @@ class ProcessUserOrderTask < CMDx::Task
111
149
  end
112
150
  end
113
151
 
114
- ProcessUserOrderTask.call(user_id: 123, order_id: 456)
152
+ GenerateInvoiceTask.call(user_id: 123, order_id: 456)
115
153
  ```
116
154
 
117
155
  ### Dynamic Sources
118
156
 
119
157
  ```ruby
120
- class ProcessDynamicParameterTask < CMDx::Task
121
- # Lambda source for dynamic resolution
122
- required :company_name, source: -> { user.company }
158
+ class CalculatePermissionsTask < CMDx::Task
159
+ # Proc/Lambda source for dynamic resolution
160
+ required :current_user, source: ->(task) { User.find(task.context.user_id) }
161
+ required :company_name, source: proc { Company.find_by(context.company_id).name }
123
162
 
124
- # Method name sources
125
- required :account_type, source: :determine_account_type
163
+ # Method symbol sources
164
+ required :role, source: :determine_user_role
126
165
  optional :access_level, source: :calculate_access_level
127
166
 
128
167
  def call
129
- company_name #=> resolved via lambda
130
- account_type #=> result of determine_account_type method
131
- access_level #=> result of calculate_access_level method
168
+ {
169
+ user: current_user.name, # Resolved via lambda
170
+ company: company_name, # Resolved via proc
171
+ role: role, # From determine_user_role method
172
+ access: access_level # From calculate_access_level method
173
+ }
132
174
  end
133
175
 
134
176
  private
135
177
 
136
- def user
137
- @user ||= User.find(context.user_id)
138
- end
139
-
140
- def determine_account_type
141
- user.premium? ? "premium" : "standard"
178
+ def determine_user_role
179
+ current_user.admin? ? "admin" : "user"
142
180
  end
143
181
 
144
182
  def calculate_access_level
145
- user.admin? ? "admin" : "user"
183
+ case role
184
+ when "admin" then "full"
185
+ when "user" then "limited"
186
+ else "none"
187
+ end
146
188
  end
147
189
  end
148
190
  ```
149
191
 
150
192
  ## Nested Parameters
151
193
 
152
- Nested parameters allow complex parameter structures where child parameters automatically inherit their parent as the source. This enables validation and access of structured data.
153
-
154
- > [!NOTE]
155
- > Child parameters are only required when their parent parameter is provided.
194
+ Nested parameters enable complex parameter structures where child parameters automatically inherit their parent as the source. This allows validation and access of structured data.
156
195
 
157
- ### Basic Nesting
196
+ > [!TIP]
197
+ > Child parameters are only required when their parent parameter is provided, enabling flexible optional structures.
158
198
 
159
199
  ```ruby
160
- class CreateShippingLabelTask < CMDx::Task
161
- # Parent parameter with nested children
200
+ class CreateShipmentTask < CMDx::Task
201
+ required :order_id
202
+
203
+ # Required parent with required children
162
204
  required :shipping_address do
163
- required :street, :city, :state, :zip_code
164
- optional :apartment_number
205
+ required :street, :city, :state, :zip
206
+ optional :apartment, :instructions
165
207
  end
166
208
 
167
- # Optional parent with required children
209
+ # Optional parent with conditional children
168
210
  optional :billing_address do
169
- required :street, :city # Only required if billing_address provided
211
+ required :street, :city # Only required if billing_address provided
170
212
  optional :same_as_shipping
171
213
  end
172
214
 
215
+ # Multi-level nesting
216
+ optional :special_handling do
217
+ required :type
218
+
219
+ optional :insurance do
220
+ required :coverage_amount, type: :float
221
+ optional :carrier
222
+ end
223
+ end
224
+
173
225
  def call
174
- # Parent parameter access
175
- shipping_address #=> { street: "123 Main St", city: "Miami", ... }
226
+ shipment = Shipment.create!(
227
+ order_id: order_id,
228
+
229
+ # Access nested parameters directly
230
+ ship_to_street: street, # From shipping_address.street
231
+ ship_to_city: city, # From shipping_address.city
232
+ ship_to_state: state, # From shipping_address.state
233
+ delivery_instructions: instructions,
176
234
 
177
- # Child parameter access (delegates to parent)
178
- street #=> "123 Main St" (from shipping_address.street)
179
- city #=> "Miami" (from shipping_address.city)
180
- apartment_number #=> nil (optional, not provided)
235
+ # Handle optional nested structures
236
+ special_handling_type: type, # From special_handling.type (if provided)
237
+ insurance_amount: coverage_amount # From special_handling.insurance.coverage_amount
238
+ )
239
+
240
+ shipment
181
241
  end
182
242
  end
183
243
 
184
- CreateShippingLabelTask.call(
244
+ CreateShipmentTask.call(
245
+ order_id: 123,
185
246
  shipping_address: {
186
247
  street: "123 Main St",
187
248
  city: "Miami",
188
249
  state: "FL",
189
- zip_code: "33101"
250
+ zip: "33101",
251
+ instructions: "Leave at door"
252
+ },
253
+ special_handling: {
254
+ type: "fragile",
255
+ insurance: {
256
+ coverage_amount: 500.00,
257
+ carrier: "FedEx"
258
+ }
190
259
  }
191
260
  )
192
261
  ```
193
262
 
194
- ### Multi-Level Nesting
263
+ ## Advanced Features
264
+
265
+ ### Parameter Method Generation
195
266
 
196
267
  ```ruby
197
- class CreateUserProfileTask < CMDx::Task
198
- required :user do
199
- required :name, :email
200
-
201
- required :profile do
202
- required :age
203
- optional :bio
204
-
205
- optional :preferences do
206
- optional :theme, :language
207
- required :notifications # Required if preferences provided
208
- end
268
+ class ProcessPaymentTask < CMDx::Task
269
+ required :amount, type: :float
270
+ required :payment_method
271
+
272
+ # Nested parameters generate flattened methods
273
+ required :customer do
274
+ required :id, :email
275
+
276
+ optional :billing_address do
277
+ required :street, :city
278
+ optional :unit
209
279
  end
210
280
  end
211
281
 
212
282
  def call
213
- # Access at any nesting level
214
- name #=> delegates to user.name
215
- email #=> delegates to user.email
216
- age #=> delegates to user.profile.age
217
- theme #=> delegates to user.profile.preferences.theme
283
+ # All parameters accessible as instance methods
284
+ payment = PaymentService.charge(
285
+ amount: amount, # Direct parameter access
286
+ method: payment_method, # Direct parameter access
287
+ customer_id: id, # From customer.id
288
+ customer_email: email, # From customer.email
289
+ billing_street: street, # From customer.billing_address.street
290
+ billing_city: city # From customer.billing_address.city
291
+ )
292
+
293
+ payment
218
294
  end
219
295
  end
220
296
  ```
221
297
 
222
- ## Parameter Method Generation
298
+ ### Parameter Introspection
223
299
 
224
- Parameters automatically generate accessor methods that delegate to their configured sources.
300
+ ```ruby
301
+ class IntrospectionExampleTask < CMDx::Task
302
+ required :name
303
+ optional :age, type: :integer, default: 18
225
304
 
226
- > [!TIP]
227
- > Parameter names become instance methods accessible within the task.
305
+ required :address do
306
+ required :street
307
+ optional :unit
308
+ end
228
309
 
229
- ```ruby
230
- class ProcessPaymentTask < CMDx::Task
231
- # Standard method generation
232
- required :payment_id # Generates: payment_id method
310
+ def call
311
+ # Access parameter metadata
312
+ params = self.class.parameters
313
+
314
+ params.each do |param|
315
+ puts "Parameter: #{param.name}"
316
+ puts "Required: #{param.required?}"
317
+ puts "Type: #{param.type}"
318
+ puts "Default: #{param.default}" if param.has_default?
319
+ puts "Source: #{param.source}"
320
+ puts "---"
321
+ end
322
+ end
323
+ end
324
+ ```
233
325
 
234
- # Custom source with method name
235
- required :account_name, source: :account # Generates: account_name method
326
+ ## Error Handling
327
+
328
+ > [!WARNING]
329
+ > Parameter validation failures result in structured error information with details about each failed parameter.
236
330
 
237
- # Nested parameter method generation
238
- required :billing_info do
239
- required :card_number # Generates: card_number method
240
- required :expiry_date # Generates: expiry_date method
331
+ ### Missing Required Parameters
332
+
333
+ ```ruby
334
+ class RequiredParamsTask < CMDx::Task
335
+ required :user_id, :order_id
336
+ required :shipping_address do
337
+ required :street, :city
241
338
  end
242
339
 
243
340
  def call
244
- payment_id #=> accesses context.payment_id
245
- account_name #=> accesses account.account_name
246
- card_number #=> accesses billing_info.card_number
247
- expiry_date #=> accesses billing_info.expiry_date
341
+ # Task logic
342
+ end
343
+ end
344
+
345
+ # Missing required parameters
346
+ result = RequiredParamsTask.call(user_id: 123)
347
+ result.failed? # → true
348
+ result.metadata
349
+ # {
350
+ # reason: "order_id is required. shipping_address is required.",
351
+ # messages: {
352
+ # order_id: ["is required"],
353
+ # shipping_address: ["is required"]
354
+ # }
355
+ # }
356
+
357
+ # Missing nested required parameters
358
+ result = RequiredParamsTask.call(
359
+ user_id: 123,
360
+ order_id: 456,
361
+ shipping_address: { street: "123 Main St" } # Missing city
362
+ )
363
+ result.failed? # → true
364
+ result.metadata
365
+ # {
366
+ # reason: "city is required.",
367
+ # messages: {
368
+ # city: ["is required"]
369
+ # }
370
+ # }
371
+ ```
372
+
373
+ ### Source Resolution Errors
374
+
375
+ ```ruby
376
+ class SourceErrorTask < CMDx::Task
377
+ required :name, source: :user
378
+ required :status, source: :nonexistent_method
379
+
380
+ def call
381
+ # Task logic
248
382
  end
249
383
 
250
384
  private
251
385
 
252
- def account
253
- @account ||= Account.find(context.account_id)
386
+ def user
387
+ # This will raise an error
388
+ raise StandardError, "User service unavailable"
254
389
  end
255
390
  end
256
- ```
257
-
258
- ## Error Handling
259
391
 
260
- Parameter validation failures result in structured error information:
392
+ result = SourceErrorTask.call
393
+ result.failed? # → true
394
+ # Error propagated from source resolution failure
395
+ ```
261
396
 
262
- > [!WARNING]
263
- > Invalid parameters will cause task execution to fail with detailed error messages.
397
+ ### Complex Validation Errors
264
398
 
265
399
  ```ruby
266
- class ValidateUserTask < CMDx::Task
400
+ class ValidationErrorTask < CMDx::Task
401
+ required :email, format: { with: /@/ }
267
402
  required :age, type: :integer, numeric: { min: 18, max: 120 }
268
- required :email, type: :string, format: { with: /@/ }
269
- optional :phone, type: :string, format: { with: /\A\d{10}\z/ }
403
+ optional :phone, format: { with: /\A\d{10}\z/ }
404
+
405
+ required :preferences do
406
+ required :theme, inclusion: { in: %w[light dark] }
407
+ optional :language, inclusion: { in: %w[en es fr] }
408
+ end
270
409
 
271
410
  def call
272
- # Task logic here
411
+ # Task logic
273
412
  end
274
413
  end
275
414
 
276
- # Invalid parameters
277
- result = ValidateUserTask.call(
278
- age: "invalid",
279
- email: "not-an-email",
280
- phone: "123"
415
+ # Multiple validation failures
416
+ result = ValidationErrorTask.call(
417
+ email: "invalid-email",
418
+ age: "not-a-number",
419
+ phone: "123",
420
+ preferences: {
421
+ theme: "purple",
422
+ language: "invalid"
423
+ }
281
424
  )
282
425
 
283
- result.failed? #=> true
426
+ result.failed? # true
284
427
  result.metadata
285
- #=> {
286
- # reason: "age could not coerce into an integer. email format is not valid. phone format is not valid.",
287
- # messages: {
288
- # age: ["could not coerce into an integer"],
289
- # email: ["format is not valid"],
290
- # phone: ["format is not valid"]
291
- # }
428
+ # {
429
+ # reason: "email format is not valid. age could not coerce into an integer. phone format is not valid. theme purple is not included in the list. language invalid is not included in the list.",
430
+ # messages: {
431
+ # email: ["format is not valid"],
432
+ # age: ["could not coerce into an integer"],
433
+ # phone: ["format is not valid"],
434
+ # theme: ["purple is not included in the list"],
435
+ # language: ["invalid is not included in the list"]
292
436
  # }
437
+ # }
293
438
  ```
294
439
 
440
+ > [!TIP]
441
+ > Parameter validation occurs before the `call` method executes, so you can rely on parameter presence and types within your task logic.
442
+
295
443
  ---
296
444
 
297
445
  - **Prev:** [Configuration](../configuration.md)