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,156 +1,158 @@
1
1
  # Interruptions - Exceptions
2
2
 
3
- CMDx provides robust exception handling that differs between the `call` and `call!`
4
- methods. Understanding how unhandled exceptions are processed is crucial for
5
- building reliable task execution flows and implementing proper error handling strategies.
3
+ CMDx provides robust exception handling that differs between the `call` and `call!` methods. Understanding how unhandled exceptions are processed is crucial for building reliable task execution flows and implementing proper error handling strategies.
6
4
 
7
5
  ## Table of Contents
8
6
 
9
7
  - [TLDR](#tldr)
10
- - [Exception Handling Behavior](#exception-handling-behavior)
11
- - [Bang Call (`call!`)](#bang-call-call)
8
+ - [Exception Handling Methods](#exception-handling-methods)
9
+ - [Exception Metadata](#exception-metadata)
10
+ - [Bang Call Behavior](#bang-call-behavior)
12
11
  - [Exception Classification](#exception-classification)
12
+ - [Error Handling Patterns](#error-handling-patterns)
13
13
 
14
14
  ## TLDR
15
15
 
16
- - **`call`** - Captures ALL exceptions, converts to failed results with metadata
17
- - **`call!`** - Lets exceptions propagate (except CMDx faults based on task_halt config)
18
- - **Exception info** - Available in `result.metadata[:original_exception]` and `result.metadata[:reason]`
19
- - **Guaranteed results** - `call` always returns a result object, never raises
20
- - **Fault vs Exception** - CMDx faults have special handling, other exceptions propagate in `call!`
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
+ ```
21
29
 
22
- ## Exception Handling Behavior
30
+ ## Exception Handling Methods
31
+
32
+ > [!IMPORTANT]
33
+ > The key difference: `call` guarantees a result object, while `call!` allows exceptions to propagate for standard error handling patterns.
23
34
 
24
35
  ### Non-bang Call (`call`)
25
36
 
26
- The `call` method captures **all** unhandled exceptions and converts them to
27
- failed results, ensuring that no exceptions escape the task execution boundary.
28
- This provides consistent, predictable behavior for result processing.
37
+ The `call` method captures **all** unhandled exceptions and converts them to failed results, ensuring predictable behavior and consistent result processing.
29
38
 
30
- ```ruby
31
- class ProcessUserOrderTask < CMDx::Task
39
+ | Behavior | Description |
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 |
32
46
 
47
+ ```ruby
48
+ class ProcessPaymentTask < CMDx::Task
33
49
  def call
34
- # This will raise a NoMethodError
35
- undefined_method_call
50
+ raise ActiveRecord::RecordNotFound, "Payment method not found"
36
51
  end
37
-
38
52
  end
39
53
 
40
- result = ProcessUserOrderTask.call
54
+ result = ProcessPaymentTask.call
41
55
  result.state #=> "interrupted"
42
56
  result.status #=> "failed"
43
57
  result.failed? #=> true
44
- result.metadata #=> {
45
- #=> reason: "[NoMethodError] undefined method `undefined_method_call`",
46
- #=> original_exception: <NoMethodError>
47
- #=> }
48
58
  ```
49
59
 
50
- > [!NOTE]
51
- > The `call` method ensures no exceptions escape task execution, making it ideal
52
- > for workflow processing and scenarios where you need guaranteed result objects.
53
-
54
- ### Exception Metadata Structure
60
+ ### Bang Call (`call!`)
55
61
 
56
- Captured exceptions populate result metadata with structured information:
62
+ The `call!` method allows unhandled exceptions to propagate, enabling standard Ruby exception handling while respecting CMDx fault configuration.
57
63
 
58
64
  ```ruby
59
- class ConnectDatabaseTask < CMDx::Task
60
-
65
+ class ProcessPaymentTask < CMDx::Task
61
66
  def call
62
- # Simulate a database connection error
63
- raise ActiveRecord::ConnectionNotEstablished, "Database unavailable"
67
+ raise StandardError, "Payment gateway unavailable"
64
68
  end
69
+ end
65
70
 
71
+ begin
72
+ ProcessPaymentTask.call!
73
+ rescue StandardError => e
74
+ puts "Handle exception: #{e.message}"
66
75
  end
76
+ ```
67
77
 
68
- result = ConnectDatabaseTask.call
78
+ ## Exception Metadata
69
79
 
70
- # Exception information in metadata
71
- result.metadata[:reason] #=> "[ActiveRecord::ConnectionNotEstablished] Database unavailable"
72
- result.metadata[:original_exception] #=> <ActiveRecord::ConnectionNotEstablished>
73
- result.metadata[:original_exception].class #=> ActiveRecord::ConnectionNotEstablished
74
- result.metadata[:original_exception].message #=> "Database unavailable"
75
- result.metadata[:original_exception].backtrace #=> ["..."]
76
- ```
80
+ > [!NOTE]
81
+ > Exception information is preserved in result metadata, providing full debugging context while maintaining clean result interfaces.
77
82
 
78
- ### Accessing Original Exception Details
83
+ ### Metadata Structure
79
84
 
80
85
  ```ruby
81
- result = ProcessUserOrderTask.call
86
+ result = ProcessPaymentTask.call
82
87
 
83
- if result.failed? && result.metadata[:original_exception]
84
- original = result.metadata[:original_exception]
85
-
86
- puts "Exception type: #{original.class}"
87
- puts "Exception message: #{original.message}"
88
- puts "Exception backtrace:"
89
- puts original.backtrace.first(5).join("\n")
88
+ # Exception metadata always includes:
89
+ result.metadata[:reason] #=> "[StandardError] Payment gateway unavailable"
90
+ result.metadata[:original_exception] #=> <StandardError instance>
90
91
 
91
- # Check exception type for specific handling
92
- case original
93
- when ActiveRecord::RecordNotFound
94
- handle_missing_record(original)
95
- when Net::TimeoutError
96
- handle_timeout_error(original)
97
- when StandardError
98
- handle_generic_error(original)
99
- end
100
- end
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'", ...]
101
97
  ```
102
98
 
103
- ## Bang Call (`call!`)
104
-
105
- The `call!` method allows unhandled exceptions to propagate **unless** they are
106
- CMDx faults that match the `task_halt` configuration. This enables exception-based
107
- control flow while still providing structured fault handling.
99
+ ### Exception Type Checking
108
100
 
109
101
  ```ruby
110
- class ProcessUserOrderTask < CMDx::Task
111
-
102
+ class DatabaseTask < CMDx::Task
112
103
  def call
113
- # This will raise a NoMethodError directly
114
- undefined_method_call
104
+ raise ActiveRecord::ConnectionNotEstablished, "Database unavailable"
115
105
  end
116
-
117
106
  end
118
107
 
119
- begin
120
- ProcessUserOrderTask.call!
121
- rescue NoMethodError => e
122
- puts "Caught original exception: #{e.message}"
123
- # Handle the original exception directly
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
124
119
  end
125
120
  ```
126
121
 
127
- ### Fault vs Exception Behavior
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:
128
130
 
129
131
  ```ruby
130
- class ProcessOrderPaymentTask < CMDx::Task
132
+ class ProcessOrderTask < CMDx::Task
133
+ cmd_settings!(task_halt: [CMDx::Result::FAILED])
131
134
 
132
135
  def call
133
- if context.simulate_fault
134
- fail!(reason: "Controlled failure") # Becomes CMDx::Failed
136
+ if context.payment_invalid
137
+ fail!(reason: "Invalid payment method") # CMDx fault
135
138
  else
136
- raise StandardError, "Uncontrolled error" # Remains StandardError
139
+ raise StandardError, "System error" # Regular exception
137
140
  end
138
141
  end
139
-
140
142
  end
141
143
 
142
- # Fault behavior (controlled)
144
+ # Fault behavior (converted to exception due to task_halt)
143
145
  begin
144
- ProcessOrderPaymentTask.call!(simulate_fault: true)
146
+ ProcessOrderTask.call!(payment_invalid: true)
145
147
  rescue CMDx::Failed => e
146
- puts "Caught CMDx fault: #{e.message}"
148
+ puts "Controlled fault: #{e.message}"
147
149
  end
148
150
 
149
- # Exception behavior (uncontrolled)
151
+ # Exception behavior (propagates normally)
150
152
  begin
151
- ProcessOrderPaymentTask.call!(simulate_fault: false)
153
+ ProcessOrderTask.call!(payment_invalid: false)
152
154
  rescue StandardError => e
153
- puts "Caught standard exception: #{e.message}"
155
+ puts "System exception: #{e.message}"
154
156
  end
155
157
  ```
156
158
 
@@ -158,57 +160,72 @@ end
158
160
 
159
161
  ### Protected Exceptions
160
162
 
161
- Certain CMDx-specific exceptions are always allowed to propagate and are never
162
- converted to failed results:
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:
163
167
 
164
168
  ```ruby
165
- class ProcessUndefinedOrderTask < CMDx::Task
169
+ class InvalidTask < CMDx::Task
166
170
  # Intentionally not implementing call method
167
171
  end
168
172
 
169
- # These exceptions always propagate regardless of call method
173
+ # Framework exceptions always propagate
170
174
  begin
171
- ProcessUndefinedOrderTask.call
175
+ InvalidTask.call # Even non-bang call propagates framework exceptions
172
176
  rescue CMDx::UndefinedCallError => e
173
- puts "This exception is never converted to a failed result"
174
- end
175
-
176
- begin
177
- ProcessUndefinedOrderTask.call!
178
- rescue CMDx::UndefinedCallError => e
179
- puts "This exception propagates normally in call! too"
177
+ puts "Framework exception: #{e.message}"
180
178
  end
181
179
  ```
182
180
 
183
- ### CMDx Fault Handling
181
+ ### Exception Hierarchy
184
182
 
185
- CMDx faults have special handling in both call methods:
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 |
186
189
 
187
- ```ruby
188
- class ProcessOrderWithHaltTask < CMDx::Task
189
- # Configure to halt on failures
190
- cmd_settings!(task_halt: [CMDx::Result::FAILED])
190
+ ## Error Handling Patterns
191
191
 
192
+ ### Graceful Degradation
193
+
194
+ ```ruby
195
+ class ProcessUserDataTask < CMDx::Task
192
196
  def call
193
- fail!(reason: "This is a controlled failure")
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)
194
206
  end
195
207
  end
196
208
 
197
- # With call - fault becomes failed result
198
- result = ProcessOrderWithHaltTask.call
199
- result.failed? #=> true
209
+ # Handle with graceful degradation
210
+ result = ProcessUserDataTask.call(user_id: 12345)
200
211
 
201
- # With call! - fault becomes exception (due to task_halt configuration)
202
- begin
203
- ProcessOrderWithHaltTask.call!
204
- rescue CMDx::Failed => e
205
- puts "Fault converted to exception: #{e.message}"
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
206
224
  end
207
225
  ```
208
226
 
209
- > [!IMPORTANT]
210
- > Always preserve original exception information in metadata when handling
211
- > exceptions manually. This maintains debugging capabilities and error traceability.
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.
212
229
 
213
230
  ---
214
231