cmdx 1.0.1 → 1.1.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/.cursor/prompts/rspec.md +20 -0
- data/.cursor/prompts/yardoc.md +8 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +17 -2
- data/README.md +1 -1
- data/docs/basics/call.md +2 -2
- data/docs/basics/chain.md +1 -1
- data/docs/callbacks.md +3 -36
- data/docs/configuration.md +58 -12
- data/docs/interruptions/exceptions.md +1 -1
- data/docs/interruptions/faults.md +2 -2
- data/docs/logging.md +4 -4
- data/docs/middlewares.md +43 -43
- data/docs/parameters/coercions.md +49 -38
- data/docs/parameters/defaults.md +1 -1
- data/docs/parameters/validations.md +0 -39
- data/docs/testing.md +11 -12
- data/docs/workflows.md +4 -4
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +36 -56
- data/lib/cmdx/callback_registry.rb +82 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +22 -115
- data/lib/cmdx/chain_serializer.rb +17 -148
- data/lib/cmdx/coercion.rb +49 -0
- data/lib/cmdx/coercion_registry.rb +94 -0
- data/lib/cmdx/coercions/array.rb +18 -36
- data/lib/cmdx/coercions/big_decimal.rb +21 -33
- data/lib/cmdx/coercions/boolean.rb +21 -40
- data/lib/cmdx/coercions/complex.rb +18 -31
- data/lib/cmdx/coercions/date.rb +20 -39
- data/lib/cmdx/coercions/date_time.rb +22 -39
- data/lib/cmdx/coercions/float.rb +19 -32
- data/lib/cmdx/coercions/hash.rb +22 -41
- data/lib/cmdx/coercions/integer.rb +20 -33
- data/lib/cmdx/coercions/rational.rb +20 -32
- data/lib/cmdx/coercions/string.rb +23 -31
- data/lib/cmdx/coercions/time.rb +24 -40
- data/lib/cmdx/coercions/virtual.rb +14 -31
- data/lib/cmdx/configuration.rb +57 -171
- data/lib/cmdx/context.rb +22 -165
- data/lib/cmdx/core_ext/hash.rb +42 -67
- data/lib/cmdx/core_ext/module.rb +35 -79
- data/lib/cmdx/core_ext/object.rb +63 -98
- data/lib/cmdx/correlator.rb +40 -156
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +165 -202
- data/lib/cmdx/fault.rb +55 -158
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -109
- data/lib/cmdx/lazy_struct.rb +103 -187
- data/lib/cmdx/log_formatters/json.rb +14 -40
- data/lib/cmdx/log_formatters/key_value.rb +14 -40
- data/lib/cmdx/log_formatters/line.rb +14 -48
- data/lib/cmdx/log_formatters/logstash.rb +14 -57
- data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
- data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
- data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
- data/lib/cmdx/log_formatters/raw.rb +19 -49
- data/lib/cmdx/logger.rb +20 -82
- data/lib/cmdx/logger_ansi.rb +18 -75
- data/lib/cmdx/logger_serializer.rb +24 -114
- data/lib/cmdx/middleware.rb +38 -60
- data/lib/cmdx/middleware_registry.rb +81 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +120 -198
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +25 -56
- data/lib/cmdx/parameter_registry.rb +59 -84
- data/lib/cmdx/parameter_serializer.rb +23 -74
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -260
- data/lib/cmdx/result_ansi.rb +19 -85
- data/lib/cmdx/result_inspector.rb +27 -68
- data/lib/cmdx/result_logger.rb +18 -81
- data/lib/cmdx/result_serializer.rb +28 -132
- data/lib/cmdx/rspec/matchers.rb +28 -0
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
- data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
- data/lib/cmdx/task.rb +213 -425
- data/lib/cmdx/task_deprecator.rb +55 -0
- data/lib/cmdx/task_processor.rb +245 -0
- data/lib/cmdx/task_serializer.rb +22 -70
- data/lib/cmdx/utils/ansi_color.rb +13 -89
- data/lib/cmdx/utils/log_timestamp.rb +13 -42
- data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +48 -0
- data/lib/cmdx/validator_registry.rb +86 -0
- data/lib/cmdx/validators/exclusion.rb +55 -94
- data/lib/cmdx/validators/format.rb +31 -85
- data/lib/cmdx/validators/inclusion.rb +65 -110
- data/lib/cmdx/validators/length.rb +117 -133
- data/lib/cmdx/validators/numeric.rb +123 -130
- data/lib/cmdx/validators/presence.rb +38 -79
- data/lib/cmdx/version.rb +1 -7
- data/lib/cmdx/workflow.rb +46 -339
- data/lib/cmdx.rb +1 -1
- data/lib/generators/cmdx/install_generator.rb +14 -31
- data/lib/generators/cmdx/task_generator.rb +39 -55
- data/lib/generators/cmdx/templates/install.rb +24 -6
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +0 -1
- data/lib/locales/cs.yml +0 -1
- data/lib/locales/da.yml +0 -1
- data/lib/locales/de.yml +0 -1
- data/lib/locales/el.yml +0 -1
- data/lib/locales/en.yml +0 -1
- data/lib/locales/es.yml +0 -1
- data/lib/locales/fi.yml +0 -1
- data/lib/locales/fr.yml +0 -1
- data/lib/locales/he.yml +0 -1
- data/lib/locales/hi.yml +0 -1
- data/lib/locales/it.yml +0 -1
- data/lib/locales/ja.yml +0 -1
- data/lib/locales/ko.yml +0 -1
- data/lib/locales/nl.yml +0 -1
- data/lib/locales/no.yml +0 -1
- data/lib/locales/pl.yml +0 -1
- data/lib/locales/pt.yml +0 -1
- data/lib/locales/ru.yml +0 -1
- data/lib/locales/sv.yml +0 -1
- data/lib/locales/th.yml +0 -1
- data/lib/locales/tr.yml +0 -1
- data/lib/locales/vi.yml +0 -1
- data/lib/locales/zh.yml +0 -1
- metadata +34 -8
- data/lib/cmdx/parameter_validator.rb +0 -81
- data/lib/cmdx/parameter_value.rb +0 -244
- data/lib/cmdx/parameters_inspector.rb +0 -72
- data/lib/cmdx/parameters_serializer.rb +0 -115
- data/lib/cmdx/rspec/result_matchers.rb +0 -917
- data/lib/cmdx/rspec/task_matchers.rb +0 -570
- data/lib/cmdx/validators/custom.rb +0 -102
data/docs/middlewares.md
CHANGED
@@ -32,9 +32,9 @@ Declare middleware using the `use` method in your task classes:
|
|
32
32
|
|
33
33
|
```ruby
|
34
34
|
class ProcessOrderTask < CMDx::Task
|
35
|
-
use AuthenticationMiddleware
|
36
|
-
use LoggingMiddleware, level: :info
|
37
|
-
use CachingMiddleware, ttl: 300
|
35
|
+
use :middleware, AuthenticationMiddleware
|
36
|
+
use :middleware, LoggingMiddleware, level: :info
|
37
|
+
use :middleware, CachingMiddleware, ttl: 300
|
38
38
|
|
39
39
|
def call
|
40
40
|
context.order = Order.find(order_id)
|
@@ -71,7 +71,7 @@ class AuditMiddleware < CMDx::Middleware
|
|
71
71
|
end
|
72
72
|
|
73
73
|
class ProcessOrderTask < CMDx::Task
|
74
|
-
use AuditMiddleware, action: 'process', resource_type: 'Order'
|
74
|
+
use :middleware, AuditMiddleware, action: 'process', resource_type: 'Order'
|
75
75
|
|
76
76
|
def call
|
77
77
|
context.order = Order.find(order_id)
|
@@ -86,7 +86,7 @@ Pre-configured middleware instances for complex initialization:
|
|
86
86
|
|
87
87
|
```ruby
|
88
88
|
class ProcessOrderTask < CMDx::Task
|
89
|
-
use LoggingMiddleware.new(
|
89
|
+
use :middleware, LoggingMiddleware.new(
|
90
90
|
level: :debug,
|
91
91
|
formatter: JSON::JSONFormatter.new,
|
92
92
|
tags: ['order', 'payment']
|
@@ -104,7 +104,7 @@ Inline middleware for simple cases:
|
|
104
104
|
|
105
105
|
```ruby
|
106
106
|
class ProcessOrderTask < CMDx::Task
|
107
|
-
use proc { |task, callable|
|
107
|
+
use :middleware, proc { |task, callable|
|
108
108
|
start_time = Time.now
|
109
109
|
result = callable.call(task)
|
110
110
|
duration = Time.now - start_time
|
@@ -125,9 +125,9 @@ Middleware executes in nested fashion - first declared wraps all others:
|
|
125
125
|
|
126
126
|
```ruby
|
127
127
|
class ProcessOrderTask < CMDx::Task
|
128
|
-
use TimingMiddleware # 1st: outermost
|
129
|
-
use AuthenticationMiddleware # 2nd: middle
|
130
|
-
use ValidationMiddleware # 3rd: innermost
|
128
|
+
use :middleware, TimingMiddleware # 1st: outermost
|
129
|
+
use :middleware, AuthenticationMiddleware # 2nd: middle
|
130
|
+
use :middleware, ValidationMiddleware # 3rd: innermost
|
131
131
|
|
132
132
|
def call
|
133
133
|
# Core logic executes last
|
@@ -173,7 +173,7 @@ class RateLimitMiddleware < CMDx::Middleware
|
|
173
173
|
end
|
174
174
|
|
175
175
|
class SendEmailTask < CMDx::Task
|
176
|
-
use RateLimitMiddleware, limit: 50, window: 1.hour
|
176
|
+
use :middleware, RateLimitMiddleware, limit: 50, window: 1.hour
|
177
177
|
|
178
178
|
def call
|
179
179
|
# Only executes if rate limit check passes
|
@@ -192,14 +192,14 @@ Middleware is inherited from parent classes, enabling application-wide patterns:
|
|
192
192
|
|
193
193
|
```ruby
|
194
194
|
class ApplicationTask < CMDx::Task
|
195
|
-
use RequestIdMiddleware # All tasks get request tracking
|
196
|
-
use PerformanceMiddleware # All tasks get performance monitoring
|
197
|
-
use ErrorReportingMiddleware # All tasks get error reporting
|
195
|
+
use :middleware, RequestIdMiddleware # All tasks get request tracking
|
196
|
+
use :middleware, PerformanceMiddleware # All tasks get performance monitoring
|
197
|
+
use :middleware, ErrorReportingMiddleware # All tasks get error reporting
|
198
198
|
end
|
199
199
|
|
200
200
|
class ProcessOrderTask < ApplicationTask
|
201
|
-
use AuthenticationMiddleware # Specific to order processing
|
202
|
-
use OrderValidationMiddleware # Domain-specific validation
|
201
|
+
use :middleware, AuthenticationMiddleware # Specific to order processing
|
202
|
+
use :middleware, OrderValidationMiddleware # Domain-specific validation
|
203
203
|
|
204
204
|
def call
|
205
205
|
# Inherits all ApplicationTask middleware plus order-specific ones
|
@@ -220,7 +220,7 @@ Enforces execution time limits with support for static and dynamic timeout value
|
|
220
220
|
|
221
221
|
```ruby
|
222
222
|
class ProcessLargeReportTask < CMDx::Task
|
223
|
-
use CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
|
223
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 300 # 5 minutes
|
224
224
|
|
225
225
|
def call
|
226
226
|
# Long-running report generation
|
@@ -229,7 +229,7 @@ end
|
|
229
229
|
|
230
230
|
# Default timeout (3 seconds when no value specified)
|
231
231
|
class QuickValidationTask < CMDx::Task
|
232
|
-
use CMDx::Middlewares::Timeout # Uses 3 seconds default
|
232
|
+
use :middleware, CMDx::Middlewares::Timeout # Uses 3 seconds default
|
233
233
|
|
234
234
|
def call
|
235
235
|
# Fast validation logic
|
@@ -244,7 +244,7 @@ The middleware supports dynamic timeout calculation using method names, procs, a
|
|
244
244
|
```ruby
|
245
245
|
# Method-based timeout calculation
|
246
246
|
class ProcessOrderTask < CMDx::Task
|
247
|
-
use CMDx::Middlewares::Timeout, seconds: :calculate_timeout
|
247
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :calculate_timeout
|
248
248
|
|
249
249
|
def call
|
250
250
|
# Task execution with dynamic timeout
|
@@ -265,7 +265,7 @@ end
|
|
265
265
|
|
266
266
|
# Proc-based timeout for inline calculation
|
267
267
|
class ProcessWorkflowTask < CMDx::Task
|
268
|
-
use CMDx::Middlewares::Timeout, seconds: -> {
|
268
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: -> {
|
269
269
|
context.workflow_size > 100 ? 120 : 60
|
270
270
|
}
|
271
271
|
|
@@ -277,7 +277,7 @@ end
|
|
277
277
|
|
278
278
|
# Context-aware timeout calculation
|
279
279
|
class GenerateReportTask < CMDx::Task
|
280
|
-
use CMDx::Middlewares::Timeout, seconds: :report_timeout
|
280
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :report_timeout
|
281
281
|
|
282
282
|
def call
|
283
283
|
context.report = ReportGenerator.create(report_params)
|
@@ -309,12 +309,12 @@ The middleware follows this precedence for determining timeout values:
|
|
309
309
|
```ruby
|
310
310
|
# Static timeout - highest precedence when specified
|
311
311
|
class ProcessOrderTask < CMDx::Task
|
312
|
-
use CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
|
312
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 45 # Always 45 seconds
|
313
313
|
end
|
314
314
|
|
315
315
|
# Method-based timeout - calls task method
|
316
316
|
class ProcessOrderTask < CMDx::Task
|
317
|
-
use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
317
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
318
318
|
|
319
319
|
private
|
320
320
|
def dynamic_timeout
|
@@ -324,7 +324,7 @@ end
|
|
324
324
|
|
325
325
|
# Default fallback when method returns nil
|
326
326
|
class ProcessOrderTask < CMDx::Task
|
327
|
-
use CMDx::Middlewares::Timeout, seconds: :might_return_nil
|
327
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :might_return_nil
|
328
328
|
|
329
329
|
private
|
330
330
|
def might_return_nil
|
@@ -340,7 +340,7 @@ Apply timeout middleware conditionally based on environment or task state:
|
|
340
340
|
```ruby
|
341
341
|
# Environment-based conditional timeout
|
342
342
|
class ProcessOrderTask < CMDx::Task
|
343
|
-
use CMDx::Middlewares::Timeout,
|
343
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
344
344
|
seconds: 60,
|
345
345
|
unless: -> { Rails.env.development? }
|
346
346
|
|
@@ -353,7 +353,7 @@ end
|
|
353
353
|
|
354
354
|
# Context-based conditional timeout
|
355
355
|
class SendEmailTask < CMDx::Task
|
356
|
-
use CMDx::Middlewares::Timeout,
|
356
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
357
357
|
seconds: 30,
|
358
358
|
if: :timeout_enabled?
|
359
359
|
|
@@ -370,7 +370,7 @@ end
|
|
370
370
|
|
371
371
|
# Combined dynamic timeout with conditions
|
372
372
|
class ProcessComplexOrderTask < CMDx::Task
|
373
|
-
use CMDx::Middlewares::Timeout,
|
373
|
+
use :middleware, CMDx::Middlewares::Timeout,
|
374
374
|
seconds: :calculate_timeout,
|
375
375
|
unless: :skip_timeout?
|
376
376
|
|
@@ -399,11 +399,11 @@ Apply timeout middleware globally with inheritance:
|
|
399
399
|
|
400
400
|
```ruby
|
401
401
|
class ApplicationTask < CMDx::Task
|
402
|
-
use CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
|
402
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 60 # Default 60 seconds for all tasks
|
403
403
|
end
|
404
404
|
|
405
405
|
class QuickTask < ApplicationTask
|
406
|
-
use CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
|
406
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: 15 # Override with 15 seconds
|
407
407
|
|
408
408
|
def call
|
409
409
|
# Fast operation with shorter timeout
|
@@ -411,7 +411,7 @@ class QuickTask < ApplicationTask
|
|
411
411
|
end
|
412
412
|
|
413
413
|
class LongRunningTask < ApplicationTask
|
414
|
-
use CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
414
|
+
use :middleware, CMDx::Middlewares::Timeout, seconds: :dynamic_timeout
|
415
415
|
|
416
416
|
def call
|
417
417
|
# Long operation with dynamic timeout
|
@@ -437,7 +437,7 @@ Manages correlation IDs for request tracing across task boundaries. This middlew
|
|
437
437
|
|
438
438
|
```ruby
|
439
439
|
class ProcessApiRequestTask < CMDx::Task
|
440
|
-
use CMDx::Middlewares::Correlate
|
440
|
+
use :middleware, CMDx::Middlewares::Correlate
|
441
441
|
|
442
442
|
def call
|
443
443
|
# Correlation ID is automatically managed
|
@@ -456,19 +456,19 @@ The middleware follows a hierarchical precedence system for determining correlat
|
|
456
456
|
|
457
457
|
# 1a. Static string ID
|
458
458
|
class ProcessOrderTask < CMDx::Task
|
459
|
-
use CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
|
459
|
+
use :middleware, CMDx::Middlewares::Correlate, id: "fixed-correlation-123"
|
460
460
|
end
|
461
461
|
ProcessOrderTask.call # Always uses "fixed-correlation-123"
|
462
462
|
|
463
463
|
# 1b. Dynamic proc/lambda ID
|
464
464
|
class ProcessOrderTask < CMDx::Task
|
465
|
-
use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
|
465
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{rand(1000)}" }
|
466
466
|
end
|
467
467
|
ProcessOrderTask.call(order_id: 456) # Uses "order-456-847" (random number varies)
|
468
468
|
|
469
469
|
# 1c. Method-based ID
|
470
470
|
class ProcessOrderTask < CMDx::Task
|
471
|
-
use CMDx::Middlewares::Correlate, id: :correlation_method
|
471
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :correlation_method
|
472
472
|
|
473
473
|
private
|
474
474
|
|
@@ -498,7 +498,7 @@ Set fixed or dynamic correlation IDs for specific tasks or workflows using strin
|
|
498
498
|
```ruby
|
499
499
|
# Static string correlation ID
|
500
500
|
class ProcessPaymentTask < CMDx::Task
|
501
|
-
use CMDx::Middlewares::Correlate, id: "payment-processing"
|
501
|
+
use :middleware, CMDx::Middlewares::Correlate, id: "payment-processing"
|
502
502
|
|
503
503
|
def call
|
504
504
|
# Always uses "payment-processing" as correlation ID
|
@@ -509,7 +509,7 @@ end
|
|
509
509
|
|
510
510
|
# Dynamic correlation ID using proc/lambda
|
511
511
|
class ProcessOrderTask < CMDx::Task
|
512
|
-
use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
|
512
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
|
513
513
|
|
514
514
|
def call
|
515
515
|
# Dynamic correlation ID based on order and timestamp
|
@@ -521,7 +521,7 @@ end
|
|
521
521
|
|
522
522
|
# Method-based correlation ID
|
523
523
|
class ProcessApiRequestTask < CMDx::Task
|
524
|
-
use CMDx::Middlewares::Correlate, id: :generate_correlation_id
|
524
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :generate_correlation_id
|
525
525
|
|
526
526
|
def call
|
527
527
|
# Uses correlation ID from generate_correlation_id method
|
@@ -537,7 +537,7 @@ end
|
|
537
537
|
|
538
538
|
# Symbol fallback when method doesn't exist
|
539
539
|
class ProcessWorkflowTask < CMDx::Task
|
540
|
-
use CMDx::Middlewares::Correlate, id: :workflow_processing
|
540
|
+
use :middleware, CMDx::Middlewares::Correlate, id: :workflow_processing
|
541
541
|
|
542
542
|
def call
|
543
543
|
# Uses :workflow_processing as correlation ID (symbol as-is)
|
@@ -554,7 +554,7 @@ Apply correlation middleware conditionally based on environment or task state:
|
|
554
554
|
```ruby
|
555
555
|
class ProcessOrderTask < CMDx::Task
|
556
556
|
# Only apply correlation in production environments
|
557
|
-
use CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
|
557
|
+
use :middleware, CMDx::Middlewares::Correlate, unless: -> { Rails.env.development? }
|
558
558
|
|
559
559
|
def call
|
560
560
|
context.order = Order.find(order_id)
|
@@ -564,7 +564,7 @@ end
|
|
564
564
|
|
565
565
|
class SendEmailTask < CMDx::Task
|
566
566
|
# Apply correlation only when tracing is enabled
|
567
|
-
use CMDx::Middlewares::Correlate, if: :tracing_enabled?
|
567
|
+
use :middleware, CMDx::Middlewares::Correlate, if: :tracing_enabled?
|
568
568
|
|
569
569
|
def call
|
570
570
|
EmailService.deliver(email_params)
|
@@ -584,7 +584,7 @@ Use correlation blocks to establish correlation contexts for groups of related t
|
|
584
584
|
|
585
585
|
```ruby
|
586
586
|
class ProcessOrderWorkflowTask < CMDx::Task
|
587
|
-
use CMDx::Middlewares::Correlate
|
587
|
+
use :middleware, CMDx::Middlewares::Correlate
|
588
588
|
|
589
589
|
def call
|
590
590
|
# Establish correlation context for entire workflow
|
@@ -604,7 +604,7 @@ Apply correlation middleware globally to all tasks:
|
|
604
604
|
|
605
605
|
```ruby
|
606
606
|
class ApplicationTask < CMDx::Task
|
607
|
-
use CMDx::Middlewares::Correlate # All tasks get correlation management
|
607
|
+
use :middleware, CMDx::Middlewares::Correlate # All tasks get correlation management
|
608
608
|
end
|
609
609
|
|
610
610
|
class ProcessOrderTask < ApplicationTask
|
@@ -648,7 +648,7 @@ class ApiController < ApplicationController
|
|
648
648
|
end
|
649
649
|
|
650
650
|
class ProcessOrderTask < CMDx::Task
|
651
|
-
use CMDx::Middlewares::Correlate
|
651
|
+
use :middleware, CMDx::Middlewares::Correlate
|
652
652
|
|
653
653
|
def call
|
654
654
|
# Inherits correlation ID from controller thread context
|
@@ -661,7 +661,7 @@ end
|
|
661
661
|
|
662
662
|
# Alternative: Task-specific correlation for API endpoints
|
663
663
|
class ProcessApiOrderTask < CMDx::Task
|
664
|
-
use CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
|
664
|
+
use :middleware, CMDx::Middlewares::Correlate, id: -> { "api-order-#{context.request_id}" }
|
665
665
|
|
666
666
|
def call
|
667
667
|
# Uses correlation ID specific to this API request
|
@@ -20,11 +20,10 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
|
|
20
20
|
- [Numeric Coercion](#numeric-coercion)
|
21
21
|
- [Coercion with Nested Parameters](#coercion-with-nested-parameters)
|
22
22
|
- [Coercion Error Handling](#coercion-error-handling)
|
23
|
-
- [Single Type Coercion Errors](#single-type-coercion-errors)
|
24
|
-
- [Multiple Type Coercion Errors](#multiple-type-coercion-errors)
|
25
23
|
- [Custom Coercion Options](#custom-coercion-options)
|
26
24
|
- [Date/Time Format Options](#datetime-format-options)
|
27
25
|
- [BigDecimal Precision Options](#bigdecimal-precision-options)
|
26
|
+
- [Custom Coercions](#custom-coercions)
|
28
27
|
|
29
28
|
## TLDR
|
30
29
|
|
@@ -33,6 +32,7 @@ string-to-integer conversion to complex JSON parsing and custom type handling.
|
|
33
32
|
- **No conversion** - Default `:virtual` type returns values unchanged
|
34
33
|
- **Before validation** - Coercion happens automatically before parameter validation
|
35
34
|
- **Rich types** - Supports all Ruby built-ins plus JSON parsing for arrays/hashes
|
35
|
+
- **Custom coercions** - Register custom coercion types
|
36
36
|
|
37
37
|
## Coercion Fundamentals
|
38
38
|
|
@@ -316,13 +316,11 @@ end
|
|
316
316
|
> [!WARNING]
|
317
317
|
> When coercion fails, CMDx provides detailed error information including the parameter name, attempted types, and specific failure reasons.
|
318
318
|
|
319
|
-
### Single Type Coercion Errors
|
320
|
-
|
321
319
|
```ruby
|
322
320
|
class ValidateUserProfileTask < CMDx::Task
|
323
321
|
|
324
322
|
required :age, type: :integer
|
325
|
-
required :salary, type: :float
|
323
|
+
required :salary, type: [:float, :big_decimal]
|
326
324
|
required :is_employed, type: :boolean
|
327
325
|
|
328
326
|
def call
|
@@ -341,46 +339,15 @@ result = ValidateUserProfileTask.call(
|
|
341
339
|
result.failed? #=> true
|
342
340
|
result.metadata
|
343
341
|
#=> {
|
344
|
-
# reason: "age could not coerce into an integer.
|
342
|
+
# reason: "age could not coerce into an integer. could not coerce into one of: float, big_decimal. is_employed could not coerce into a boolean.",
|
345
343
|
# messages: {
|
346
344
|
# age: ["could not coerce into an integer"],
|
347
|
-
# salary: ["could not coerce into
|
345
|
+
# salary: ["could not coerce into one of: float, big_decimal"],
|
348
346
|
# is_employed: ["could not coerce into a boolean"]
|
349
347
|
# }
|
350
348
|
# }
|
351
349
|
```
|
352
350
|
|
353
|
-
### Multiple Type Coercion Errors
|
354
|
-
|
355
|
-
```ruby
|
356
|
-
class ProcessFlexibleDataTask < CMDx::Task
|
357
|
-
|
358
|
-
required :order_value, type: [:float, :integer]
|
359
|
-
required :customer_data, type: [:hash, :array, :string]
|
360
|
-
|
361
|
-
def call
|
362
|
-
# Task logic here
|
363
|
-
end
|
364
|
-
|
365
|
-
end
|
366
|
-
|
367
|
-
# Failed coercion with multiple types
|
368
|
-
result = ProcessFlexibleDataTask.call(
|
369
|
-
order_value: "invalid-number",
|
370
|
-
customer_data: Object.new
|
371
|
-
)
|
372
|
-
|
373
|
-
result.failed? #=> true
|
374
|
-
result.metadata
|
375
|
-
#=> {
|
376
|
-
# reason: "order_value could not coerce into one of: float, integer. customer_data could not coerce into one of: hash, array, string.",
|
377
|
-
# messages: {
|
378
|
-
# order_value: ["could not coerce into one of: float, integer"],
|
379
|
-
# customer_data: ["could not coerce into one of: hash, array, string"]
|
380
|
-
# }
|
381
|
-
# }
|
382
|
-
```
|
383
|
-
|
384
351
|
## Custom Coercion Options
|
385
352
|
|
386
353
|
### Date/Time Format Options
|
@@ -428,6 +395,50 @@ class CalculatePricingTask < CMDx::Task
|
|
428
395
|
end
|
429
396
|
```
|
430
397
|
|
398
|
+
## Custom Coercions
|
399
|
+
|
400
|
+
> [!NOTE]
|
401
|
+
> CMDx allows you to register custom coercions for domain-specific types that aren't covered by the built-in coercions. Custom coercions can be registered globally or per-task basis.
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
module MoneyCoercion
|
405
|
+
module_function
|
406
|
+
|
407
|
+
def call(value, options = {})
|
408
|
+
return value if value.is_a?(BigDecimal)
|
409
|
+
|
410
|
+
# Handle string amounts like "$123.45"
|
411
|
+
if value.is_a?(String)
|
412
|
+
clean_value = value.gsub(/[$,]/, '')
|
413
|
+
BigDecimal(clean_value)
|
414
|
+
else
|
415
|
+
BigDecimal(value.to_s)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
CMDx.configure do |config|
|
421
|
+
config.coercions.register(:money, MoneyCoercion)
|
422
|
+
config.coercions.register(:slug, proc do |value|
|
423
|
+
value.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/-+/, '-').strip('-')
|
424
|
+
end)
|
425
|
+
end
|
426
|
+
|
427
|
+
# Now use in any task
|
428
|
+
class ProcessProductTask < CMDx::Task
|
429
|
+
required :cost, type: :money
|
430
|
+
required :slug, type: :slug
|
431
|
+
|
432
|
+
def call
|
433
|
+
cost #=> 123.45
|
434
|
+
slug #=> "my-blog-post-title" (URL-friendly)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
```
|
438
|
+
|
439
|
+
> [!TIP]
|
440
|
+
> Custom coercions should be idempotent - calling them multiple times with the same input should produce the same result. This ensures predictable behavior when coercions are applied during parameter processing.
|
441
|
+
|
431
442
|
---
|
432
443
|
|
433
444
|
- **Prev:** [Parameters - Namespacing](namespacing.md)
|
data/docs/parameters/defaults.md
CHANGED
@@ -180,7 +180,7 @@ class ValidateOrderPriorityTask < CMDx::Task
|
|
180
180
|
|
181
181
|
# Custom validation with default
|
182
182
|
optional :approval_code, type: :string, default: :generate_approval_code,
|
183
|
-
|
183
|
+
presence: true
|
184
184
|
|
185
185
|
def call
|
186
186
|
priority #=> "standard" (validated against inclusion list)
|
@@ -12,7 +12,6 @@ Parameter values can be validated using built-in validators or custom validation
|
|
12
12
|
- [Inclusion](#inclusion)
|
13
13
|
- [Length](#length)
|
14
14
|
- [Numeric](#numeric)
|
15
|
-
- [Custom](#custom)
|
16
15
|
- [Validation Results](#validation-results)
|
17
16
|
|
18
17
|
## TLDR
|
@@ -21,7 +20,6 @@ Parameter values can be validated using built-in validators or custom validation
|
|
21
20
|
- **Common options** - All support `:allow_nil`, `:if`, `:unless`, `:message`
|
22
21
|
- **Usage** - Add to parameter definitions: `required :email, presence: true, format: { with: /@/ }`
|
23
22
|
- **Conditional** - Use `:if` and `:unless` for conditional validation
|
24
|
-
- **Custom validators** - Use `custom: { validator: CustomValidator }` for complex logic
|
25
23
|
|
26
24
|
## Common Options
|
27
25
|
|
@@ -247,43 +245,6 @@ end
|
|
247
245
|
| `:is_message` | "must be %{is}" |
|
248
246
|
| `:is_not_message` | "must not be %{is_not}" |
|
249
247
|
|
250
|
-
## Custom
|
251
|
-
|
252
|
-
Validates using custom logic. Accepts any callable object (class, proc, lambda) implementing a `call` method that returns truthy for valid values.
|
253
|
-
|
254
|
-
```ruby
|
255
|
-
class EmailDomainValidator
|
256
|
-
def self.call(value, options)
|
257
|
-
allowed_domains = options.dig(:custom, :allowed_domains) || ['example.com']
|
258
|
-
domain = value.split('@').last
|
259
|
-
allowed_domains.include?(domain)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
class CreateAccountTask < CMDx::Task
|
264
|
-
required :work_email, custom: {
|
265
|
-
validator: EmailDomainValidator,
|
266
|
-
allowed_domains: ['company.com', 'partner.org'],
|
267
|
-
message: "must be from an approved domain"
|
268
|
-
}
|
269
|
-
|
270
|
-
required :age, custom: {
|
271
|
-
validator: ->(value, options) { value.between?(18, 120) },
|
272
|
-
message: "must be a valid age"
|
273
|
-
}
|
274
|
-
|
275
|
-
def call
|
276
|
-
create_user_account
|
277
|
-
end
|
278
|
-
end
|
279
|
-
```
|
280
|
-
|
281
|
-
**Options:**
|
282
|
-
|
283
|
-
| Option | Description |
|
284
|
-
| ------------ | ----------- |
|
285
|
-
| `:validator` | Callable object returning true/false. Receives value and options as parameters |
|
286
|
-
|
287
248
|
## Validation Results
|
288
249
|
|
289
250
|
When validation fails, tasks enter a failed state with detailed error information:
|
data/docs/testing.md
CHANGED
@@ -24,7 +24,7 @@ CMDx provides a comprehensive suite of custom RSpec matchers designed for expres
|
|
24
24
|
## TLDR
|
25
25
|
|
26
26
|
- **Custom matchers** - 40+ specialized RSpec matchers for testing CMDx tasks and results
|
27
|
-
- **Setup** - Require `cmdx/rspec/
|
27
|
+
- **Setup** - Require `cmdx/rspec/matchers`
|
28
28
|
- **Result matchers** - `be_successful_task`, `be_failed_task`, `be_skipped_task` with chainable metadata
|
29
29
|
- **Task matchers** - Parameter validation, lifecycle, exception handling, and configuration testing
|
30
30
|
- **Composable** - Chain matchers for complex validation scenarios
|
@@ -35,18 +35,17 @@ CMDx provides a comprehensive suite of custom RSpec matchers designed for expres
|
|
35
35
|
To use CMDx's custom matchers in an external RSpec-based project update your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
require "cmdx/rspec/
|
39
|
-
require "cmdx/rspec/task_matchers"
|
38
|
+
require "cmdx/rspec/matchers"
|
40
39
|
```
|
41
40
|
|
42
41
|
## Matcher Organization
|
43
42
|
|
44
43
|
CMDx matchers are organized into two primary files with comprehensive YARD documentation:
|
45
44
|
|
46
|
-
|
|
47
|
-
|
48
|
-
|
|
49
|
-
|
|
45
|
+
| Purpose | Matcher Count |
|
46
|
+
|---------|---------------|
|
47
|
+
| Task execution outcomes and side effects | 15+ matchers |
|
48
|
+
| Task behavior, validation, and lifecycle | 5+ matchers |
|
50
49
|
|
51
50
|
All matchers include:
|
52
51
|
- Complete parameter descriptions
|
@@ -437,15 +436,15 @@ expect(SimpleTask).not_to have_middleware(ComplexMiddleware)
|
|
437
436
|
|
438
437
|
```ruby
|
439
438
|
# Test setting presence
|
440
|
-
expect(ConfiguredTask).to
|
441
|
-
expect(CustomTask).to
|
439
|
+
expect(ConfiguredTask).to have_cmd_setting(:timeout)
|
440
|
+
expect(CustomTask).to have_cmd_setting(:priority)
|
442
441
|
|
443
442
|
# Test setting with specific value
|
444
|
-
expect(TimedTask).to
|
445
|
-
expect(PriorityTask).to
|
443
|
+
expect(TimedTask).to have_cmd_setting(:timeout, 30)
|
444
|
+
expect(PriorityTask).to have_cmd_setting(:priority, "high")
|
446
445
|
|
447
446
|
# Negated usage
|
448
|
-
expect(SimpleTask).not_to
|
447
|
+
expect(SimpleTask).not_to have_cmd_setting(:complex_setting)
|
449
448
|
```
|
450
449
|
|
451
450
|
## Composable Testing
|
data/docs/workflows.md
CHANGED
@@ -153,12 +153,12 @@ end
|
|
153
153
|
|
154
154
|
### Class-Level Configuration
|
155
155
|
|
156
|
-
Configure halt behavior for the entire workflow using `
|
156
|
+
Configure halt behavior for the entire workflow using `cmd_settings!`:
|
157
157
|
|
158
158
|
```ruby
|
159
159
|
class CriticalDataProcessingWorkflow < CMDx::Workflow
|
160
160
|
# Halt on both failed and skipped results
|
161
|
-
|
161
|
+
cmd_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
|
162
162
|
|
163
163
|
process LoadCriticalDataTask
|
164
164
|
process ValidateCriticalDataTask
|
@@ -166,7 +166,7 @@ end
|
|
166
166
|
|
167
167
|
class OptionalDataProcessingWorkflow < CMDx::Workflow
|
168
168
|
# Never halt, always continue
|
169
|
-
|
169
|
+
cmd_settings!(workflow_halt: [])
|
170
170
|
|
171
171
|
process TryLoadDataTask
|
172
172
|
process TryValidateDataTask
|
@@ -274,7 +274,7 @@ Workflows support all task settings and can be configured like regular tasks:
|
|
274
274
|
```ruby
|
275
275
|
class PaymentProcessingWorkflow < CMDx::Workflow
|
276
276
|
# Configure workflow-specific settings
|
277
|
-
|
277
|
+
cmd_settings!(
|
278
278
|
workflow_halt: [CMDx::Result::FAILED],
|
279
279
|
log_level: :debug,
|
280
280
|
tags: [:critical, :payment]
|
data/lib/cmdx/.DS_Store
CHANGED
Binary file
|