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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +17 -2
  6. data/README.md +1 -1
  7. data/docs/basics/call.md +2 -2
  8. data/docs/basics/chain.md +1 -1
  9. data/docs/callbacks.md +3 -36
  10. data/docs/configuration.md +58 -12
  11. data/docs/interruptions/exceptions.md +1 -1
  12. data/docs/interruptions/faults.md +2 -2
  13. data/docs/logging.md +4 -4
  14. data/docs/middlewares.md +43 -43
  15. data/docs/parameters/coercions.md +49 -38
  16. data/docs/parameters/defaults.md +1 -1
  17. data/docs/parameters/validations.md +0 -39
  18. data/docs/testing.md +11 -12
  19. data/docs/workflows.md +4 -4
  20. data/lib/cmdx/.DS_Store +0 -0
  21. data/lib/cmdx/callback.rb +36 -56
  22. data/lib/cmdx/callback_registry.rb +82 -73
  23. data/lib/cmdx/chain.rb +65 -122
  24. data/lib/cmdx/chain_inspector.rb +22 -115
  25. data/lib/cmdx/chain_serializer.rb +17 -148
  26. data/lib/cmdx/coercion.rb +49 -0
  27. data/lib/cmdx/coercion_registry.rb +94 -0
  28. data/lib/cmdx/coercions/array.rb +18 -36
  29. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  30. data/lib/cmdx/coercions/boolean.rb +21 -40
  31. data/lib/cmdx/coercions/complex.rb +18 -31
  32. data/lib/cmdx/coercions/date.rb +20 -39
  33. data/lib/cmdx/coercions/date_time.rb +22 -39
  34. data/lib/cmdx/coercions/float.rb +19 -32
  35. data/lib/cmdx/coercions/hash.rb +22 -41
  36. data/lib/cmdx/coercions/integer.rb +20 -33
  37. data/lib/cmdx/coercions/rational.rb +20 -32
  38. data/lib/cmdx/coercions/string.rb +23 -31
  39. data/lib/cmdx/coercions/time.rb +24 -40
  40. data/lib/cmdx/coercions/virtual.rb +14 -31
  41. data/lib/cmdx/configuration.rb +57 -171
  42. data/lib/cmdx/context.rb +22 -165
  43. data/lib/cmdx/core_ext/hash.rb +42 -67
  44. data/lib/cmdx/core_ext/module.rb +35 -79
  45. data/lib/cmdx/core_ext/object.rb +63 -98
  46. data/lib/cmdx/correlator.rb +40 -156
  47. data/lib/cmdx/error.rb +37 -202
  48. data/lib/cmdx/errors.rb +165 -202
  49. data/lib/cmdx/fault.rb +55 -158
  50. data/lib/cmdx/faults.rb +26 -137
  51. data/lib/cmdx/immutator.rb +22 -109
  52. data/lib/cmdx/lazy_struct.rb +103 -187
  53. data/lib/cmdx/log_formatters/json.rb +14 -40
  54. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  55. data/lib/cmdx/log_formatters/line.rb +14 -48
  56. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  57. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  58. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  59. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  60. data/lib/cmdx/log_formatters/raw.rb +19 -49
  61. data/lib/cmdx/logger.rb +20 -82
  62. data/lib/cmdx/logger_ansi.rb +18 -75
  63. data/lib/cmdx/logger_serializer.rb +24 -114
  64. data/lib/cmdx/middleware.rb +38 -60
  65. data/lib/cmdx/middleware_registry.rb +81 -77
  66. data/lib/cmdx/middlewares/correlate.rb +41 -226
  67. data/lib/cmdx/middlewares/timeout.rb +46 -185
  68. data/lib/cmdx/parameter.rb +120 -198
  69. data/lib/cmdx/parameter_evaluator.rb +231 -0
  70. data/lib/cmdx/parameter_inspector.rb +25 -56
  71. data/lib/cmdx/parameter_registry.rb +59 -84
  72. data/lib/cmdx/parameter_serializer.rb +23 -74
  73. data/lib/cmdx/railtie.rb +24 -107
  74. data/lib/cmdx/result.rb +254 -260
  75. data/lib/cmdx/result_ansi.rb +19 -85
  76. data/lib/cmdx/result_inspector.rb +27 -68
  77. data/lib/cmdx/result_logger.rb +18 -81
  78. data/lib/cmdx/result_serializer.rb +28 -132
  79. data/lib/cmdx/rspec/matchers.rb +28 -0
  80. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  81. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  82. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  83. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  84. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  85. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  86. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  87. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  88. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  89. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  90. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  91. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  92. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  93. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  94. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  95. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  96. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  97. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  98. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  99. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  100. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  101. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  102. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  103. data/lib/cmdx/task.rb +213 -425
  104. data/lib/cmdx/task_deprecator.rb +55 -0
  105. data/lib/cmdx/task_processor.rb +245 -0
  106. data/lib/cmdx/task_serializer.rb +22 -70
  107. data/lib/cmdx/utils/ansi_color.rb +13 -89
  108. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  109. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  110. data/lib/cmdx/utils/name_affix.rb +21 -71
  111. data/lib/cmdx/validator.rb +48 -0
  112. data/lib/cmdx/validator_registry.rb +86 -0
  113. data/lib/cmdx/validators/exclusion.rb +55 -94
  114. data/lib/cmdx/validators/format.rb +31 -85
  115. data/lib/cmdx/validators/inclusion.rb +65 -110
  116. data/lib/cmdx/validators/length.rb +117 -133
  117. data/lib/cmdx/validators/numeric.rb +123 -130
  118. data/lib/cmdx/validators/presence.rb +38 -79
  119. data/lib/cmdx/version.rb +1 -7
  120. data/lib/cmdx/workflow.rb +46 -339
  121. data/lib/cmdx.rb +1 -1
  122. data/lib/generators/cmdx/install_generator.rb +14 -31
  123. data/lib/generators/cmdx/task_generator.rb +39 -55
  124. data/lib/generators/cmdx/templates/install.rb +24 -6
  125. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  126. data/lib/locales/ar.yml +0 -1
  127. data/lib/locales/cs.yml +0 -1
  128. data/lib/locales/da.yml +0 -1
  129. data/lib/locales/de.yml +0 -1
  130. data/lib/locales/el.yml +0 -1
  131. data/lib/locales/en.yml +0 -1
  132. data/lib/locales/es.yml +0 -1
  133. data/lib/locales/fi.yml +0 -1
  134. data/lib/locales/fr.yml +0 -1
  135. data/lib/locales/he.yml +0 -1
  136. data/lib/locales/hi.yml +0 -1
  137. data/lib/locales/it.yml +0 -1
  138. data/lib/locales/ja.yml +0 -1
  139. data/lib/locales/ko.yml +0 -1
  140. data/lib/locales/nl.yml +0 -1
  141. data/lib/locales/no.yml +0 -1
  142. data/lib/locales/pl.yml +0 -1
  143. data/lib/locales/pt.yml +0 -1
  144. data/lib/locales/ru.yml +0 -1
  145. data/lib/locales/sv.yml +0 -1
  146. data/lib/locales/th.yml +0 -1
  147. data/lib/locales/tr.yml +0 -1
  148. data/lib/locales/vi.yml +0 -1
  149. data/lib/locales/zh.yml +0 -1
  150. metadata +34 -8
  151. data/lib/cmdx/parameter_validator.rb +0 -81
  152. data/lib/cmdx/parameter_value.rb +0 -244
  153. data/lib/cmdx/parameters_inspector.rb +0 -72
  154. data/lib/cmdx/parameters_serializer.rb +0 -115
  155. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  156. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  157. 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. salary could not coerce into a float. is_employed could not coerce into a boolean.",
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 a float"],
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)
@@ -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
- custom: { validator: ApprovalCodeValidator }
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/result_matchers` and `cmdx/rspec/task_matchers`
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/result_matchers"
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
- | File | Purpose | Matcher Count |
47
- |------|---------|---------------|
48
- | `result_matchers.rb` | Task execution outcomes and side effects | 25+ matchers |
49
- | `task_matchers.rb` | Task behavior, validation, and lifecycle | 15+ matchers |
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 have_task_setting(:timeout)
441
- expect(CustomTask).to have_task_setting(:priority)
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 have_task_setting(:timeout, 30)
445
- expect(PriorityTask).to have_task_setting(:priority, "high")
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 have_task_setting(:complex_setting)
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 `task_settings!`:
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
- task_settings!(workflow_halt: [CMDx::Result::FAILED, CMDx::Result::SKIPPED])
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
- task_settings!(workflow_halt: [])
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
- task_settings!(
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