cmdx 1.0.0 → 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 +5 -0
- data/CHANGELOG.md +101 -49
- data/README.md +2 -1
- data/docs/ai_prompts.md +10 -0
- data/docs/basics/call.md +11 -2
- data/docs/basics/chain.md +10 -1
- data/docs/basics/context.md +9 -0
- data/docs/basics/setup.md +9 -0
- data/docs/callbacks.md +14 -37
- data/docs/configuration.md +68 -27
- data/docs/getting_started.md +11 -0
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +10 -1
- data/docs/interruptions/faults.md +11 -2
- data/docs/interruptions/halt.md +9 -0
- data/docs/logging.md +14 -4
- data/docs/middlewares.md +53 -43
- data/docs/outcomes/result.md +9 -0
- data/docs/outcomes/states.md +9 -0
- data/docs/outcomes/statuses.md +9 -0
- data/docs/parameters/coercions.md +58 -38
- data/docs/parameters/defaults.md +10 -1
- data/docs/parameters/definitions.md +9 -0
- data/docs/parameters/namespacing.md +9 -0
- data/docs/parameters/validations.md +8 -67
- data/docs/testing.md +22 -13
- data/docs/tips_and_tricks.md +9 -0
- data/docs/workflows.md +14 -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 +61 -11
- data/lib/generators/cmdx/workflow_generator.rb +41 -66
- data/lib/locales/ar.yml +35 -0
- data/lib/locales/cs.yml +35 -0
- data/lib/locales/da.yml +35 -0
- data/lib/locales/de.yml +35 -0
- data/lib/locales/el.yml +35 -0
- data/lib/locales/en.yml +19 -20
- data/lib/locales/es.yml +19 -20
- data/lib/locales/fi.yml +35 -0
- data/lib/locales/fr.yml +35 -0
- data/lib/locales/he.yml +35 -0
- data/lib/locales/hi.yml +35 -0
- data/lib/locales/it.yml +35 -0
- data/lib/locales/ja.yml +35 -0
- data/lib/locales/ko.yml +35 -0
- data/lib/locales/nl.yml +35 -0
- data/lib/locales/no.yml +35 -0
- data/lib/locales/pl.yml +35 -0
- data/lib/locales/pt.yml +35 -0
- data/lib/locales/ru.yml +35 -0
- data/lib/locales/sv.yml +35 -0
- data/lib/locales/th.yml +35 -0
- data/lib/locales/tr.yml +35 -0
- data/lib/locales/vi.yml +35 -0
- data/lib/locales/zh.yml +35 -0
- metadata +57 -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/lib/cmdx/core_ext/module.rb
CHANGED
@@ -2,65 +2,39 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module CoreExt
|
5
|
-
# Extensions
|
6
|
-
#
|
7
|
-
# ModuleExtensions adds method delegation and attribute setting functionality
|
8
|
-
# used throughout the CMDx framework. These methods enable declarative
|
9
|
-
# programming patterns and automatic method generation.
|
10
|
-
#
|
11
|
-
# @example Method delegation
|
12
|
-
# class Task
|
13
|
-
# __cmdx_attr_delegator :name, :email, to: :user
|
14
|
-
# __cmdx_attr_delegator :save, to: :record, private: true
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example Attribute settings
|
18
|
-
# class Task
|
19
|
-
# __cmdx_attr_setting :default_options, default: -> { {} }
|
20
|
-
# __cmdx_attr_setting :configuration, default: {}
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# @see Task Tasks that use module extensions for delegation
|
24
|
-
# @see Parameter Parameters that use attribute settings
|
5
|
+
# Extensions for Ruby's Module class that provide attribute delegation and settings functionality.
|
6
|
+
# These extensions are automatically included in all modules when CMDx is loaded.
|
25
7
|
module ModuleExtensions
|
26
8
|
|
27
|
-
#
|
9
|
+
# Creates delegated methods that forward calls to another object or class.
|
10
|
+
# Supports method name prefixing, privacy levels, and optional method existence checking.
|
28
11
|
#
|
29
|
-
#
|
30
|
-
# another object. It supports method visibility controls and optional
|
31
|
-
# missing method handling.
|
32
|
-
#
|
33
|
-
# @param methods [Array<Symbol>] method names to delegate
|
12
|
+
# @param methods [Array<Symbol>] the method names to delegate
|
34
13
|
# @param options [Hash] delegation options
|
35
|
-
# @option options [Symbol] :to target object
|
36
|
-
# @option options [Boolean] :allow_missing whether to allow
|
37
|
-
# @option options [Boolean] :
|
38
|
-
# @option options [Boolean] :
|
39
|
-
# @
|
14
|
+
# @option options [Symbol] :to the target object or :class to delegate to
|
15
|
+
# @option options [Boolean] :allow_missing (false) whether to allow delegation to non-existent methods
|
16
|
+
# @option options [Boolean] :protected (false) whether to make the delegated method protected
|
17
|
+
# @option options [Boolean] :private (false) whether to make the delegated method private
|
18
|
+
# @option options [String, Symbol] :prefix optional prefix for the delegated method name
|
19
|
+
# @option options [String, Symbol] :suffix optional suffix for the delegated method name
|
40
20
|
#
|
41
|
-
# @
|
42
|
-
#
|
43
|
-
# __cmdx_attr_delegator :first_name, :last_name, to: :profile
|
44
|
-
# # Creates: def first_name; profile.first_name; end
|
45
|
-
# end
|
21
|
+
# @return [void]
|
22
|
+
# @raise [NoMethodError] when delegating to a non-existent method and :allow_missing is false
|
46
23
|
#
|
47
|
-
# @example
|
24
|
+
# @example Delegate methods to an instance variable
|
48
25
|
# class Task
|
49
|
-
#
|
50
|
-
#
|
26
|
+
# def initialize
|
27
|
+
# @logger = Logger.new
|
28
|
+
# end
|
51
29
|
#
|
52
|
-
#
|
53
|
-
# class Task
|
54
|
-
# __cmdx_attr_delegator :configuration, to: :class
|
30
|
+
# cmdx_attr_delegator :info, :warn, :error, to: :@logger
|
55
31
|
# end
|
56
32
|
#
|
57
|
-
# @example
|
58
|
-
# class
|
59
|
-
#
|
33
|
+
# @example Delegate with prefix and privacy
|
34
|
+
# class Workflow
|
35
|
+
# cmdx_attr_delegator :perform, to: :task, prefix: 'execute_', private: true
|
60
36
|
# end
|
61
|
-
|
62
|
-
# @raise [NoMethodError] if target object doesn't respond to method and allow_missing is false
|
63
|
-
def __cmdx_attr_delegator(*methods, **options)
|
37
|
+
def cmdx_attr_delegator(*methods, **options)
|
64
38
|
methods.each do |method|
|
65
39
|
method_name = Utils::NameAffix.call(method, options.fetch(:to), options)
|
66
40
|
|
@@ -83,53 +57,36 @@ module CMDx
|
|
83
57
|
end
|
84
58
|
end
|
85
59
|
|
86
|
-
#
|
60
|
+
# Creates a singleton method for accessing inheritable settings with caching and default values.
|
61
|
+
# Settings are inherited from superclass and can have default values via blocks or static values.
|
87
62
|
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
63
|
+
# @param method [Symbol] the name of the setting method to create
|
64
|
+
# @param options [Hash] setting options
|
65
|
+
# @option options [Object, Proc] :default the default value or a proc that returns the default value
|
91
66
|
#
|
92
|
-
# @param method [Symbol] name of the attribute method
|
93
|
-
# @param options [Hash] attribute options
|
94
|
-
# @option options [Object, Proc] :default default value or proc to generate value
|
95
67
|
# @return [void]
|
96
68
|
#
|
97
|
-
# @example
|
98
|
-
# class Task
|
99
|
-
# __cmdx_attr_setting :timeout, default: 30
|
100
|
-
# end
|
101
|
-
# # Task.timeout => 30
|
102
|
-
#
|
103
|
-
# @example Dynamic default with proc
|
104
|
-
# class Task
|
105
|
-
# __cmdx_attr_setting :timestamp, default: -> { Time.now }
|
106
|
-
# end
|
107
|
-
# # Task.timestamp => current time (evaluated lazily)
|
108
|
-
#
|
109
|
-
# @example Inherited settings
|
69
|
+
# @example Define a setting with a default value
|
110
70
|
# class BaseTask
|
111
|
-
#
|
71
|
+
# cmdx_attr_setting :timeout, default: 30
|
112
72
|
# end
|
113
73
|
#
|
114
|
-
#
|
115
|
-
# end
|
116
|
-
# # ProcessTask.options => {retry: 3} (inherited from BaseTask)
|
74
|
+
# BaseTask.timeout # => 30
|
117
75
|
#
|
118
|
-
# @example
|
76
|
+
# @example Define a setting with a dynamic default
|
119
77
|
# class Task
|
120
|
-
#
|
78
|
+
# cmdx_attr_setting :retry_count, default: -> { ENV['RETRY_COUNT']&.to_i || 3 }
|
121
79
|
# end
|
122
|
-
|
123
|
-
def __cmdx_attr_setting(method, **options)
|
80
|
+
def cmdx_attr_setting(method, **options)
|
124
81
|
define_singleton_method(method) do
|
125
82
|
@cmd_facets ||= {}
|
126
83
|
return @cmd_facets[method] if @cmd_facets.key?(method)
|
127
84
|
|
128
|
-
value = superclass.
|
85
|
+
value = superclass.cmdx_try(method)
|
129
86
|
return @cmd_facets[method] = value.dup unless value.nil?
|
130
87
|
|
131
88
|
default = options[:default]
|
132
|
-
value = default.
|
89
|
+
value = default.cmdx_call
|
133
90
|
@cmd_facets[method] = default.is_a?(Proc) ? value : value.dup
|
134
91
|
end
|
135
92
|
end
|
@@ -138,5 +95,4 @@ module CMDx
|
|
138
95
|
end
|
139
96
|
end
|
140
97
|
|
141
|
-
# Extend all modules with CMDx utility methods
|
142
98
|
Module.include(CMDx::CoreExt::ModuleExtensions)
|
data/lib/cmdx/core_ext/object.rb
CHANGED
@@ -2,151 +2,117 @@
|
|
2
2
|
|
3
3
|
module CMDx
|
4
4
|
module CoreExt
|
5
|
-
# Extensions
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# and value yielding capabilities to all Ruby objects. These methods
|
9
|
-
# are prefixed with `__cmdx_` to avoid conflicts with existing methods.
|
10
|
-
#
|
11
|
-
# @example Safe method calling
|
12
|
-
# object.__cmdx_try(:some_method) # Returns nil if method doesn't exist
|
13
|
-
# object.__cmdx_try(proc { expensive_calculation }) # Calls proc safely
|
14
|
-
#
|
15
|
-
# @example Conditional evaluation
|
16
|
-
# object.__cmdx_eval(if: :valid?) # True if object.valid? is true
|
17
|
-
# object.__cmdx_eval(unless: :empty?) # True unless object.empty? is true
|
18
|
-
# object.__cmdx_eval(if: :valid?, unless: :processed?) # Combined conditions
|
19
|
-
#
|
20
|
-
# @example Value yielding
|
21
|
-
# object.__cmdx_yield(:name) # Returns object.name if method exists, otherwise :name
|
22
|
-
# object.__cmdx_yield(-> { compute }) # Executes lambda and returns result
|
23
|
-
#
|
24
|
-
# @see Task Tasks that use these object extensions
|
25
|
-
# @see Parameter Parameters that leverage object extensions
|
5
|
+
# Extensions for Ruby's Object class that provide flexible method calling and evaluation utilities.
|
6
|
+
# These extensions are automatically included in all objects when CMDx is loaded, providing
|
7
|
+
# safe method invocation, conditional evaluation, and dynamic yielding capabilities.
|
26
8
|
module ObjectExtensions
|
27
9
|
|
28
|
-
|
29
|
-
alias __cmdx_respond_to? respond_to?
|
10
|
+
alias cmdx_respond_to? respond_to?
|
30
11
|
|
31
|
-
# Safely
|
12
|
+
# Safely tries to call a method, evaluate a proc, or access a hash key.
|
13
|
+
# Provides flexible invocation that handles different types of callables gracefully.
|
32
14
|
#
|
33
|
-
#
|
34
|
-
#
|
15
|
+
# @param key [Symbol, String, Proc, Object] the method name, proc, or hash key to try
|
16
|
+
# @param args [Array] arguments to pass to the method or proc
|
35
17
|
#
|
36
|
-
# @
|
37
|
-
# @param args [Array] arguments to pass to the method/proc (passed via splat)
|
38
|
-
# @return [Object, nil] result of method call, proc execution, or nil if not possible
|
18
|
+
# @return [Object, nil] the result of the method call, proc evaluation, or hash access; nil if not found
|
39
19
|
#
|
40
|
-
# @example
|
41
|
-
#
|
42
|
-
#
|
20
|
+
# @example Try calling a method
|
21
|
+
# "hello".cmdx_try(:upcase) # => "HELLO"
|
22
|
+
# "hello".cmdx_try(:missing) # => nil
|
43
23
|
#
|
44
|
-
# @example
|
45
|
-
#
|
46
|
-
# user.__cmdx_try(proc { |x| x * 2 }, 5) # => 10
|
24
|
+
# @example Try evaluating a proc
|
25
|
+
# obj.cmdx_try(-> { self.class.name }) # => "String"
|
47
26
|
#
|
48
|
-
# @example
|
49
|
-
#
|
50
|
-
|
51
|
-
def __cmdx_try(key, ...)
|
27
|
+
# @example Try accessing a hash key
|
28
|
+
# {name: "John"}.cmdx_try(:name) # => "John"
|
29
|
+
def cmdx_try(key, *args, **kwargs, &)
|
52
30
|
if key.is_a?(Proc)
|
53
31
|
return instance_eval(&key) unless is_a?(Module) || key.inspect.include?("(lambda)")
|
54
32
|
|
55
|
-
key.
|
33
|
+
if key.arity.positive? && args.empty?
|
34
|
+
key.call(self, *args, **kwargs, &)
|
35
|
+
else
|
36
|
+
key.call(*args, **kwargs, &)
|
37
|
+
end
|
56
38
|
elsif respond_to?(key, true)
|
57
|
-
send(key,
|
39
|
+
send(key, *args, **kwargs, &)
|
58
40
|
elsif is_a?(Hash)
|
59
|
-
|
41
|
+
cmdx_fetch(key)
|
60
42
|
end
|
61
43
|
end
|
62
44
|
|
63
|
-
#
|
45
|
+
# Evaluates conditional options using :if and :unless logic.
|
46
|
+
# Supports both method names and procs for conditional evaluation.
|
64
47
|
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
48
|
+
# @param options [Hash] evaluation options
|
49
|
+
# @option options [Symbol, Proc] :if condition that must be truthy
|
50
|
+
# @option options [Symbol, Proc] :unless condition that must be falsy
|
51
|
+
# @option options [Object] :default (true) default value when no conditions are specified
|
68
52
|
#
|
69
|
-
# @
|
70
|
-
# @option options [Symbol, Proc] :if condition that must be true
|
71
|
-
# @option options [Symbol, Proc] :unless condition that must be false
|
72
|
-
# @option options [Boolean] :default default value if no conditions (default: true)
|
73
|
-
# @return [Boolean] true if conditions are met
|
53
|
+
# @return [Boolean] true if conditions are met, false otherwise
|
74
54
|
#
|
75
|
-
# @example
|
76
|
-
# user.
|
77
|
-
# user.__cmdx_eval(unless: :guest?) # => true unless user.guest? is true
|
55
|
+
# @example Evaluate with if condition
|
56
|
+
# user.cmdx_eval(if: :active?) # => true if user.active? is truthy
|
78
57
|
#
|
79
|
-
# @example
|
80
|
-
# user.
|
58
|
+
# @example Evaluate with unless condition
|
59
|
+
# user.cmdx_eval(unless: :banned?) # => true if user.banned? is falsy
|
81
60
|
#
|
82
|
-
# @example
|
83
|
-
# user.
|
84
|
-
def
|
61
|
+
# @example Evaluate with both conditions
|
62
|
+
# user.cmdx_eval(if: :active?, unless: :banned?) # => true if active and not banned
|
63
|
+
def cmdx_eval(options = {})
|
85
64
|
if options[:if] && options[:unless]
|
86
|
-
|
65
|
+
cmdx_try(options[:if]) && !cmdx_try(options[:unless])
|
87
66
|
elsif options[:if]
|
88
|
-
|
67
|
+
cmdx_try(options[:if])
|
89
68
|
elsif options[:unless]
|
90
|
-
!
|
69
|
+
!cmdx_try(options[:unless])
|
91
70
|
else
|
92
71
|
options.fetch(:default, true)
|
93
72
|
end
|
94
73
|
end
|
95
74
|
|
96
|
-
#
|
75
|
+
# Yields or returns a value based on its type, with smart method calling.
|
76
|
+
# Handles symbols/strings as method names, procs/hashes via cmdx_try, and returns other values as-is.
|
97
77
|
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# Otherwise, try to execute it as a proc or return the value as-is.
|
78
|
+
# @param key [Symbol, String, Proc, Hash, Object] the value to yield or method to call
|
79
|
+
# @param args [Array] arguments to pass to method calls
|
101
80
|
#
|
102
|
-
# @
|
103
|
-
# @param args [Array] arguments to pass if calling method/proc (passed via splat)
|
104
|
-
# @return [Object] yielded value
|
81
|
+
# @return [Object] the result of method call, proc evaluation, or the value itself
|
105
82
|
#
|
106
|
-
# @example
|
107
|
-
#
|
108
|
-
# user.__cmdx_yield("email") # => calls user.email if method exists, otherwise returns "email"
|
83
|
+
# @example Yield a method call
|
84
|
+
# "hello".cmdx_yield(:upcase) # => "HELLO"
|
109
85
|
#
|
110
|
-
# @example
|
111
|
-
#
|
112
|
-
# hash.__cmdx_yield({key: "value"}) # => tries hash access
|
86
|
+
# @example Yield a static value
|
87
|
+
# obj.cmdx_yield("static") # => "static"
|
113
88
|
#
|
114
|
-
# @example
|
115
|
-
#
|
116
|
-
|
117
|
-
def __cmdx_yield(key, ...)
|
89
|
+
# @example Yield a proc
|
90
|
+
# obj.cmdx_yield(-> { Time.now }) # => 2023-01-01 12:00:00 UTC
|
91
|
+
def cmdx_yield(key, ...)
|
118
92
|
if key.is_a?(Symbol) || key.is_a?(String)
|
119
93
|
return key unless respond_to?(key, true)
|
120
94
|
|
121
95
|
send(key, ...)
|
122
96
|
elsif is_a?(Hash) || key.is_a?(Proc)
|
123
|
-
|
97
|
+
cmdx_try(key, ...)
|
124
98
|
else
|
125
99
|
key
|
126
100
|
end
|
127
101
|
end
|
128
102
|
|
129
|
-
#
|
130
|
-
#
|
131
|
-
# This method provides safe callable execution - if the object
|
132
|
-
# can be called (like a proc or lambda), call it with the given
|
133
|
-
# arguments. Otherwise, return the object unchanged.
|
103
|
+
# Invokes the object if it responds to :call, otherwise returns the object itself.
|
104
|
+
# Useful for handling both callable and non-callable objects uniformly.
|
134
105
|
#
|
135
|
-
# @param args [Array] arguments to pass to call method
|
136
|
-
# @return [Object] result of calling or the object itself
|
106
|
+
# @param args [Array] arguments to pass to the call method
|
137
107
|
#
|
138
|
-
# @
|
139
|
-
# proc = -> { "Hello" }
|
140
|
-
# proc.__cmdx_call # => "Hello"
|
108
|
+
# @return [Object] the result of calling the object, or the object itself if not callable
|
141
109
|
#
|
142
|
-
# @example
|
143
|
-
#
|
144
|
-
# string.__cmdx_call # => "Hello"
|
110
|
+
# @example Invoke a proc
|
111
|
+
# proc { "hello" }.cmdx_call # => "hello"
|
145
112
|
#
|
146
|
-
# @example
|
147
|
-
#
|
148
|
-
|
149
|
-
def __cmdx_call(...)
|
113
|
+
# @example Invoke a non-callable object
|
114
|
+
# "hello".cmdx_call # => "hello"
|
115
|
+
def cmdx_call(...)
|
150
116
|
return self unless respond_to?(:call)
|
151
117
|
|
152
118
|
call(...)
|
@@ -156,5 +122,4 @@ module CMDx
|
|
156
122
|
end
|
157
123
|
end
|
158
124
|
|
159
|
-
# Extend all objects with CMDx utility methods
|
160
125
|
Object.include(CMDx::CoreExt::ObjectExtensions)
|
data/lib/cmdx/correlator.rb
CHANGED
@@ -1,205 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
|
5
|
-
# Thread-safe correlation ID management for tracking related operations across request boundaries.
|
4
|
+
# Thread-local correlation ID management for tracing and tracking execution context.
|
6
5
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# Correlation IDs are automatically used by CMDx runs when available, providing seamless
|
12
|
-
# request tracking without requiring explicit parameter passing between tasks.
|
13
|
-
#
|
14
|
-
# ## Thread Safety
|
15
|
-
#
|
16
|
-
# All correlation operations are thread-local, ensuring that different threads maintain
|
17
|
-
# separate correlation contexts without interference. This is essential for concurrent
|
18
|
-
# request processing in multi-threaded applications.
|
19
|
-
#
|
20
|
-
# ## Integration with CMDx Runs
|
21
|
-
#
|
22
|
-
# When a new CMDx::Chain is created, it automatically uses the current thread's correlation
|
23
|
-
# ID as its chain identifier if available, falling back to UUID generation if none exists.
|
24
|
-
#
|
25
|
-
# @example Basic correlation usage
|
26
|
-
# CMDx::Correlator.id = "req-12345"
|
27
|
-
# CMDx::Correlator.id # => "req-12345"
|
28
|
-
#
|
29
|
-
# # Chain automatically inherits correlation ID
|
30
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
31
|
-
# result.chain.id # => "req-12345"
|
32
|
-
#
|
33
|
-
# @example Block-based correlation context
|
34
|
-
# CMDx::Correlator.use("workflow-operation-456") do
|
35
|
-
# # All tasks within this block share the same correlation
|
36
|
-
# ProcessOrderTask.call(order_id: 123)
|
37
|
-
# SendEmailTask.call(user_id: 456)
|
38
|
-
# end
|
39
|
-
# # Correlation context is automatically restored
|
40
|
-
#
|
41
|
-
# @example Nested correlation contexts
|
42
|
-
# CMDx::Correlator.use("parent-operation") do
|
43
|
-
# CMDx::Correlator.use("child-operation") do
|
44
|
-
# ProcessOrderTask.call(order_id: 123)
|
45
|
-
# # Uses "child-operation" as correlation ID
|
46
|
-
# end
|
47
|
-
# # Restored to "parent-operation"
|
48
|
-
# SendEmailTask.call(user_id: 456)
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# @example Manual correlation management
|
52
|
-
# # Set correlation ID for current thread
|
53
|
-
# CMDx::Correlator.id = "manual-correlation"
|
54
|
-
#
|
55
|
-
# # Check current correlation
|
56
|
-
# current_id = CMDx::Correlator.id
|
57
|
-
#
|
58
|
-
# # Clear correlation when done
|
59
|
-
# CMDx::Correlator.clear
|
60
|
-
#
|
61
|
-
# @example Middleware integration pattern
|
62
|
-
# class CorrelationMiddleware
|
63
|
-
# def call(env)
|
64
|
-
# correlation_id = env['HTTP_X_CORRELATION_ID'] || CMDx::Correlator.generate
|
65
|
-
#
|
66
|
-
# CMDx::Correlator.use(correlation_id) do
|
67
|
-
# @app.call(env)
|
68
|
-
# end
|
69
|
-
# end
|
70
|
-
# end
|
71
|
-
#
|
72
|
-
# @see CMDx::Chain Chain execution context that inherits correlation IDs
|
73
|
-
# @since 1.0.0
|
6
|
+
# This module provides utilities for managing correlation IDs within thread-local storage,
|
7
|
+
# enabling request tracing and execution context tracking across task chains and workflows.
|
8
|
+
# Each thread maintains its own correlation ID that can be used to correlate related operations.
|
74
9
|
module Correlator
|
75
10
|
|
76
|
-
##
|
77
|
-
# Thread-local storage key for correlation identifiers.
|
78
|
-
#
|
79
|
-
# Uses a Symbol key to avoid potential conflicts with other thread-local
|
80
|
-
# variables and to ensure consistent access across the correlator methods.
|
81
|
-
#
|
82
|
-
# @return [Symbol] the thread-local storage key
|
83
11
|
THREAD_KEY = :cmdx_correlation_id
|
84
12
|
|
85
13
|
module_function
|
86
14
|
|
87
|
-
|
88
|
-
#
|
89
|
-
#
|
90
|
-
# Creates a RFC 4122 compliant UUID using SecureRandom, providing a globally
|
91
|
-
# unique identifier suitable for distributed request tracing.
|
15
|
+
# Generates a new unique correlation ID using SecureRandom.
|
16
|
+
# Prefers UUID v7 when available, falls back to UUID v4.
|
92
17
|
#
|
93
|
-
# @return [String] a new UUID string
|
18
|
+
# @return [String] a new UUID string for use as correlation ID
|
94
19
|
#
|
95
|
-
# @example
|
96
|
-
#
|
97
|
-
# id2 = CMDx::Correlator.generate # => "018c2b95-b765-7123-b456-dd7c920fe3a8"
|
98
|
-
# id1 != id2 # => true
|
20
|
+
# @example Generate a new correlation ID
|
21
|
+
# CMDx::Correlator.generate #=> "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
99
22
|
def generate
|
23
|
+
return SecureRandom.uuid_v7 if SecureRandom.respond_to?(:uuid_v7)
|
24
|
+
|
100
25
|
SecureRandom.uuid
|
101
26
|
end
|
102
27
|
|
103
|
-
|
104
|
-
# Retrieves the current thread's correlation identifier.
|
105
|
-
#
|
106
|
-
# Returns the correlation ID that was previously set for the current thread,
|
107
|
-
# or nil if no correlation ID has been established. This method is thread-safe
|
108
|
-
# and will not interfere with correlation IDs set in other threads.
|
28
|
+
# Retrieves the current thread's correlation ID.
|
109
29
|
#
|
110
|
-
# @return [String, nil] the current correlation ID or nil if
|
30
|
+
# @return [String, nil] the current correlation ID or nil if not set
|
111
31
|
#
|
112
|
-
# @example
|
113
|
-
# CMDx::Correlator.id
|
32
|
+
# @example Get current correlation ID
|
33
|
+
# CMDx::Correlator.id #=> "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
114
34
|
#
|
115
|
-
#
|
116
|
-
# CMDx::Correlator.id
|
35
|
+
# @example When no correlation ID is set
|
36
|
+
# CMDx::Correlator.id #=> nil
|
117
37
|
def id
|
118
38
|
Thread.current[THREAD_KEY]
|
119
39
|
end
|
120
40
|
|
121
|
-
|
122
|
-
# Sets the correlation identifier for the current thread.
|
123
|
-
#
|
124
|
-
# Assigns a correlation ID to the current thread's local storage, making it
|
125
|
-
# available for subsequent operations within the same thread context. The
|
126
|
-
# correlation ID will persist until explicitly changed or cleared.
|
41
|
+
# Sets the current thread's correlation ID.
|
127
42
|
#
|
128
|
-
# @param value [String,
|
129
|
-
# @return [String] the assigned correlation identifier
|
43
|
+
# @param value [String, Symbol] the correlation ID to set
|
130
44
|
#
|
131
|
-
# @
|
132
|
-
# CMDx::Correlator.id = "user-request-123"
|
133
|
-
# CMDx::Correlator.id # => "user-request-123"
|
45
|
+
# @return [String, Symbol] the value that was set
|
134
46
|
#
|
135
|
-
# @example
|
136
|
-
# CMDx::Correlator.id =
|
137
|
-
# CMDx::Correlator.id
|
47
|
+
# @example Set correlation ID
|
48
|
+
# CMDx::Correlator.id = "custom-trace-123"
|
49
|
+
# CMDx::Correlator.id #=> "custom-trace-123"
|
138
50
|
def id=(value)
|
139
51
|
Thread.current[THREAD_KEY] = value
|
140
52
|
end
|
141
53
|
|
142
|
-
|
143
|
-
# Clears the correlation identifier for the current thread.
|
144
|
-
#
|
145
|
-
# Removes the correlation ID from the current thread's local storage,
|
146
|
-
# effectively resetting the correlation context. Subsequent calls to
|
147
|
-
# {#id} will return nil until a new correlation ID is set.
|
54
|
+
# Clears the current thread's correlation ID.
|
148
55
|
#
|
149
56
|
# @return [nil] always returns nil
|
150
57
|
#
|
151
|
-
# @example
|
152
|
-
# CMDx::Correlator.id = "temporary-correlation"
|
153
|
-
# CMDx::Correlator.id # => "temporary-correlation"
|
154
|
-
#
|
58
|
+
# @example Clear correlation ID
|
155
59
|
# CMDx::Correlator.clear
|
156
|
-
# CMDx::Correlator.id
|
60
|
+
# CMDx::Correlator.id #=> nil
|
157
61
|
def clear
|
158
62
|
Thread.current[THREAD_KEY] = nil
|
159
63
|
end
|
160
64
|
|
161
|
-
|
162
|
-
#
|
65
|
+
# Temporarily uses a correlation ID for the duration of a block.
|
66
|
+
# Restores the previous correlation ID after the block completes, even if an exception occurs.
|
163
67
|
#
|
164
|
-
#
|
165
|
-
# restoring the previous correlation ID when the block completes. This
|
166
|
-
# method is exception-safe and will restore the original context even
|
167
|
-
# if the block raises an error.
|
68
|
+
# @param value [String, Symbol] the correlation ID to use during block execution
|
168
69
|
#
|
169
|
-
#
|
170
|
-
# ensures proper cleanup and supports nested correlation scopes.
|
70
|
+
# @return [Object] the result of the block execution
|
171
71
|
#
|
172
|
-
# @
|
173
|
-
# @yieldreturn [Object] the return value of the block
|
174
|
-
# @return [Object] the return value of the executed block
|
72
|
+
# @raise [TypeError] if value is not a String or Symbol
|
175
73
|
#
|
176
|
-
# @example
|
177
|
-
#
|
178
|
-
#
|
74
|
+
# @example Use temporary correlation ID
|
75
|
+
# CMDx::Correlator.use("temp-123") do
|
76
|
+
# puts CMDx::Correlator.id # => "temp-123"
|
77
|
+
# # ... perform work ...
|
179
78
|
# end
|
180
|
-
# #
|
181
|
-
#
|
182
|
-
# @example Nested contexts
|
183
|
-
# CMDx::Correlator.use("parent") do
|
184
|
-
# CMDx::Correlator.id # => "parent"
|
185
|
-
#
|
186
|
-
# CMDx::Correlator.use("child") do
|
187
|
-
# CMDx::Correlator.id # => "child"
|
188
|
-
# end
|
79
|
+
# # Previous correlation ID is restored
|
189
80
|
#
|
190
|
-
#
|
191
|
-
# end
|
192
|
-
#
|
193
|
-
# @example Exception safety
|
81
|
+
# @example With exception handling
|
194
82
|
# CMDx::Correlator.id = "original"
|
195
|
-
#
|
196
|
-
#
|
197
|
-
# CMDx::Correlator.use("temporary") do
|
198
|
-
# raise StandardError, "something went wrong"
|
199
|
-
# end
|
200
|
-
# rescue StandardError
|
201
|
-
# CMDx::Correlator.id # => "original" (still restored)
|
83
|
+
# CMDx::Correlator.use("temp") do
|
84
|
+
# raise StandardError, "oops"
|
202
85
|
# end
|
86
|
+
# # CMDx::Correlator.id is still "original"
|
203
87
|
def use(value)
|
204
88
|
unless value.is_a?(String) || value.is_a?(Symbol)
|
205
89
|
raise TypeError,
|