cmdx 1.1.2 → 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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +5 -133
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +91 -154
  85. data/lib/cmdx/validators/numeric.rb +87 -162
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
@@ -1,668 +1,32 @@
1
1
  # Internationalization (i18n)
2
2
 
3
- CMDx provides comprehensive internationalization support for all error messages, parameter validation failures, coercion errors, and fault messages. All user-facing text is automatically localized based on the current `I18n.locale`, ensuring your applications can serve global audiences with native-language error reporting.
3
+ CMDx provides comprehensive internationalization support for all error messages, attribute validation failures, coercion errors, and fault messages. All user-facing text is automatically localized based on the current `I18n.locale`, ensuring your applications can serve global audiences with native-language error reporting.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- - [TLDR](#tldr)
8
- - [Available Locales](#available-locales)
9
- - [Configuration](#configuration)
10
- - [Fault Messages](#fault-messages)
11
- - [Parameter Messages](#parameter-messages)
12
- - [Coercion Messages](#coercion-messages)
13
- - [Validation Messages](#validation-messages)
14
- - [Custom Message Overrides](#custom-message-overrides)
15
- - [Error Handling and Debugging](#error-handling-and-debugging)
7
+ - [Localization](#localization)
16
8
 
17
- ## TLDR
18
-
19
- ```ruby
20
- # Automatic localization based on I18n.locale
21
- I18n.locale = :es
22
- result = CreateUserTask.call(email: "invalid", age: "too-young")
23
- result.metadata[:messages][:email] #=> ["formato inválido"]
24
-
25
- # 24 built-in languages with complete coverage
26
- # Parameter-specific overrides available
27
- # Covers coercion, validation, and fault messages
28
- ```
9
+ ## Localization
29
10
 
30
11
  > [!NOTE]
31
- > CMDx automatically localizes all error messages based on your application's `I18n.locale` setting. No additional configuration is required for basic usage.
32
-
33
- ## Available Locales
34
-
35
- CMDx includes built-in translations for 24 major world languages, covering both Western and Eastern language families:
36
-
37
- | Language | Locale | Language | Locale | Language | Locale |
38
- |----------|--------|----------|--------|----------|--------|
39
- | English | `:en` | Russian | `:ru` | Arabic | `:ar` |
40
- | Spanish | `:es` | Korean | `:ko` | Dutch | `:nl` |
41
- | French | `:fr` | Hindi | `:hi` | Swedish | `:sv` |
42
- | German | `:de` | Polish | `:pl` | Norwegian | `:no` |
43
- | Portuguese | `:pt` | Turkish | `:tr` | Finnish | `:fi` |
44
- | Italian | `:it` | Danish | `:da` | Greek | `:el` |
45
- | Japanese | `:ja` | Czech | `:cs` | Hebrew | `:he` |
46
- | Chinese | `:zh` | Thai | `:th` | Vietnamese | `:vi` |
47
-
48
- > [!TIP]
49
- > All locales provide complete coverage for every error message type, including complex nested parameter validation errors and multi-type coercion failures.
50
-
51
- ## Configuration
52
-
53
- ### Basic Setup
12
+ > CMDx automatically localizes all error messages based on the `I18n.locale` setting.
54
13
 
55
14
  ```ruby
56
- # In Rails applications (config/application.rb)
57
- config.i18n.default_locale = :en
58
- config.i18n.available_locales = [:en, :es, :fr, :de]
15
+ class ProcessQuote < CMDx::Task
16
+ attribute :price, type: :float
59
17
 
60
- # Runtime locale switching
61
- class ApiController < ApplicationController
62
- before_action :set_locale
63
-
64
- private
65
-
66
- def set_locale
67
- I18n.locale = params[:locale] || request.headers['Accept-Language']&.scan(/^[a-z]{2}/)&.first || :en
18
+ def work
19
+ # Your logic here...
68
20
  end
69
21
  end
70
- ```
71
22
 
72
- ### Per-Request Localization
73
-
74
- ```ruby
75
- class ProcessOrderTask < CMDx::Task
76
- required :amount, type: :float
77
- required :customer_email, format: { with: /@/ }
78
-
79
- def call
80
- # Task logic runs with current I18n.locale
81
- ChargeCustomer.call(amount: amount, email: customer_email)
82
- end
83
- end
84
-
85
- # Different locales produce localized errors
86
23
  I18n.with_locale(:fr) do
87
- result = ProcessOrderTask.call(amount: "invalid", customer_email: "bad-email")
88
- result.metadata[:messages][:amount] #=> ["impossible de contraindre en float"]
89
- end
90
- ```
91
-
92
- ## Fault Messages
93
-
94
- > [!IMPORTANT]
95
- > Fault messages from `fail!` and `skip!` methods are automatically localized when no explicit reason is provided.
96
-
97
- ### Default Fault Localization
98
-
99
- ```ruby
100
- class ProcessPaymentTask < CMDx::Task
101
- required :payment_method, inclusion: { in: %w[card paypal bank] }
102
- required :amount, type: :float
103
-
104
- def call
105
- if payment_declined?
106
- fail! # Uses localized default message
107
- end
108
-
109
- if amount < minimum_charge
110
- skip! # Uses localized default message
111
- end
112
-
113
- charge_payment
114
- end
115
-
116
- private
117
-
118
- def payment_declined?
119
- # Payment gateway logic
120
- rand > 0.8
121
- end
122
-
123
- def minimum_charge
124
- 5.00
125
- end
126
- end
127
-
128
- # English
129
- I18n.locale = :en
130
- result = ProcessPaymentTask.call(payment_method: "card", amount: 99.99)
131
- result.metadata[:reason] #=> "no reason given"
132
-
133
- # Spanish
134
- I18n.locale = :es
135
- result = ProcessPaymentTask.call(payment_method: "card", amount: 99.99)
136
- result.metadata[:reason] #=> "no se proporcionó razón"
137
-
138
- # Japanese
139
- I18n.locale = :ja
140
- result = ProcessPaymentTask.call(payment_method: "card", amount: 99.99)
141
- result.metadata[:reason] #=> "理由が提供されませんでした"
142
- ```
143
-
144
- ### Custom Fault Messages
145
-
146
- ```ruby
147
- class ProcessRefundTask < CMDx::Task
148
- required :order_id, type: :integer
149
- required :reason, presence: true
150
-
151
- def call
152
- order = find_order(order_id)
153
-
154
- # Custom messages override locale defaults
155
- fail!("Payment gateway unavailable") if gateway_down?
156
- skip!("Refund already processed") if order.refunded?
157
-
158
- process_refund(order)
159
- end
160
- end
161
- ```
162
-
163
- ## Parameter Messages
164
-
165
- > [!WARNING]
166
- > Parameter errors include both missing required parameters and undefined source method delegates, with full internationalization support.
167
-
168
- ### Required Parameter Errors
169
-
170
- ```ruby
171
- class CreateUserAccountTask < CMDx::Task
172
- required :email, format: { with: /@/ }
173
- required :password, length: { min: 8 }
174
- required :age, type: :integer, numeric: { min: 18 }
175
-
176
- optional :profile_image, source: :nonexistent_upload_method
177
- optional :referral_code, source: :missing_referral_source
178
-
179
- def call
180
- User.create!(email: email, password: password, age: age)
181
- end
182
- end
183
-
184
- # Missing required parameters
185
- I18n.locale = :en
186
- result = CreateUserAccountTask.call({})
187
- result.metadata[:messages]
188
- # {
189
- # email: ["is a required parameter"],
190
- # password: ["is a required parameter"],
191
- # age: ["is a required parameter"]
192
- # }
193
-
194
- # German localization
195
- I18n.locale = :de
196
- result = CreateUserAccountTask.call({})
197
- result.metadata[:messages]
198
- # {
199
- # email: ["ist ein erforderlicher Parameter"],
200
- # password: ["ist ein erforderlicher Parameter"],
201
- # age: ["ist ein erforderlicher Parameter"]
202
- # }
203
- ```
204
-
205
- ### Source Method Errors
206
-
207
- ```ruby
208
- # Undefined source method delegation
209
- I18n.locale = :en
210
- result = CreateUserAccountTask.call(
211
- email: "user@example.com",
212
- password: "securepass",
213
- age: 25
214
- )
215
- result.metadata[:messages]
216
- # {
217
- # profile_image: ["delegates to undefined method nonexistent_upload_method"],
218
- # referral_code: ["delegates to undefined method missing_referral_source"]
219
- # }
220
-
221
- # French localization
222
- I18n.locale = :fr
223
- result = CreateUserAccountTask.call(
224
- email: "user@example.com",
225
- password: "securepass",
226
- age: 25
227
- )
228
- result.metadata[:messages]
229
- # {
230
- # profile_image: ["délègue à la méthode non définie nonexistent_upload_method"],
231
- # referral_code: ["délègue à la méthode non définie missing_referral_source"]
232
- # }
233
- ```
234
-
235
- ## Coercion Messages
236
-
237
- > [!NOTE]
238
- > Type conversion failures provide detailed, localized error messages that specify the attempted type(s) and input value context.
239
-
240
- ### Single Type Coercion Errors
241
-
242
- ```ruby
243
- class ProcessInventoryTask < CMDx::Task
244
- required :product_id, type: :integer
245
- required :price, type: :float
246
- required :in_stock, type: :boolean
247
- required :categories, type: :array
248
- required :metadata, type: :hash
249
-
250
- def call
251
- # Task implementation
252
- end
253
- end
254
-
255
- # English coercion errors
256
- I18n.locale = :en
257
- result = ProcessInventoryTask.call(
258
- product_id: "not-a-number",
259
- price: "invalid-price",
260
- in_stock: "maybe",
261
- categories: "[invalid json",
262
- metadata: "not-a-hash"
263
- )
264
-
265
- result.metadata[:messages]
266
- # {
267
- # product_id: ["could not coerce into an integer"],
268
- # price: ["could not coerce into a float"],
269
- # in_stock: ["could not coerce into a boolean"],
270
- # categories: ["could not coerce into an array"],
271
- # metadata: ["could not coerce into a hash"]
272
- # }
273
-
274
- # Spanish coercion errors
275
- I18n.locale = :es
276
- result = ProcessInventoryTask.call(
277
- product_id: "not-a-number",
278
- price: "invalid-price"
279
- )
280
-
281
- result.metadata[:messages]
282
- # {
283
- # product_id: ["no se pudo coaccionar a un integer"],
284
- # price: ["no se pudo coaccionar a un float"]
285
- # }
286
- ```
287
-
288
- ### Multiple Type Coercion Errors
289
-
290
- ```ruby
291
- class ProcessFlexibleDataTask < CMDx::Task
292
- required :amount, type: [:float, :big_decimal, :integer]
293
- required :identifier, type: [:integer, :string]
294
- required :timestamp, type: [:datetime, :date, :time]
295
-
296
- def call
297
- # Task implementation
298
- end
299
- end
300
-
301
- # Multiple type failure messages
302
- I18n.locale = :en
303
- result = ProcessFlexibleDataTask.call(
304
- amount: "definitely-not-numeric",
305
- identifier: nil,
306
- timestamp: "not-a-date"
307
- )
308
-
309
- result.metadata[:messages]
310
- # {
311
- # amount: ["could not coerce into one of: float, big_decimal, integer"],
312
- # identifier: ["could not coerce into one of: integer, string"],
313
- # timestamp: ["could not coerce into one of: datetime, date, time"]
314
- # }
315
-
316
- # Chinese localization
317
- I18n.locale = :zh
318
- result = ProcessFlexibleDataTask.call(amount: "invalid")
319
- result.metadata[:messages][:amount] #=> ["无法强制转换为以下类型之一:float、big_decimal、integer"]
320
- ```
321
-
322
- ### Nested Parameter Coercion
323
-
324
- ```ruby
325
- class ProcessOrderTask < CMDx::Task
326
- required :order, type: :hash do
327
- required :id, type: :integer
328
- required :total, type: :float
329
-
330
- required :customer, type: :hash do
331
- required :id, type: :integer
332
- required :active, type: :boolean
333
- end
334
- end
335
-
336
- def call
337
- # Task implementation
338
- end
339
- end
340
-
341
- # Nested coercion errors with full path context
342
- result = ProcessOrderTask.call(
343
- order: {
344
- id: "not-a-number",
345
- total: "invalid-amount",
346
- customer: {
347
- id: "bad-id",
348
- active: "maybe"
349
- }
350
- }
351
- )
352
-
353
- result.metadata[:messages]
354
- # {
355
- # "order.id": ["could not coerce into an integer"],
356
- # "order.total": ["could not coerce into a float"],
357
- # "order.customer.id": ["could not coerce into an integer"],
358
- # "order.customer.active": ["could not coerce into a boolean"]
359
- # }
360
- ```
361
-
362
- ## Validation Messages
363
-
364
- > [!TIP]
365
- > All built-in validators provide comprehensive internationalization support, including contextual information for complex validation rules.
366
-
367
- ### Format Validation
368
-
369
- ```ruby
370
- class CreateUserTask < CMDx::Task
371
- required :email, format: { with: /@/, message: nil } # Use default i18n
372
- required :phone, format: { with: /\A\+?[\d\s-()]+\z/ }
373
- required :username, format: { with: /\A[a-zA-Z0-9_]+\z/ }
374
-
375
- def call
376
- User.create!(email: email, phone: phone, username: username)
377
- end
378
- end
379
-
380
- # English format errors
381
- I18n.locale = :en
382
- result = CreateUserTask.call(
383
- email: "not-an-email",
384
- phone: "invalid!phone",
385
- username: "bad@username"
386
- )
387
-
388
- result.metadata[:messages]
389
- # {
390
- # email: ["is an invalid format"],
391
- # phone: ["is an invalid format"],
392
- # username: ["is an invalid format"]
393
- # }
394
-
395
- # Japanese format errors
396
- I18n.locale = :ja
397
- result = CreateUserTask.call(email: "invalid", phone: "bad")
398
- result.metadata[:messages]
399
- # {
400
- # email: ["無効な形式です"],
401
- # phone: ["無効な形式です"]
402
- # }
403
- ```
404
-
405
- ### Numeric Validation
406
-
407
- ```ruby
408
- class ConfigureServiceTask < CMDx::Task
409
- required :port, numeric: { min: 1024, max: 65535 }
410
- required :timeout, numeric: { greater_than: 0, less_than: 300 }
411
- required :retry_count, numeric: { min: 1, max: 10 }
412
-
413
- def call
414
- # Service configuration
415
- end
416
- end
417
-
418
- # English numeric errors
419
- I18n.locale = :en
420
- result = ConfigureServiceTask.call(
421
- port: 80, # Below minimum
422
- timeout: 500, # Above maximum
423
- retry_count: 0 # Below minimum
424
- )
425
-
426
- result.metadata[:messages]
427
- # {
428
- # port: ["must be greater than or equal to 1024"],
429
- # timeout: ["must be less than 300"],
430
- # retry_count: ["must be greater than or equal to 1"]
431
- # }
432
-
433
- # German numeric errors
434
- I18n.locale = :de
435
- result = ConfigureServiceTask.call(port: 80, timeout: 500)
436
- result.metadata[:messages]
437
- # {
438
- # port: ["muss größer oder gleich 1024 sein"],
439
- # timeout: ["muss kleiner als 300 sein"]
440
- # }
441
- ```
442
-
443
- ### Inclusion and Exclusion
444
-
445
- ```ruby
446
- class ProcessSubscriptionTask < CMDx::Task
447
- required :plan, inclusion: { in: %w[basic premium enterprise] }
448
- required :billing_cycle, inclusion: { in: %w[monthly yearly] }
449
- required :username, exclusion: { from: %w[admin root system] }
450
-
451
- def call
452
- # Subscription processing
453
- end
454
- end
455
-
456
- # English inclusion/exclusion errors
457
- I18n.locale = :en
458
- result = ProcessSubscriptionTask.call(
459
- plan: "invalid-plan",
460
- billing_cycle: "weekly",
461
- username: "admin"
462
- )
463
-
464
- result.metadata[:messages]
465
- # {
466
- # plan: ["is not included in the list"],
467
- # billing_cycle: ["is not included in the list"],
468
- # username: ["is reserved"]
469
- # }
470
-
471
- # French inclusion/exclusion errors
472
- I18n.locale = :fr
473
- result = ProcessSubscriptionTask.call(plan: "invalid", username: "root")
474
- result.metadata[:messages]
475
- # {
476
- # plan: ["n'est pas inclus dans la liste"],
477
- # username: ["est réservé"]
478
- # }
479
- ```
480
-
481
- ### Length Validation
482
-
483
- ```ruby
484
- class CreatePostTask < CMDx::Task
485
- required :title, length: { min: 5, max: 100 }
486
- required :content, length: { min: 50 }
487
- required :tags, length: { max: 10 }
488
-
489
- def call
490
- Post.create!(title: title, content: content, tags: tags)
491
- end
492
- end
493
-
494
- # English length errors
495
- I18n.locale = :en
496
- result = CreatePostTask.call(
497
- title: "Hi", # Too short
498
- content: "Brief content", # Too short
499
- tags: (1..15).to_a # Too many
500
- )
501
-
502
- result.metadata[:messages]
503
- # {
504
- # title: ["is too short (minimum is 5 characters)"],
505
- # content: ["is too short (minimum is 50 characters)"],
506
- # tags: ["is too long (maximum is 10 characters)"]
507
- # }
508
-
509
- # Russian length errors
510
- I18n.locale = :ru
511
- result = CreatePostTask.call(title: "Hi", content: "Short")
512
- result.metadata[:messages]
513
- # {
514
- # title: ["слишком короткий (минимум 5 символов)"],
515
- # content: ["слишком короткий (минимум 50 символов)"]
516
- # }
517
- ```
518
-
519
- ## Custom Message Overrides
520
-
521
- > [!IMPORTANT]
522
- > Parameter-specific custom messages always take precedence over locale defaults, allowing fine-grained control while maintaining i18n support.
523
-
524
- ### Override Examples
525
-
526
- ```ruby
527
- class RegisterAccountTask < CMDx::Task
528
- required :email,
529
- format: { with: /@/, message: "Please provide a valid email address" }
530
-
531
- required :password,
532
- length: { min: 8, message: "Password must be at least 8 characters" }
533
-
534
- required :age,
535
- numeric: { min: 18, message: "You must be 18 or older to register" }
536
-
537
- def call
538
- # Custom messages override i18n, regardless of locale
539
- end
540
- end
541
-
542
- # Custom messages ignore locale settings
543
- I18n.locale = :es
544
- result = RegisterAccountTask.call(
545
- email: "invalid",
546
- password: "short",
547
- age: 16
548
- )
549
-
550
- result.metadata[:messages]
551
- # {
552
- # email: ["Please provide a valid email address"], # Custom override
553
- # password: ["Password must be at least 8 characters"], # Custom override
554
- # age: ["You must be 18 or older to register"] # Custom override
555
- # }
556
- ```
557
-
558
- ### Conditional Overrides
559
-
560
- ```ruby
561
- class ProcessPaymentTask < CMDx::Task
562
- required :amount, type: :float, numeric: { min: 0.01 }
563
-
564
- # Conditional message based on context
565
- def validate_amount
566
- if context[:currency] == "USD" && amount < 0.50
567
- add_error(:amount, "USD payments must be at least $0.50")
568
- elsif context[:currency] == "EUR" && amount < 0.01
569
- add_error(:amount, "EUR payments must be at least €0.01")
570
- end
571
- end
572
-
573
- def call
574
- validate_amount
575
- # Payment processing
576
- end
577
- end
578
- ```
579
-
580
- ## Error Handling and Debugging
581
-
582
- > [!WARNING]
583
- > When debugging i18n issues, check locale availability, fallback behavior, and message key resolution to identify configuration problems.
584
-
585
- ### Debugging Missing Translations
586
-
587
- ```ruby
588
- # Enable translation debugging
589
- I18n.exception_handler = lambda do |exception, locale, key, options|
590
- Rails.logger.warn "Missing translation: #{locale}.#{key}"
591
- "translation missing: #{locale}.#{key}"
592
- end
593
-
594
- class DebuggingTask < CMDx::Task
595
- required :test_param, type: :integer
596
-
597
- def call
598
- # Intentionally trigger coercion error for debugging
599
- end
600
- end
601
-
602
- # Test with unsupported locale
603
- I18n.locale = :unsupported_locale
604
- result = DebuggingTask.call(test_param: "invalid")
605
- # Logs: "Missing translation: unsupported_locale.cmdx.errors.coercion.integer"
606
- ```
607
-
608
- ### Fallback Configuration
609
-
610
- ```ruby
611
- # Configure fallback behavior in Rails
612
- I18n.fallbacks = { es: [:es, :en], fr: [:fr, :en] }
613
-
614
- class TestLocalizationTask < CMDx::Task
615
- required :value, type: :integer
616
-
617
- def call
618
- # Task logic
619
- end
620
- end
621
-
622
- # Test fallback behavior
623
- I18n.locale = :es # Falls back to :en if Spanish translation missing
624
- result = TestLocalizationTask.call(value: "invalid")
625
- # Uses English if Spanish translation unavailable
626
- ```
627
-
628
- ### Error Message Analysis
629
-
630
- ```ruby
631
- class AnalyzeErrorsTask < CMDx::Task
632
- required :data, type: :hash do
633
- required :id, type: :integer
634
- required :nested, type: :hash do
635
- required :value, type: :float
636
- end
637
- end
638
-
639
- def call
640
- # Complex nested structure for testing
641
- end
642
- end
643
-
644
- # Comprehensive error analysis
645
- result = AnalyzeErrorsTask.call(
646
- data: {
647
- id: "not-integer",
648
- nested: {
649
- value: "not-float"
650
- }
651
- }
652
- )
653
-
654
- # Analyze error structure
655
- puts "Failed: #{result.failed?}"
656
- puts "Error count: #{result.metadata[:messages].count}"
657
- puts "Nested errors present: #{result.metadata[:messages].keys.any? { |k| k.include?('.') }}"
658
-
659
- result.metadata[:messages].each do |param, errors|
660
- puts "#{param}: #{errors.join(', ')}"
24
+ result = ProcessQuote.execute(price: "invalid")
25
+ result.metadata[:messages][:price] #=> ["impossible de contraindre en float"]
661
26
  end
662
- # Output shows full parameter path context for nested errors
663
27
  ```
664
28
 
665
29
  ---
666
30
 
667
31
  - **Prev:** [Logging](logging.md)
668
- - **Next:** [Testing](testing.md)
32
+ - **Next:** [Deprecation](deprecation.md)