cmdx 1.0.1 → 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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,8 +1,6 @@
1
1
  # Interruptions - Halt
2
2
 
3
- Halting stops execution of a task with explicit intent signaling. Tasks provide
4
- two primary halt methods that control execution flow and result in different
5
- outcomes, each serving specific use cases in business logic.
3
+ Halting stops execution of a task with explicit intent signaling. Tasks provide two primary halt methods that control execution flow and result in different outcomes, each serving specific use cases in business logic.
6
4
 
7
5
  ## Table of Contents
8
6
 
@@ -12,147 +10,175 @@ outcomes, each serving specific use cases in business logic.
12
10
  - [Metadata Enrichment](#metadata-enrichment)
13
11
  - [State Transitions](#state-transitions)
14
12
  - [Exception Behavior](#exception-behavior)
13
+ - [Error Handling](#error-handling)
15
14
  - [The Reason Key](#the-reason-key)
16
15
 
17
16
  ## TLDR
18
17
 
19
- - **`skip!`** - Controlled interruption when task shouldn't execute (not an error)
20
- - **`fail!`** - Controlled interruption when task encounters an error condition
21
- - **Metadata** - Both methods accept metadata hash: `skip!(reason: "...", error_code: "...")`
22
- - **State changes** - Both transition to `interrupted` state, `skipped` or `failed` status
23
- - **Exception behavior** - `call` returns results, `call!` raises `CMDx::Skipped/Failed` based on task_halt config
18
+ ```ruby
19
+ # Skip when task shouldn't execute (not an error)
20
+ skip!(reason: "Order already processed")
21
+
22
+ # Fail when task encounters error condition
23
+ fail!(reason: "Insufficient funds", error_code: "PAYMENT_DECLINED")
24
+
25
+ # With structured metadata
26
+ skip!(
27
+ reason: "User inactive",
28
+ user_id: 123,
29
+ last_active: "2023-01-01"
30
+ )
31
+
32
+ # Exception behavior with call vs call!
33
+ result = Task.call(params) # Returns result object
34
+ Task.call!(params) # Raises CMDx::Skipped/Failed on halt
35
+ ```
24
36
 
25
37
  ## Skip (`skip!`)
26
38
 
27
- The `skip!` method indicates that a task did not meet the criteria to continue
28
- execution. This represents a controlled, intentional interruption where the
29
- task determines that execution is not necessary or appropriate under current
30
- conditions.
39
+ > [!NOTE]
40
+ > Use `skip!` when a task cannot or should not execute under current conditions, but this is not an error. Skipped tasks are considered successful outcomes.
41
+
42
+ The `skip!` method indicates that a task did not meet the criteria to continue execution. This represents a controlled, intentional interruption where the task determines that execution is not necessary or appropriate.
31
43
 
32
44
  ### Basic Usage
33
45
 
34
46
  ```ruby
35
- class ProcessUserOrderTask < CMDx::Task
47
+ class ProcessOrderTask < CMDx::Task
48
+ required :order_id, type: :integer
36
49
 
37
50
  def call
38
- context.order = Order.find(context.order_id)
51
+ context.order = Order.find(order_id)
39
52
 
40
- # Skip if order is already processed
53
+ # Skip if order already processed
41
54
  skip!(reason: "Order already processed") if context.order.processed?
42
55
 
43
- # Skip if prerequisites aren't met
44
- skip!(reason: "Payment method not configured") unless context.order.payment_method
56
+ # Skip if prerequisites not met
57
+ skip!(reason: "Payment method required") unless context.order.payment_method
45
58
 
46
59
  # Continue with business logic
47
60
  context.order.process!
48
61
  end
49
-
50
62
  end
51
63
  ```
52
64
 
53
- > [!NOTE]
54
- > Use `skip!` when a task cannot or should not execute under current conditions, but this is not an error. Skipped tasks are considered successful outcomes.
65
+ ### Common Skip Scenarios
66
+
67
+ | Scenario | Example |
68
+ |----------|---------|
69
+ | **Already processed** | `skip!(reason: "User already verified")` |
70
+ | **Prerequisites missing** | `skip!(reason: "Required documents not uploaded")` |
71
+ | **Business rules** | `skip!(reason: "Outside business hours")` |
72
+ | **State conditions** | `skip!(reason: "Account suspended")` |
55
73
 
56
74
  ## Fail (`fail!`)
57
75
 
58
- The `fail!` method indicates that a task encountered an error condition that
59
- prevents successful completion. This represents controlled failure where the
60
- task explicitly determines that execution cannot continue successfully.
76
+ > [!IMPORTANT]
77
+ > Use `fail!` when a task encounters an error that prevents successful completion. Failed tasks represent error conditions that need to be handled or corrected.
78
+
79
+ The `fail!` method indicates that a task encountered an error condition that prevents successful completion. This represents controlled failure where the task explicitly determines that execution cannot continue.
61
80
 
62
81
  ### Basic Usage
63
82
 
64
83
  ```ruby
65
- class ProcessOrderPaymentTask < CMDx::Task
84
+ class ProcessPaymentTask < CMDx::Task
85
+ required :payment_id, type: :integer
66
86
 
67
87
  def call
68
- context.payment = Payment.find(context.payment_id)
88
+ context.payment = Payment.find(payment_id)
69
89
 
70
90
  # Fail on validation errors
71
91
  fail!(reason: "Payment amount must be positive") unless context.payment.amount > 0
72
92
 
73
93
  # Fail on business rule violations
74
- fail!(reason: "Insufficient funds") unless sufficient_funds?
94
+ fail!(reason: "Insufficient funds", code: "INSUFFICIENT_FUNDS") unless sufficient_funds?
75
95
 
76
96
  # Continue with processing
77
- process_payment
97
+ charge_payment
78
98
  end
79
99
 
100
+ private
101
+
102
+ def sufficient_funds?
103
+ context.payment.account.balance >= context.payment.amount
104
+ end
80
105
  end
81
106
  ```
82
107
 
83
- > [!IMPORTANT]
84
- > Use `fail!` when a task encounters an error that prevents successful completion. Failed tasks represent error conditions that need to be handled or corrected.
108
+ ### Common Fail Scenarios
109
+
110
+ | Scenario | Example |
111
+ |----------|---------|
112
+ | **Validation errors** | `fail!(reason: "Invalid email format")` |
113
+ | **Business rule violations** | `fail!(reason: "Credit limit exceeded")` |
114
+ | **External service errors** | `fail!(reason: "Payment gateway unavailable")` |
115
+ | **Data integrity issues** | `fail!(reason: "Duplicate transaction detected")` |
85
116
 
86
117
  ## Metadata Enrichment
87
118
 
88
- Both halt methods accept metadata to provide context about the interruption.
89
- Metadata is stored as a hash and becomes available through the result object.
119
+ Both halt methods accept metadata to provide context about the interruption. Metadata is stored as a hash and becomes available through the result object.
90
120
 
91
121
  ### Structured Metadata
92
122
 
93
123
  ```ruby
94
- class ProcessUserOrderTask < CMDx::Task
124
+ class ProcessSubscriptionTask < CMDx::Task
125
+ required :user_id, type: :integer
95
126
 
96
127
  def call
97
- context.order = Order.find(context.order_id)
128
+ context.user = User.find(user_id)
98
129
 
99
- if context.order.status == "cancelled"
130
+ if context.user.subscription_expired?
100
131
  skip!(
101
- reason: "Order was cancelled",
102
- order_id: context.order.id,
103
- cancelled_at: context.order.cancelled_at,
104
- reason_code: context.order.cancellation_reason
132
+ reason: "Subscription expired",
133
+ user_id: context.user.id,
134
+ expired_at: context.user.subscription_expires_at,
135
+ plan_type: context.user.subscription_plan,
136
+ grace_period_ends: context.user.subscription_expires_at + 7.days
105
137
  )
106
138
  end
107
139
 
108
- unless inventory_available?
140
+ unless context.user.payment_method_valid?
109
141
  fail!(
110
- reason: "Insufficient inventory",
111
- required_quantity: context.order.quantity,
112
- available_quantity: current_inventory,
113
- restock_date: estimated_restock_date,
114
- error_code: "INVENTORY_DEPLETED"
142
+ reason: "Invalid payment method",
143
+ user_id: context.user.id,
144
+ payment_method_id: context.user.payment_method&.id,
145
+ error_code: "PAYMENT_METHOD_INVALID",
146
+ retry_after: Time.current + 1.hour
115
147
  )
116
148
  end
117
149
 
118
- process_order
150
+ process_subscription
119
151
  end
120
-
121
152
  end
122
153
  ```
123
154
 
124
155
  ### Accessing Metadata
125
156
 
126
157
  ```ruby
127
- result = ProcessUserOrderTask.call(order_id: 123)
158
+ result = ProcessSubscriptionTask.call(user_id: 123)
128
159
 
129
160
  # Check result status
130
- result.skipped? #=> true
131
- result.failed? #=> false
161
+ result.skipped? #=> true
162
+ result.failed? #=> false
132
163
 
133
164
  # Access metadata
134
- result.metadata[:reason] #=> "Order was cancelled"
135
- result.metadata[:order_id] #=> 123
136
- result.metadata[:cancelled_at] #=> 2023-01-01 10:00:00 UTC
137
- result.metadata[:reason_code] #=> "customer_request"
165
+ result.metadata[:reason] #=> "Subscription expired"
166
+ result.metadata[:user_id] #=> 123
167
+ result.metadata[:expired_at] #=> 2023-01-01 10:00:00 UTC
168
+ result.metadata[:grace_period_ends] #=> 2023-01-08 10:00:00 UTC
138
169
  ```
139
170
 
140
171
  ## State Transitions
141
172
 
142
173
  Halt methods trigger specific state and status transitions:
143
174
 
144
- ### Skip Transitions
145
- - **State**: `initialized` → `executing` → `interrupted`
146
- - **Status**: `success` → `skipped`
147
- - **Result**: `good? = true`, `bad? = true`
148
-
149
- ### Fail Transitions
150
- - **State**: `initialized` → `executing` → `interrupted`
151
- - **Status**: `success` → `failed`
152
- - **Result**: `good? = false`, `bad? = true`
175
+ | Method | State Transition | Status | Outcome |
176
+ |--------|------------------|--------|---------|
177
+ | `skip!` | `executing` → `interrupted` | `skipped` | `good? = true`, `bad? = true` |
178
+ | `fail!` | `executing` → `interrupted` | `failed` | `good? = false`, `bad? = true` |
153
179
 
154
180
  ```ruby
155
- result = ProcessUserOrderTask.call(order_id: 123)
181
+ result = ProcessSubscriptionTask.call(user_id: 123)
156
182
 
157
183
  # State information
158
184
  result.state #=> "interrupted"
@@ -170,42 +196,63 @@ result.bad? #=> true for both skipped and failed
170
196
  Halt methods behave differently depending on the call method used:
171
197
 
172
198
  ### With `call` (Non-bang)
199
+
173
200
  Returns a result object without raising exceptions:
174
201
 
175
202
  ```ruby
176
- result = ProcessUserOrderTask.call(order_id: 123)
203
+ result = ProcessPaymentTask.call(payment_id: 123)
177
204
 
178
205
  case result.status
179
206
  when "success"
180
- puts "Order processed successfully"
207
+ puts "Payment processed: $#{result.context.payment.amount}"
181
208
  when "skipped"
182
- puts "Order skipped: #{result.metadata[:reason]}"
209
+ puts "Payment skipped: #{result.metadata[:reason]}"
183
210
  when "failed"
184
- puts "Order failed: #{result.metadata[:reason]}"
211
+ puts "Payment failed: #{result.metadata[:reason]}"
212
+ handle_payment_error(result.metadata[:code])
185
213
  end
186
214
  ```
187
215
 
188
216
  ### With `call!` (Bang)
189
- Raises fault exceptions based on `task_halt` configuration:
217
+
218
+ > [!WARNING]
219
+ > The `call!` method raises exceptions for halt conditions based on the `task_halt` configuration. Handle these exceptions appropriately in your application flow.
190
220
 
191
221
  ```ruby
192
222
  begin
193
- result = ProcessUserOrderTask.call!(order_id: 123)
194
- puts "Success: #{result.context.order.id}"
223
+ result = ProcessPaymentTask.call!(payment_id: 123)
224
+ puts "Success: Payment processed for $#{result.context.payment.amount}"
195
225
  rescue CMDx::Skipped => e
196
226
  puts "Skipped: #{e.message}"
197
- puts "Order ID: #{e.context.order_id}"
227
+ log_skip_event(e.context.payment_id, e.result.metadata)
198
228
  rescue CMDx::Failed => e
199
229
  puts "Failed: #{e.message}"
200
- puts "Error code: #{e.result.metadata[:error_code]}"
230
+ handle_payment_failure(e.result.metadata[:code])
231
+ notify_payment_team(e.context.payment_id)
201
232
  end
202
233
  ```
203
234
 
204
- > [!WARNING]
205
- > The `call!` method raises exceptions for halt conditions based on the `task_halt` configuration. The `call` method always returns result objects without raising exceptions.
235
+ ## Error Handling
236
+
237
+ ### Invalid Metadata
238
+
239
+ ```ruby
240
+ class ProcessOrderTask < CMDx::Task
241
+ def call
242
+ # This works - metadata accepts any hash
243
+ skip!(reason: "Valid skip", order_id: 123, custom_data: {nested: true})
244
+
245
+ # This also works - no metadata required
246
+ fail!
247
+ end
248
+ end
249
+ ```
206
250
 
207
251
  ## The Reason Key
208
252
 
253
+ > [!TIP]
254
+ > Always include a `:reason` key in metadata when using halt methods. This provides clear context for debugging and creates meaningful exception messages.
255
+
209
256
  The `:reason` key in metadata has special significance:
210
257
 
211
258
  - Used as the exception message when faults are raised
@@ -213,19 +260,26 @@ The `:reason` key in metadata has special significance:
213
260
  - Strongly recommended for all halt calls
214
261
 
215
262
  ```ruby
216
- # Good: Provides clear reason
217
- skip!(reason: "User already has an active session")
218
- fail!(reason: "Credit card expired", code: "EXPIRED_CARD")
263
+ # Good: Clear, specific reason
264
+ skip!(reason: "User account suspended until manual review")
265
+ fail!(reason: "Credit card declined by issuer", code: "CARD_DECLINED")
219
266
 
220
267
  # Acceptable: Other metadata without reason
221
- skip!(status: "redundant", timestamp: Time.now)
268
+ skip!(status: "redundant", processed_at: Time.current)
222
269
 
223
270
  # Fallback: Default message if no reason provided
224
271
  skip! # Exception message: "no reason given"
272
+ fail! # Exception message: "no reason given"
225
273
  ```
226
274
 
227
- > [!TIP]
228
- > Always try to include a `:reason` key in metadata when using halt methods. This provides clear context for debugging and creates meaningful exception messages when using `call!`.
275
+ ### Reason Best Practices
276
+
277
+ | Practice | Example |
278
+ |----------|---------|
279
+ | **Be specific** | `"Credit card expired on 2023-12-31"` vs `"Payment error"` |
280
+ | **Include context** | `"Inventory insufficient: need 5, have 2"` |
281
+ | **Use actionable language** | `"Email verification required before login"` |
282
+ | **Avoid technical jargon** | `"Payment declined"` vs `"Gateway returned 402"` |
229
283
 
230
284
  ---
231
285