cmdx 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/prompts/docs.md +9 -0
- data/.cursor/prompts/rspec.md +21 -0
- data/.cursor/prompts/yardoc.md +13 -0
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +2 -1
- data/docs/ai_prompts.md +269 -195
- data/docs/basics/call.md +126 -60
- data/docs/basics/chain.md +190 -160
- data/docs/basics/context.md +242 -154
- data/docs/basics/setup.md +302 -32
- data/docs/callbacks.md +382 -119
- data/docs/configuration.md +211 -49
- data/docs/deprecation.md +245 -0
- data/docs/getting_started.md +161 -39
- data/docs/internationalization.md +590 -70
- data/docs/interruptions/exceptions.md +135 -118
- data/docs/interruptions/faults.md +152 -127
- data/docs/interruptions/halt.md +134 -80
- data/docs/logging.md +183 -120
- data/docs/middlewares.md +165 -392
- data/docs/outcomes/result.md +140 -112
- data/docs/outcomes/states.md +134 -99
- data/docs/outcomes/statuses.md +204 -146
- data/docs/parameters/coercions.md +251 -289
- data/docs/parameters/defaults.md +224 -169
- data/docs/parameters/definitions.md +289 -141
- data/docs/parameters/namespacing.md +250 -161
- data/docs/parameters/validations.md +247 -159
- data/docs/testing.md +196 -203
- data/docs/workflows.md +146 -101
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +39 -55
- data/lib/cmdx/callback_registry.rb +80 -73
- data/lib/cmdx/chain.rb +65 -122
- data/lib/cmdx/chain_inspector.rb +23 -116
- data/lib/cmdx/chain_serializer.rb +34 -146
- data/lib/cmdx/coercion.rb +57 -0
- data/lib/cmdx/coercion_registry.rb +113 -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 +101 -162
- data/lib/cmdx/context.rb +34 -166
- 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 +59 -154
- data/lib/cmdx/error.rb +37 -202
- data/lib/cmdx/errors.rb +153 -216
- data/lib/cmdx/fault.rb +68 -150
- data/lib/cmdx/faults.rb +26 -137
- data/lib/cmdx/immutator.rb +22 -110
- data/lib/cmdx/lazy_struct.rb +110 -186
- 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 +22 -79
- data/lib/cmdx/logger_ansi.rb +31 -72
- data/lib/cmdx/logger_serializer.rb +74 -103
- data/lib/cmdx/middleware.rb +56 -60
- data/lib/cmdx/middleware_registry.rb +82 -77
- data/lib/cmdx/middlewares/correlate.rb +41 -226
- data/lib/cmdx/middlewares/timeout.rb +46 -185
- data/lib/cmdx/parameter.rb +167 -183
- data/lib/cmdx/parameter_evaluator.rb +231 -0
- data/lib/cmdx/parameter_inspector.rb +37 -55
- data/lib/cmdx/parameter_registry.rb +65 -84
- data/lib/cmdx/parameter_serializer.rb +32 -76
- data/lib/cmdx/railtie.rb +24 -107
- data/lib/cmdx/result.rb +254 -259
- data/lib/cmdx/result_ansi.rb +28 -80
- data/lib/cmdx/result_inspector.rb +34 -70
- data/lib/cmdx/result_logger.rb +23 -77
- data/lib/cmdx/result_serializer.rb +59 -125
- 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 +336 -427
- data/lib/cmdx/task_deprecator.rb +52 -0
- data/lib/cmdx/task_processor.rb +246 -0
- data/lib/cmdx/task_serializer.rb +34 -69
- 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 +11 -63
- data/lib/cmdx/utils/name_affix.rb +21 -71
- data/lib/cmdx/validator.rb +57 -0
- data/lib/cmdx/validator_registry.rb +108 -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 +58 -330
- 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 +36 -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
@@ -1,103 +1,108 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# The MiddlewareRegistry collection provides a Rack-style middleware chain that wraps
|
6
|
-
# task execution with cross-cutting concerns like logging, authentication,
|
7
|
-
# caching, and more. Middleware can short-circuit execution by returning
|
8
|
-
# early without calling the next middleware in the chain.
|
4
|
+
# Registry for managing middleware definitions and execution within tasks.
|
9
5
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# middleware_registry.use(AuthenticationMiddleware, required_role: :admin)
|
18
|
-
# middleware_registry.use(CachingMiddleware, ttl: 300)
|
19
|
-
#
|
20
|
-
# result = middleware_registry.call(task) do |t|
|
21
|
-
# t.call
|
22
|
-
# t.result
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# @example Array-like operations
|
26
|
-
# middleware_registry << [LoggingMiddleware, [], nil]
|
27
|
-
# middleware_registry.size # => 1
|
28
|
-
# middleware_registry.empty? # => false
|
29
|
-
# middleware_registry.each { |middleware| puts middleware.inspect }
|
30
|
-
#
|
31
|
-
# @example Using proc middleware
|
32
|
-
# middleware_registry.use(proc do |task, callable|
|
33
|
-
# puts "Before task execution"
|
34
|
-
# result = callable.call(task)
|
35
|
-
# puts "After task execution"
|
36
|
-
# result
|
37
|
-
# end)
|
38
|
-
#
|
39
|
-
# @see Middleware Base middleware class
|
40
|
-
# @since 1.0.0
|
41
|
-
class MiddlewareRegistry < Array
|
6
|
+
# This registry handles the registration and execution of middleware that can
|
7
|
+
# wrap task execution, providing cross-cutting concerns like logging, timing,
|
8
|
+
# authentication, and error handling.
|
9
|
+
class MiddlewareRegistry
|
10
|
+
|
11
|
+
# @return [Hash] hash containing middleware classes/objects and their configurations
|
12
|
+
attr_reader :registry
|
42
13
|
|
43
|
-
#
|
14
|
+
# Initializes a new middleware registry.
|
15
|
+
#
|
16
|
+
# @param registry [Hash] optional hash of initial middleware configurations
|
17
|
+
#
|
18
|
+
# @return [MiddlewareRegistry] a new middleware registry instance
|
19
|
+
#
|
20
|
+
# @example Creating an empty registry
|
21
|
+
# MiddlewareRegistry.new
|
22
|
+
#
|
23
|
+
# @example Creating a registry with initial middleware
|
24
|
+
# MiddlewareRegistry.new(TimeoutMiddleware => [[], {timeout: 30}, nil])
|
25
|
+
def initialize(registry = {})
|
26
|
+
@registry = registry.to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
# Registers a middleware with the registry.
|
30
|
+
#
|
31
|
+
# @param middleware [Class, Object] the middleware class or instance to register
|
32
|
+
# @param args [Array] positional arguments to pass to middleware initialization
|
33
|
+
# @param kwargs [Hash] keyword arguments to pass to middleware initialization
|
34
|
+
# @param block [Proc] optional block to pass to middleware initialization
|
44
35
|
#
|
45
|
-
# @param middleware [Class, Object, Proc] The middleware to add
|
46
|
-
# @param args [Array] Arguments to pass to middleware constructor
|
47
|
-
# @param block [Proc] Block to pass to middleware constructor
|
48
36
|
# @return [MiddlewareRegistry] self for method chaining
|
49
37
|
#
|
50
|
-
# @example
|
51
|
-
# registry.
|
38
|
+
# @example Register a middleware class
|
39
|
+
# registry.register(TimeoutMiddleware, 30)
|
52
40
|
#
|
53
|
-
# @example
|
54
|
-
# registry.
|
41
|
+
# @example Register a middleware with keyword arguments
|
42
|
+
# registry.register(LoggingMiddleware, level: :info)
|
55
43
|
#
|
56
|
-
# @example
|
57
|
-
# registry.
|
58
|
-
def
|
59
|
-
|
44
|
+
# @example Register a middleware with a block
|
45
|
+
# registry.register(CustomMiddleware) { |task| puts "Processing #{task.id}" }
|
46
|
+
def register(middleware, *args, **kwargs, &block)
|
47
|
+
registry[middleware] = [args, kwargs, block]
|
60
48
|
self
|
61
49
|
end
|
62
50
|
|
63
|
-
# Executes
|
64
|
-
#
|
65
|
-
# @param task [Task]
|
66
|
-
# @
|
67
|
-
#
|
68
|
-
# @return [Object]
|
69
|
-
#
|
70
|
-
# @
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
51
|
+
# Executes all registered middleware around the provided task.
|
52
|
+
#
|
53
|
+
# @param task [Task] the task instance to execute middleware around
|
54
|
+
# @param block [Proc] the block to execute after all middleware processing
|
55
|
+
#
|
56
|
+
# @return [Object] the result of the middleware chain execution
|
57
|
+
#
|
58
|
+
# @raise [ArgumentError] if no block is provided
|
59
|
+
#
|
60
|
+
# @example Execute middleware around a task
|
61
|
+
# registry.call(task) { |task| task.process }
|
62
|
+
#
|
63
|
+
# @example Execute with early return if no middleware
|
64
|
+
# registry.call(task) { |task| puts "No middleware to execute" }
|
75
65
|
def call(task, &)
|
76
|
-
|
66
|
+
raise ArgumentError, "block required" unless block_given?
|
67
|
+
|
68
|
+
return yield(task) if registry.empty?
|
77
69
|
|
78
70
|
build_chain(&).call(task)
|
79
71
|
end
|
80
72
|
|
73
|
+
# Returns a hash representation of the registry.
|
74
|
+
#
|
75
|
+
# @return [Hash] deep copy of registry with duplicated configuration arrays
|
76
|
+
# @option return [Array] args duplicated positional arguments array
|
77
|
+
# @option return [Hash] kwargs duplicated keyword arguments hash
|
78
|
+
# @option return [Proc, nil] block the original block reference (not duplicated)
|
79
|
+
#
|
80
|
+
# @example Getting registry hash
|
81
|
+
# registry.to_h
|
82
|
+
# #=> { TimeoutMiddleware => [[30], {}, nil] }
|
83
|
+
def to_h
|
84
|
+
registry.transform_values do |config|
|
85
|
+
args, kwargs, block = config
|
86
|
+
[args.dup, kwargs.dup, block]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
81
90
|
private
|
82
91
|
|
83
|
-
# Builds the middleware call
|
92
|
+
# Builds the middleware execution chain by wrapping middleware around the call block.
|
93
|
+
#
|
94
|
+
# @param call_block [Proc] the final block to execute after all middleware
|
84
95
|
#
|
85
|
-
#
|
86
|
-
# with the provided block as the innermost callable.
|
96
|
+
# @return [Proc] the complete middleware chain as a callable proc
|
87
97
|
#
|
88
|
-
# @
|
89
|
-
#
|
90
|
-
def build_chain(&
|
91
|
-
|
98
|
+
# @example Building a middleware chain (internal use)
|
99
|
+
# build_chain { |task| task.process }
|
100
|
+
def build_chain(&call_block)
|
101
|
+
registry.reverse_each.reduce(call_block) do |next_callable, (middleware, config)|
|
92
102
|
proc do |task|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
else
|
97
|
-
# Class or instance middleware
|
98
|
-
instance = middleware.respond_to?(:new) ? middleware.new(*args, &middleware_block) : middleware
|
99
|
-
instance.call(task, next_callable)
|
100
|
-
end
|
103
|
+
args, kwargs, block = config
|
104
|
+
instance = middleware.respond_to?(:new) ? middleware.new(*args, **kwargs, &block) : middleware
|
105
|
+
instance.call(task, next_callable)
|
101
106
|
end
|
102
107
|
end
|
103
108
|
end
|
@@ -2,255 +2,70 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module Middlewares
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# The Correlate middleware establishes and maintains correlation ID context throughout
|
9
|
-
# task execution, enabling seamless request tracking across task boundaries. It ensures
|
10
|
-
# that all tasks within an execution chain share the same correlation identifier for
|
11
|
-
# comprehensive traceability and debugging.
|
12
|
-
#
|
13
|
-
# ## Correlation ID Precedence
|
14
|
-
#
|
15
|
-
# The middleware determines the correlation ID using the following precedence:
|
16
|
-
# 1. **Explicit correlation ID** - Value provided during middleware initialization
|
17
|
-
# 2. **Current thread correlation** - Existing correlation from `CMDx::Correlator.id`
|
18
|
-
# 3. **Chain identifier** - The task's chain ID if no thread correlation exists
|
19
|
-
# 4. **Generated UUID** - New correlation ID if none of the above is available
|
20
|
-
#
|
21
|
-
# ## Conditional Execution
|
22
|
-
#
|
23
|
-
# The middleware supports conditional execution using `:if` and `:unless` options:
|
24
|
-
# - `:if` - Only applies correlation when the condition evaluates to true
|
25
|
-
# - `:unless` - Only applies correlation when the condition evaluates to false
|
26
|
-
# - Conditions can be Procs, method symbols, or boolean values
|
27
|
-
#
|
28
|
-
# ## Thread Safety
|
29
|
-
#
|
30
|
-
# The middleware uses `CMDx::Correlator.use` to establish a correlation context
|
31
|
-
# that is automatically restored after task execution, ensuring thread-local
|
32
|
-
# isolation and proper cleanup even in case of exceptions.
|
33
|
-
#
|
34
|
-
# ## Integration with CMDx Framework
|
35
|
-
#
|
36
|
-
# - **Automatic activation**: Can be applied globally or per-task via `use` directive
|
37
|
-
# - **Chain integration**: Works seamlessly with CMDx::Chain correlation inheritance
|
38
|
-
# - **Nested tasks**: Maintains correlation context across nested task calls
|
39
|
-
# - **Exception safety**: Restores correlation context even when tasks fail
|
40
|
-
#
|
41
|
-
# @example Basic task-specific middleware application
|
42
|
-
# class ProcessOrderTask < CMDx::Task
|
43
|
-
# use CMDx::Middlewares::Correlate
|
44
|
-
#
|
45
|
-
# def call
|
46
|
-
# # Task execution maintains correlation context
|
47
|
-
# SendEmailTask.call(context) # Inherits same correlation
|
48
|
-
# end
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# @example Middleware with explicit correlation ID
|
52
|
-
# class ProcessOrderTask < CMDx::Task
|
53
|
-
# use CMDx::Middlewares::Correlate, id: "order-processing-123"
|
54
|
-
#
|
55
|
-
# def call
|
56
|
-
# # Always uses "order-processing-123" as correlation ID
|
57
|
-
# context.correlation_used = CMDx::Correlator.id
|
58
|
-
# end
|
59
|
-
# end
|
60
|
-
#
|
61
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
62
|
-
# result.context.correlation_used # => "order-processing-123"
|
63
|
-
#
|
64
|
-
# @example Middleware with dynamic correlation ID using procs
|
65
|
-
# class ProcessOrderTask < CMDx::Task
|
66
|
-
# use CMDx::Middlewares::Correlate, id: -> { "order-#{order_id}-#{Time.now.to_i}" }
|
67
|
-
#
|
68
|
-
# def call
|
69
|
-
# # Uses dynamically generated correlation ID
|
70
|
-
# context.correlation_used = CMDx::Correlator.id
|
71
|
-
# end
|
72
|
-
# end
|
73
|
-
#
|
74
|
-
# result = ProcessOrderTask.call(order_id: 456)
|
75
|
-
# result.context.correlation_used # => "order-456-1703123456"
|
76
|
-
#
|
77
|
-
# @example Middleware with method-based correlation ID
|
78
|
-
# class ProcessOrderTask < CMDx::Task
|
79
|
-
# use CMDx::Middlewares::Correlate, id: :generate_order_correlation
|
80
|
-
#
|
81
|
-
# def call
|
82
|
-
# # Uses correlation ID from generate_order_correlation method
|
83
|
-
# context.correlation_used = CMDx::Correlator.id
|
84
|
-
# end
|
85
|
-
#
|
86
|
-
# private
|
87
|
-
#
|
88
|
-
# def generate_order_correlation
|
89
|
-
# "order-#{order_id}-#{context.request_id}"
|
90
|
-
# end
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# @example Conditional correlation based on environment
|
94
|
-
# class ProcessOrderTask < CMDx::Task
|
95
|
-
# use CMDx::Middlewares::Correlate, unless: -> { Rails.env.test? }
|
96
|
-
#
|
97
|
-
# def call
|
98
|
-
# # Correlation only applied in non-test environments
|
99
|
-
# context.order = Order.find(order_id)
|
100
|
-
# end
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# @example Conditional correlation based on task state
|
104
|
-
# class ProcessOrderTask < CMDx::Task
|
105
|
-
# use CMDx::Middlewares::Correlate, if: :correlation_required?
|
106
|
-
#
|
107
|
-
# def call
|
108
|
-
# # Correlation applied only when correlation_required? returns true
|
109
|
-
# context.order = Order.find(order_id)
|
110
|
-
# end
|
111
|
-
#
|
112
|
-
# private
|
113
|
-
#
|
114
|
-
# def correlation_required?
|
115
|
-
# context.tracking_enabled == true
|
116
|
-
# end
|
117
|
-
# end
|
118
|
-
#
|
119
|
-
# @example Nested task correlation propagation
|
120
|
-
# class ParentTask < CMDx::Task
|
121
|
-
# use CMDx::Middlewares::Correlate
|
122
|
-
#
|
123
|
-
# def call
|
124
|
-
# # Correlation established at parent level
|
125
|
-
# ChildTask.call(context)
|
126
|
-
# end
|
127
|
-
# end
|
128
|
-
#
|
129
|
-
# class ChildTask < CMDx::Task
|
130
|
-
# use CMDx::Middlewares::Correlate
|
131
|
-
#
|
132
|
-
# def call
|
133
|
-
# # Inherits parent's correlation ID
|
134
|
-
# context.child_correlation = CMDx::Correlator.id
|
135
|
-
# end
|
136
|
-
# end
|
137
|
-
#
|
138
|
-
# @example Exception handling with correlation restoration
|
139
|
-
# class RiskyTask < CMDx::Task
|
140
|
-
# use CMDx::Middlewares::Correlate
|
141
|
-
#
|
142
|
-
# def call
|
143
|
-
# raise StandardError, "Task failed"
|
144
|
-
# end
|
145
|
-
# end
|
146
|
-
#
|
147
|
-
# CMDx::Correlator.id = "original-correlation"
|
148
|
-
#
|
149
|
-
# begin
|
150
|
-
# RiskyTask.call
|
151
|
-
# rescue StandardError
|
152
|
-
# CMDx::Correlator.id # => "original-correlation" (properly restored)
|
153
|
-
# end
|
154
|
-
#
|
155
|
-
# @see CMDx::Correlator Thread-safe correlation ID management
|
156
|
-
# @see CMDx::Chain Chain execution context with correlation inheritance
|
157
|
-
# @see CMDx::Middleware Base middleware class
|
158
|
-
# @since 1.0.0
|
5
|
+
# Middleware that manages correlation IDs for task execution tracing.
|
6
|
+
# Automatically generates or uses provided correlation IDs to track task execution
|
7
|
+
# across complex workflows, enabling better debugging and monitoring.
|
159
8
|
class Correlate < CMDx::Middleware
|
160
9
|
|
161
|
-
# @return [String, nil] The explicit correlation ID to use
|
10
|
+
# @return [String, Symbol, Proc, nil] The explicit correlation ID to use, or callable that generates one
|
11
|
+
attr_reader :id
|
12
|
+
|
162
13
|
# @return [Hash] The conditional options for correlation application
|
163
|
-
attr_reader :
|
14
|
+
attr_reader :conditional
|
164
15
|
|
165
|
-
|
166
|
-
# Initializes the Correlate middleware with optional configuration.
|
16
|
+
# Initializes the correlation middleware with optional configuration.
|
167
17
|
#
|
168
18
|
# @param options [Hash] configuration options for the middleware
|
169
|
-
# @option options [String, Symbol, Proc] :id explicit correlation ID
|
170
|
-
# @option options [
|
171
|
-
# @option options [
|
19
|
+
# @option options [String, Symbol, Proc] :id explicit correlation ID or callable to generate one
|
20
|
+
# @option options [Symbol, Proc] :if condition that must be truthy to apply correlation
|
21
|
+
# @option options [Symbol, Proc] :unless condition that must be falsy to apply correlation
|
22
|
+
#
|
23
|
+
# @return [Correlate] new instance of the middleware
|
24
|
+
#
|
25
|
+
# @example Register with a middleware instance
|
26
|
+
# use :middleware, CMDx::Middlewares::Correlate.new(id: "request-123")
|
172
27
|
#
|
173
|
-
# @example
|
174
|
-
# middleware
|
28
|
+
# @example Register with explicit ID
|
29
|
+
# use :middleware, CMDx::Middlewares::Correlate, id: "request-123"
|
175
30
|
#
|
176
|
-
# @example
|
177
|
-
# middleware
|
31
|
+
# @example Register with dynamic ID generation
|
32
|
+
# use :middleware, CMDx::Middlewares::Correlate, id: -> { SecureRandom.uuid }
|
178
33
|
#
|
179
|
-
# @example
|
180
|
-
# middleware
|
181
|
-
# middleware = CMDx::Middlewares::Correlate.new(if: :correlation_enabled?)
|
34
|
+
# @example Register with conditions
|
35
|
+
# use :middleware, CMDx::Middlewares::Correlate, if: :production?, unless: :testing?
|
182
36
|
def initialize(options = {})
|
183
37
|
@id = options[:id]
|
184
38
|
@conditional = options.slice(:if, :unless)
|
185
39
|
end
|
186
40
|
|
187
|
-
|
188
|
-
#
|
189
|
-
#
|
190
|
-
# First evaluates any conditional execution rules (`:if` or `:unless` options).
|
191
|
-
# If conditions allow execution, establishes a correlation ID using the
|
192
|
-
# precedence hierarchy and executes the task within that correlation context.
|
193
|
-
# The correlation ID is automatically restored after task completion, ensuring
|
194
|
-
# proper cleanup and thread isolation.
|
195
|
-
#
|
196
|
-
# The correlation ID determination follows this precedence:
|
197
|
-
# 1. Explicit correlation ID (provided during middleware initialization)
|
198
|
-
# - String/Symbol: Used as-is or called as method if task responds to it
|
199
|
-
# - Proc/Lambda: Executed in task context for dynamic generation
|
200
|
-
# 2. Current thread correlation (CMDx::Correlator.id)
|
201
|
-
# 3. Task's chain ID (task.chain.id)
|
202
|
-
# 4. Generated UUID (CMDx::Correlator.generate)
|
203
|
-
#
|
204
|
-
# @param task [CMDx::Task] the task instance to execute
|
205
|
-
# @param callable [#call] the callable that executes the task
|
206
|
-
# @return [CMDx::Result] the task execution result
|
207
|
-
#
|
208
|
-
# @example Basic middleware execution
|
209
|
-
# middleware = CMDx::Middlewares::Correlate.new
|
210
|
-
# task = ProcessOrderTask.new(order_id: 123)
|
211
|
-
# callable = -> { task.call }
|
212
|
-
#
|
213
|
-
# result = middleware.call(task, callable)
|
214
|
-
# # Task executed within correlation context
|
215
|
-
#
|
216
|
-
# @example Correlation ID precedence in action
|
217
|
-
# # Scenario 1: Explicit string correlation ID takes precedence
|
218
|
-
# middleware = CMDx::Middlewares::Correlate.new(id: "explicit-123")
|
219
|
-
# middleware.call(task, callable) # Uses "explicit-123"
|
220
|
-
#
|
221
|
-
# # Scenario 2: Dynamic correlation ID using proc
|
222
|
-
# middleware = CMDx::Middlewares::Correlate.new(id: -> { "dynamic-#{order_id}" })
|
223
|
-
# middleware.call(task, callable) # Uses result of proc execution
|
41
|
+
# Executes the middleware, wrapping task execution with correlation context.
|
42
|
+
# Evaluates conditions, determines correlation ID, and executes the task within
|
43
|
+
# the correlation context for tracing purposes.
|
224
44
|
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
# middleware.call(task, callable) # Uses task.correlation_method if it exists
|
45
|
+
# @param task [CMDx::Task] the task being executed
|
46
|
+
# @param callable [Proc] the callable that executes the task
|
228
47
|
#
|
229
|
-
#
|
230
|
-
# CMDx::Correlator.id = "thread-correlation"
|
231
|
-
# middleware = CMDx::Middlewares::Correlate.new
|
232
|
-
# middleware.call(task, callable) # Uses "thread-correlation"
|
48
|
+
# @return [Object] the result of the task execution
|
233
49
|
#
|
234
|
-
#
|
235
|
-
# CMDx::
|
236
|
-
#
|
50
|
+
# @example Task using correlation middleware
|
51
|
+
# class ProcessOrderTask < CMDx::Task
|
52
|
+
# use :middleware, CMDx::Middlewares::Correlate, id: "trace-123"
|
237
53
|
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
54
|
+
# def call
|
55
|
+
# # Task execution is automatically wrapped with correlation
|
56
|
+
# end
|
57
|
+
# end
|
242
58
|
#
|
243
|
-
# @example
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
# # Correlation applied only in production environment
|
59
|
+
# @example Global configuration with conditional tracing
|
60
|
+
# CMDx.configure do |config|
|
61
|
+
# config.middlewares.register CMDx::Middlewares::Correlate, if: :should_trace?
|
62
|
+
# end
|
248
63
|
def call(task, callable)
|
249
64
|
# Check if correlation should be applied based on conditions
|
250
|
-
return callable.call(task) unless task.
|
65
|
+
return callable.call(task) unless task.cmdx_eval(conditional)
|
251
66
|
|
252
67
|
# Get correlation ID using yield for dynamic generation
|
253
|
-
correlation_id = task.
|
68
|
+
correlation_id = task.cmdx_yield(id) ||
|
254
69
|
CMDx::Correlator.id ||
|
255
70
|
task.chain.id ||
|
256
71
|
CMDx::Correlator.generate
|