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
@@ -1,232 +1,57 @@
|
|
1
1
|
# Interruptions - Exceptions
|
2
2
|
|
3
|
-
CMDx provides robust exception handling that differs between the `
|
3
|
+
CMDx provides robust exception handling that differs between the `execute` and `execute!` methods. Understanding how unhandled exceptions are processed is crucial for building reliable task execution flows and implementing proper error handling strategies.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
-
- [
|
8
|
-
- [
|
9
|
-
- [
|
10
|
-
- [Bang Call Behavior](#bang-call-behavior)
|
11
|
-
- [Exception Classification](#exception-classification)
|
12
|
-
- [Error Handling Patterns](#error-handling-patterns)
|
7
|
+
- [Exception Handling](#exception-handling)
|
8
|
+
- [Non-bang execution](#non-bang-execution)
|
9
|
+
- [Bang execution](#bang-execution)
|
13
10
|
|
14
|
-
##
|
15
|
-
|
16
|
-
```ruby
|
17
|
-
# Non-bang call - captures ALL exceptions
|
18
|
-
result = ProcessOrderTask.call # Never raises, always returns result
|
19
|
-
result.failed? # true if exception occurred
|
20
|
-
result.metadata[:original_exception] # Access original exception
|
21
|
-
|
22
|
-
# Bang call - lets exceptions propagate
|
23
|
-
ProcessOrderTask.call! # Raises exceptions (except configured faults)
|
24
|
-
|
25
|
-
# Exception info always available in metadata
|
26
|
-
result.metadata[:reason] # Human-readable error message
|
27
|
-
result.metadata[:original_exception] # Original exception object
|
28
|
-
```
|
29
|
-
|
30
|
-
## Exception Handling Methods
|
11
|
+
## Exception Handling
|
31
12
|
|
32
13
|
> [!IMPORTANT]
|
33
|
-
>
|
34
|
-
|
35
|
-
### Non-bang Call (`call`)
|
14
|
+
> When designing tasks try not to `raise` your own exceptions directly, instead use `skip!` or `fail!` to signal intent clearly.
|
36
15
|
|
37
|
-
|
16
|
+
### Non-bang execution
|
38
17
|
|
39
|
-
|
40
|
-
|----------|-------------|
|
41
|
-
| **Exception Capture** | All exceptions caught and converted |
|
42
|
-
| **Return Value** | Always returns a result object |
|
43
|
-
| **State** | `"interrupted"` for exception failures |
|
44
|
-
| **Status** | `"failed"` for all captured exceptions |
|
45
|
-
| **Metadata** | Exception details preserved |
|
18
|
+
The `execute` method captures **all** unhandled exceptions and converts them to failed results, ensuring predictable behavior and consistent result processing.
|
46
19
|
|
47
20
|
```ruby
|
48
|
-
class
|
49
|
-
def
|
50
|
-
|
21
|
+
class CompressDocument < CMDx::Task
|
22
|
+
def work
|
23
|
+
document = Document.find(context.document_id)
|
24
|
+
document.compress!
|
51
25
|
end
|
52
26
|
end
|
53
27
|
|
54
|
-
result =
|
28
|
+
result = CompressDocument.execute(document_id: "unknown-doc-id")
|
55
29
|
result.state #=> "interrupted"
|
56
30
|
result.status #=> "failed"
|
57
31
|
result.failed? #=> true
|
32
|
+
result.reason #=> "[ActiveRecord::NotFoundError] record not found"
|
33
|
+
result.cause #=> <ActiveRecord::NotFoundError>
|
58
34
|
```
|
59
35
|
|
60
|
-
### Bang
|
36
|
+
### Bang execution
|
61
37
|
|
62
|
-
The `
|
38
|
+
The `execute!` method allows unhandled exceptions to propagate, enabling standard Ruby exception handling while respecting CMDx fault configuration.
|
63
39
|
|
64
40
|
```ruby
|
65
|
-
class
|
66
|
-
def
|
67
|
-
|
41
|
+
class CompressDocument < CMDx::Task
|
42
|
+
def work
|
43
|
+
document = Document.find(context.document_id)
|
44
|
+
document.compress!
|
68
45
|
end
|
69
46
|
end
|
70
47
|
|
71
48
|
begin
|
72
|
-
|
73
|
-
rescue
|
49
|
+
CompressDocument.execute!(document_id: "unknown-doc-id")
|
50
|
+
rescue ActiveRecord::NotFoundError => e
|
74
51
|
puts "Handle exception: #{e.message}"
|
75
52
|
end
|
76
53
|
```
|
77
54
|
|
78
|
-
## Exception Metadata
|
79
|
-
|
80
|
-
> [!NOTE]
|
81
|
-
> Exception information is preserved in result metadata, providing full debugging context while maintaining clean result interfaces.
|
82
|
-
|
83
|
-
### Metadata Structure
|
84
|
-
|
85
|
-
```ruby
|
86
|
-
result = ProcessPaymentTask.call
|
87
|
-
|
88
|
-
# Exception metadata always includes:
|
89
|
-
result.metadata[:reason] #=> "[StandardError] Payment gateway unavailable"
|
90
|
-
result.metadata[:original_exception] #=> <StandardError instance>
|
91
|
-
|
92
|
-
# Access original exception properties
|
93
|
-
exception = result.metadata[:original_exception]
|
94
|
-
exception.class #=> StandardError
|
95
|
-
exception.message #=> "Payment gateway unavailable"
|
96
|
-
exception.backtrace #=> ["lib/tasks/payment.rb:15:in `call'", ...]
|
97
|
-
```
|
98
|
-
|
99
|
-
### Exception Type Checking
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
class DatabaseTask < CMDx::Task
|
103
|
-
def call
|
104
|
-
raise ActiveRecord::ConnectionNotEstablished, "Database unavailable"
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
result = DatabaseTask.call
|
109
|
-
|
110
|
-
if result.failed? && result.metadata[:original_exception]
|
111
|
-
case result.metadata[:original_exception]
|
112
|
-
when ActiveRecord::ConnectionNotEstablished
|
113
|
-
retry_with_fallback_database
|
114
|
-
when Net::TimeoutError
|
115
|
-
retry_with_increased_timeout
|
116
|
-
when StandardError
|
117
|
-
log_and_alert_administrators
|
118
|
-
end
|
119
|
-
end
|
120
|
-
```
|
121
|
-
|
122
|
-
## Bang Call Behavior
|
123
|
-
|
124
|
-
> [!WARNING]
|
125
|
-
> `call!` propagates exceptions immediately, bypassing result object creation. Only use when you need direct exception handling or integration with exception-based error handling systems.
|
126
|
-
|
127
|
-
### Fault vs Exception Handling
|
128
|
-
|
129
|
-
CMDx faults receive special treatment based on `task_halt` configuration:
|
130
|
-
|
131
|
-
```ruby
|
132
|
-
class ProcessOrderTask < CMDx::Task
|
133
|
-
cmd_settings!(task_halt: [CMDx::Result::FAILED])
|
134
|
-
|
135
|
-
def call
|
136
|
-
if context.payment_invalid
|
137
|
-
fail!(reason: "Invalid payment method") # CMDx fault
|
138
|
-
else
|
139
|
-
raise StandardError, "System error" # Regular exception
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
# Fault behavior (converted to exception due to task_halt)
|
145
|
-
begin
|
146
|
-
ProcessOrderTask.call!(payment_invalid: true)
|
147
|
-
rescue CMDx::Failed => e
|
148
|
-
puts "Controlled fault: #{e.message}"
|
149
|
-
end
|
150
|
-
|
151
|
-
# Exception behavior (propagates normally)
|
152
|
-
begin
|
153
|
-
ProcessOrderTask.call!(payment_invalid: false)
|
154
|
-
rescue StandardError => e
|
155
|
-
puts "System exception: #{e.message}"
|
156
|
-
end
|
157
|
-
```
|
158
|
-
|
159
|
-
## Exception Classification
|
160
|
-
|
161
|
-
### Protected Exceptions
|
162
|
-
|
163
|
-
> [!IMPORTANT]
|
164
|
-
> CMDx framework exceptions always propagate regardless of call method, ensuring framework integrity and proper error reporting.
|
165
|
-
|
166
|
-
Certain exceptions are never converted to failed results:
|
167
|
-
|
168
|
-
```ruby
|
169
|
-
class InvalidTask < CMDx::Task
|
170
|
-
# Intentionally not implementing call method
|
171
|
-
end
|
172
|
-
|
173
|
-
# Framework exceptions always propagate
|
174
|
-
begin
|
175
|
-
InvalidTask.call # Even non-bang call propagates framework exceptions
|
176
|
-
rescue CMDx::UndefinedCallError => e
|
177
|
-
puts "Framework exception: #{e.message}"
|
178
|
-
end
|
179
|
-
```
|
180
|
-
|
181
|
-
### Exception Hierarchy
|
182
|
-
|
183
|
-
| Exception Type | `call` Behavior | `call!` Behavior |
|
184
|
-
|----------------|-----------------|------------------|
|
185
|
-
| **CMDx Framework** | Propagates | Propagates |
|
186
|
-
| **CMDx Faults** | Converts to result | Respects `task_halt` config |
|
187
|
-
| **Standard Exceptions** | Converts to result | Propagates |
|
188
|
-
| **Custom Exceptions** | Converts to result | Propagates |
|
189
|
-
|
190
|
-
## Error Handling Patterns
|
191
|
-
|
192
|
-
### Graceful Degradation
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
class ProcessUserDataTask < CMDx::Task
|
196
|
-
def call
|
197
|
-
user_data = fetch_user_data
|
198
|
-
process_data(user_data)
|
199
|
-
end
|
200
|
-
|
201
|
-
private
|
202
|
-
|
203
|
-
def fetch_user_data
|
204
|
-
# May raise various exceptions
|
205
|
-
external_api.get_user_data(context.user_id)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# Handle with graceful degradation
|
210
|
-
result = ProcessUserDataTask.call(user_id: 12345)
|
211
|
-
|
212
|
-
if result.failed?
|
213
|
-
case result.metadata[:original_exception]
|
214
|
-
when Net::TimeoutError
|
215
|
-
# Retry with cached data
|
216
|
-
fallback_processor.process_cached_data(user_id)
|
217
|
-
when JSON::ParserError
|
218
|
-
# Handle malformed response
|
219
|
-
error_reporter.log_api_format_error
|
220
|
-
else
|
221
|
-
# Generic error handling
|
222
|
-
notify_administrators(result.metadata[:reason])
|
223
|
-
end
|
224
|
-
end
|
225
|
-
```
|
226
|
-
|
227
|
-
> [!TIP]
|
228
|
-
> Use `call` for workflow processing where you need guaranteed result objects, and `call!` for direct integration with existing exception-based error handling patterns.
|
229
|
-
|
230
55
|
---
|
231
56
|
|
232
57
|
- **Prev:** [Interruptions - Faults](faults.md)
|
@@ -1,113 +1,72 @@
|
|
1
1
|
# Interruptions - Faults
|
2
2
|
|
3
|
-
Faults are exception mechanisms that halt task execution via `skip!` and `fail!` methods. When tasks execute with the `
|
3
|
+
Faults are exception mechanisms that halt task execution via `skip!` and `fail!` methods. When tasks execute with the `execute!` method, fault exceptions matching the task's interruption status are raised, enabling sophisticated exception handling and control flow patterns.
|
4
4
|
|
5
5
|
## Table of Contents
|
6
6
|
|
7
|
-
- [TLDR](#tldr)
|
8
7
|
- [Fault Types](#fault-types)
|
9
|
-
- [
|
10
|
-
- [
|
8
|
+
- [Fault Handling](#fault-handling)
|
9
|
+
- [Data Access](#data-access)
|
11
10
|
- [Advanced Matching](#advanced-matching)
|
11
|
+
- [Task-Specific Matching](#task-specific-matching)
|
12
|
+
- [Custom Logic Matching](#custom-logic-matching)
|
12
13
|
- [Fault Propagation](#fault-propagation)
|
14
|
+
- [Basic Propagation](#basic-propagation)
|
15
|
+
- [Additional Metadata](#additional-metadata)
|
13
16
|
- [Chain Analysis](#chain-analysis)
|
14
|
-
- [Configuration](#configuration)
|
15
|
-
|
16
|
-
## TLDR
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
# Basic exception handling
|
20
|
-
begin
|
21
|
-
PaymentProcessor.call!(amount: 100)
|
22
|
-
rescue CMDx::Skipped => e
|
23
|
-
handle_skipped_payment(e.result.metadata[:reason])
|
24
|
-
rescue CMDx::Failed => e
|
25
|
-
handle_failed_payment(e.result.metadata[:error])
|
26
|
-
rescue CMDx::Fault => e
|
27
|
-
handle_any_interruption(e)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Advanced matching
|
31
|
-
rescue CMDx::Failed.for?(PaymentProcessor, CardValidator) => e
|
32
|
-
rescue CMDx::Fault.matches? { |f| f.context.amount > 1000 } => e
|
33
|
-
|
34
|
-
# Fault propagation
|
35
|
-
throw!(validation_result) if validation_result.failed?
|
36
|
-
```
|
37
17
|
|
38
18
|
## Fault Types
|
39
19
|
|
40
20
|
| Type | Triggered By | Use Case |
|
41
21
|
|------|--------------|----------|
|
42
|
-
| `CMDx::Skipped` | `skip!` method | Optional processing, early returns |
|
43
|
-
| `CMDx::Failed` | `fail!` method | Validation errors, processing failures |
|
44
22
|
| `CMDx::Fault` | Base class | Catch-all for any interruption |
|
23
|
+
| `CMDx::SkipFault` | `skip!` method | Optional processing, early returns |
|
24
|
+
| `CMDx::FailFault` | `fail!` method | Validation errors, processing failures |
|
45
25
|
|
46
|
-
> [!
|
26
|
+
> [!IMPORTANT]
|
47
27
|
> All fault exceptions inherit from `CMDx::Fault` and provide access to the complete task execution context including result, task, context, and chain information.
|
48
28
|
|
49
|
-
##
|
50
|
-
|
51
|
-
### Basic Rescue Patterns
|
29
|
+
## Fault Handling
|
52
30
|
|
53
31
|
```ruby
|
54
32
|
begin
|
55
|
-
|
56
|
-
rescue CMDx::
|
57
|
-
logger.info "
|
58
|
-
schedule_retry(e.context.
|
59
|
-
rescue CMDx::
|
60
|
-
logger.error "
|
61
|
-
|
33
|
+
ProcessTicket.execute!(ticket_id: 456)
|
34
|
+
rescue CMDx::SkipFault => e
|
35
|
+
logger.info "Ticket processing skipped: #{e.message}"
|
36
|
+
schedule_retry(e.context.ticket_id)
|
37
|
+
rescue CMDx::FailFault => e
|
38
|
+
logger.error "Ticket processing failed: #{e.message}"
|
39
|
+
notify_admin(e.context.assigned_agent, e.result.metadata[:error_code])
|
62
40
|
rescue CMDx::Fault => e
|
63
|
-
logger.warn "
|
64
|
-
|
65
|
-
end
|
66
|
-
```
|
67
|
-
|
68
|
-
### Error-Specific Handling
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
begin
|
72
|
-
PaymentProcessor.call!(card_token: token, amount: amount)
|
73
|
-
rescue CMDx::Failed => e
|
74
|
-
case e.result.metadata[:error_code]
|
75
|
-
when "INSUFFICIENT_FUNDS"
|
76
|
-
suggest_different_payment_method
|
77
|
-
when "CARD_DECLINED"
|
78
|
-
request_card_verification
|
79
|
-
when "NETWORK_ERROR"
|
80
|
-
retry_payment_later
|
81
|
-
else
|
82
|
-
escalate_to_support(e)
|
83
|
-
end
|
41
|
+
logger.warn "Ticket processing interrupted: #{e.message}"
|
42
|
+
rollback_changes
|
84
43
|
end
|
85
44
|
```
|
86
45
|
|
87
|
-
##
|
46
|
+
## Data Access
|
88
47
|
|
89
|
-
Faults provide comprehensive access to execution context:
|
48
|
+
Faults provide comprehensive access to execution context, eg:
|
90
49
|
|
91
50
|
```ruby
|
92
51
|
begin
|
93
|
-
|
52
|
+
LicenseActivation.execute!(license_key: key, machine_id: machine)
|
94
53
|
rescue CMDx::Fault => e
|
95
54
|
# Result information
|
96
|
-
e.result.
|
97
|
-
e.result.
|
98
|
-
e.result.
|
55
|
+
e.result.state #=> "interrupted"
|
56
|
+
e.result.status #=> "failed" or "skipped"
|
57
|
+
e.result.reason #=> "License key already activated"
|
99
58
|
|
100
59
|
# Task information
|
101
|
-
e.task.class
|
102
|
-
e.task.id
|
60
|
+
e.task.class #=> <LicenseActivation>
|
61
|
+
e.task.id #=> "abc123..."
|
103
62
|
|
104
63
|
# Context data
|
105
|
-
e.context.
|
106
|
-
e.context.
|
64
|
+
e.context.license_key #=> "ABC-123-DEF"
|
65
|
+
e.context.machine_id #=> "[FILTERED]"
|
107
66
|
|
108
|
-
# Chain information
|
109
|
-
e.chain
|
110
|
-
e.chain
|
67
|
+
# Chain information
|
68
|
+
e.chain.id #=> "def456..."
|
69
|
+
e.chain.size #=> 3
|
111
70
|
end
|
112
71
|
```
|
113
72
|
|
@@ -115,18 +74,17 @@ end
|
|
115
74
|
|
116
75
|
### Task-Specific Matching
|
117
76
|
|
118
|
-
|
119
|
-
> Use `for?` to handle faults only from specific task classes, enabling targeted exception handling in complex workflows.
|
77
|
+
Use `for?` to handle faults only from specific task classes, enabling targeted exception handling in complex workflows.
|
120
78
|
|
121
79
|
```ruby
|
122
80
|
begin
|
123
|
-
|
124
|
-
rescue CMDx::
|
125
|
-
# Handle only
|
126
|
-
|
127
|
-
rescue CMDx::
|
81
|
+
DocumentWorkflow.execute!(document_data: data)
|
82
|
+
rescue CMDx::FailFault.for?(FormatValidator, ContentProcessor) => e
|
83
|
+
# Handle only document-related failures
|
84
|
+
retry_with_alternate_parser(e.context)
|
85
|
+
rescue CMDx::SkipFault.for?(VirusScanner, ContentFilter) => e
|
128
86
|
# Handle security-related skips
|
129
|
-
|
87
|
+
quarantine_for_review(e.context.document_id)
|
130
88
|
end
|
131
89
|
```
|
132
90
|
|
@@ -134,74 +92,76 @@ end
|
|
134
92
|
|
135
93
|
```ruby
|
136
94
|
begin
|
137
|
-
|
138
|
-
rescue CMDx::Fault.matches? { |f| f.context.
|
139
|
-
|
140
|
-
rescue CMDx::
|
141
|
-
|
142
|
-
rescue CMDx::Fault.matches? { |f| f.result.metadata[:error_type] == "
|
143
|
-
|
95
|
+
ReportGenerator.execute!(report: report_data)
|
96
|
+
rescue CMDx::Fault.matches? { |f| f.context.data_size > 10_000 } => e
|
97
|
+
escalate_large_dataset_failure(e)
|
98
|
+
rescue CMDx::FailFault.matches? { |f| f.result.metadata[:attempt_count] > 3 } => e
|
99
|
+
abandon_report_generation(e)
|
100
|
+
rescue CMDx::Fault.matches? { |f| f.result.metadata[:error_type] == "memory" } => e
|
101
|
+
increase_memory_and_retry(e)
|
144
102
|
end
|
145
103
|
```
|
146
104
|
|
147
105
|
## Fault Propagation
|
148
106
|
|
149
|
-
|
150
|
-
> Use `throw!` to propagate failures while preserving fault context and maintaining the error chain for debugging.
|
107
|
+
Use `throw!` to propagate failures while preserving fault context and maintaining the error chain for debugging.
|
151
108
|
|
152
109
|
### Basic Propagation
|
153
110
|
|
154
111
|
```ruby
|
155
|
-
class
|
156
|
-
def
|
157
|
-
#
|
158
|
-
validation_result =
|
159
|
-
throw!(validation_result)
|
112
|
+
class ReportGenerator < CMDx::Task
|
113
|
+
def work
|
114
|
+
# Throw if skipped or failed
|
115
|
+
validation_result = DataValidator.execute(context)
|
116
|
+
throw!(validation_result)
|
117
|
+
|
118
|
+
# Only throw if skipped
|
119
|
+
check_permissions = CheckPermissions.execute(context)
|
120
|
+
throw!(check_permissions) if check_permissions.skipped?
|
160
121
|
|
161
|
-
#
|
162
|
-
|
163
|
-
throw!(
|
122
|
+
# Only throw if failed
|
123
|
+
data_result = DataProcessor.execute(context)
|
124
|
+
throw!(data_result) if data_result.failed?
|
164
125
|
|
165
126
|
# Continue processing
|
166
|
-
|
127
|
+
generate_report
|
167
128
|
end
|
168
129
|
end
|
169
130
|
```
|
170
131
|
|
171
|
-
###
|
132
|
+
### Additional Metadata
|
172
133
|
|
173
134
|
```ruby
|
174
|
-
class
|
175
|
-
def
|
176
|
-
step_result =
|
135
|
+
class BatchProcessor < CMDx::Task
|
136
|
+
def work
|
137
|
+
step_result = FileValidation.execute(context)
|
177
138
|
|
178
139
|
if step_result.failed?
|
179
140
|
throw!(step_result, {
|
180
|
-
|
141
|
+
batch_stage: "validation",
|
181
142
|
can_retry: true,
|
182
|
-
next_step: "
|
143
|
+
next_step: "file_repair"
|
183
144
|
})
|
184
145
|
end
|
185
146
|
|
186
|
-
|
147
|
+
continue_batch
|
187
148
|
end
|
188
149
|
end
|
189
150
|
```
|
190
151
|
|
191
152
|
## Chain Analysis
|
192
153
|
|
193
|
-
|
194
|
-
> Results provide methods to analyze fault propagation and identify original failure sources in complex execution chains.
|
154
|
+
Results provide methods to analyze fault propagation and identify original failure sources in complex execution chains.
|
195
155
|
|
196
156
|
```ruby
|
197
|
-
result =
|
157
|
+
result = DocumentWorkflow.execute(invalid_data)
|
198
158
|
|
199
159
|
if result.failed?
|
200
160
|
# Trace the original failure
|
201
161
|
original = result.caused_failure
|
202
162
|
if original
|
203
163
|
puts "Original failure: #{original.task.class.name}"
|
204
|
-
puts "Reason: #{original.
|
164
|
+
puts "Reason: #{original.reason}"
|
205
165
|
end
|
206
166
|
|
207
167
|
# Find what propagated the failure
|
@@ -220,46 +180,6 @@ if result.failed?
|
|
220
180
|
end
|
221
181
|
```
|
222
182
|
|
223
|
-
## Configuration
|
224
|
-
|
225
|
-
### Task Halt Settings
|
226
|
-
|
227
|
-
Control which statuses raise exceptions using `task_halt`:
|
228
|
-
|
229
|
-
```ruby
|
230
|
-
class DataProcessor < CMDx::Task
|
231
|
-
# Only failures raise exceptions
|
232
|
-
cmd_settings!(task_halt: [CMDx::Result::FAILED])
|
233
|
-
|
234
|
-
def call
|
235
|
-
skip!(reason: "No data to process") if data.empty?
|
236
|
-
# Skip will NOT raise exception on call!
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
class CriticalValidator < CMDx::Task
|
241
|
-
# Both failures and skips raise exceptions
|
242
|
-
cmd_settings!(task_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
|
243
|
-
|
244
|
-
def call
|
245
|
-
skip!(reason: "Validation bypassed") if bypass_mode?
|
246
|
-
# Skip WILL raise exception on call!
|
247
|
-
end
|
248
|
-
end
|
249
|
-
```
|
250
|
-
|
251
|
-
> [!WARNING]
|
252
|
-
> Task halt configuration only affects the `call!` method. The `call` method always captures exceptions and converts them to result objects regardless of halt settings.
|
253
|
-
|
254
|
-
### Global Configuration
|
255
|
-
|
256
|
-
```ruby
|
257
|
-
# Configure default halt behavior
|
258
|
-
CMDx.configure do |config|
|
259
|
-
config.task_halt = [CMDx::Result::FAILED] # Default: only failures halt
|
260
|
-
end
|
261
|
-
```
|
262
|
-
|
263
183
|
---
|
264
184
|
|
265
185
|
- **Prev:** [Interruptions - Halt](halt.md)
|