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/chain.rb
CHANGED
@@ -1,79 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Manages execution chains for task results with thread-local storage support.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# @example Basic usage with automatic chain creation
|
12
|
-
# # Chain is automatically created when first task runs
|
13
|
-
# result1 = MyTask.call(data: "first")
|
14
|
-
# result2 = MyTask.call(data: "second")
|
15
|
-
#
|
16
|
-
# result1.chain.id == result2.chain.id #=> true
|
17
|
-
# result1.index #=> 0
|
18
|
-
# result2.index #=> 1
|
19
|
-
#
|
20
|
-
# @example Using custom chain ID
|
21
|
-
# chain = CMDx::Chain.new(id: "custom-correlation-123")
|
22
|
-
# CMDx::Chain.current = chain
|
23
|
-
#
|
24
|
-
# result = MyTask.call(data: "test")
|
25
|
-
# result.chain.id #=> "custom-correlation-123"
|
26
|
-
#
|
27
|
-
# @example Thread isolation
|
28
|
-
# # Each thread gets its own chain
|
29
|
-
# Thread.new do
|
30
|
-
# result = MyTask.call(data: "thread1")
|
31
|
-
# result.chain.id #=> unique ID for this thread
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# Thread.new do
|
35
|
-
# result = MyTask.call(data: "thread2")
|
36
|
-
# result.chain.id #=> different unique ID
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# @example Temporary chain context
|
40
|
-
# CMDx::Chain.use(id: "temp-correlation") do
|
41
|
-
# result = MyTask.call(data: "test")
|
42
|
-
# result.chain.id #=> "temp-correlation"
|
43
|
-
# end
|
44
|
-
# # Original chain is restored after block
|
45
|
-
#
|
46
|
-
# @see CMDx::Correlator
|
47
|
-
# @since 1.0.0
|
6
|
+
# Chain provides a mechanism to track and correlate multiple task executions
|
7
|
+
# within a single logical operation. It maintains a collection of results
|
8
|
+
# and provides thread-local storage for tracking the current execution chain.
|
9
|
+
# The chain automatically delegates common methods to its results collection
|
10
|
+
# and the first result for convenient access to execution state.
|
48
11
|
class Chain
|
49
12
|
|
50
|
-
# Thread-local storage key for the current chain
|
51
13
|
THREAD_KEY = :cmdx_correlation_chain
|
52
14
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
15
|
+
cmdx_attr_delegator :index, :first, :last, :size,
|
16
|
+
to: :results
|
17
|
+
cmdx_attr_delegator :state, :status, :outcome, :runtime,
|
18
|
+
to: :first
|
57
19
|
|
58
|
-
#
|
59
|
-
|
60
|
-
# @!attribute [r] results
|
61
|
-
# @return [Array<CMDx::Result>] the collection of task results in this chain
|
62
|
-
attr_reader :id, :results
|
20
|
+
# @return [String] the unique identifier for this chain
|
21
|
+
attr_reader :id
|
63
22
|
|
64
|
-
#
|
23
|
+
# @return [Array<CMDx::Result>] the collection of task results in this chain
|
24
|
+
attr_reader :results
|
25
|
+
|
26
|
+
# Creates a new execution chain with optional attributes.
|
27
|
+
#
|
28
|
+
# @param attributes [Hash] optional attributes for chain initialization
|
29
|
+
# @option attributes [String] :id custom chain identifier, defaults to current correlation ID or generates new one
|
65
30
|
#
|
66
|
-
# @
|
67
|
-
# @option attributes [String] :id custom identifier for the chain.
|
68
|
-
# If not provided, uses the current correlator ID or generates a new UUID.
|
31
|
+
# @return [Chain] the newly created chain instance
|
69
32
|
#
|
70
|
-
# @example Create chain with default ID
|
33
|
+
# @example Create a chain with default ID
|
71
34
|
# chain = CMDx::Chain.new
|
72
|
-
# chain.id
|
35
|
+
# chain.id #=> "generated-uuid"
|
73
36
|
#
|
74
|
-
# @example Create chain with custom ID
|
75
|
-
# chain = CMDx::Chain.new(id: "
|
76
|
-
# chain.id
|
37
|
+
# @example Create a chain with custom ID
|
38
|
+
# chain = CMDx::Chain.new(id: "custom-123")
|
39
|
+
# chain.id #=> "custom-123"
|
77
40
|
def initialize(attributes = {})
|
78
41
|
@id = attributes[:id] || CMDx::Correlator.id || CMDx::Correlator.generate
|
79
42
|
@results = []
|
@@ -81,53 +44,55 @@ module CMDx
|
|
81
44
|
|
82
45
|
class << self
|
83
46
|
|
84
|
-
#
|
47
|
+
# Gets the current execution chain from thread-local storage.
|
85
48
|
#
|
86
|
-
# @return [
|
49
|
+
# @return [Chain, nil] the current chain or nil if none is set
|
87
50
|
#
|
88
|
-
# @example
|
89
|
-
# CMDx::Chain.current
|
90
|
-
#
|
91
|
-
# MyTask.call(data: "test")
|
92
|
-
# CMDx::Chain.current #=> #<CMDx::Chain:0x... @id="018c2b95...">
|
51
|
+
# @example Access current chain
|
52
|
+
# chain = CMDx::Chain.current
|
53
|
+
# chain.id if chain #=> "current-chain-id"
|
93
54
|
def current
|
94
55
|
Thread.current[THREAD_KEY]
|
95
56
|
end
|
96
57
|
|
97
|
-
# Sets the current thread-local
|
58
|
+
# Sets the current execution chain in thread-local storage.
|
59
|
+
#
|
60
|
+
# @param chain [Chain, nil] the chain to set as current
|
98
61
|
#
|
99
|
-
# @
|
100
|
-
# @return [CMDx::Chain, nil] the chain that was set
|
62
|
+
# @return [Chain, nil] the chain that was set
|
101
63
|
#
|
102
|
-
# @example
|
103
|
-
#
|
104
|
-
# CMDx::Chain.current =
|
105
|
-
# CMDx::Chain.current.id
|
64
|
+
# @example Set current chain
|
65
|
+
# new_chain = CMDx::Chain.new
|
66
|
+
# CMDx::Chain.current = new_chain
|
67
|
+
# CMDx::Chain.current.id #=> new_chain.id
|
106
68
|
def current=(chain)
|
107
69
|
Thread.current[THREAD_KEY] = chain
|
108
70
|
end
|
109
71
|
|
110
|
-
# Clears the current thread-local
|
72
|
+
# Clears the current execution chain from thread-local storage.
|
111
73
|
#
|
112
|
-
# @return [nil]
|
74
|
+
# @return [nil] always returns nil
|
113
75
|
#
|
114
|
-
# @example
|
115
|
-
# CMDx::Chain.current #=> #<CMDx::Chain:0x...>
|
76
|
+
# @example Clear current chain
|
116
77
|
# CMDx::Chain.clear
|
117
|
-
# CMDx::Chain.current
|
78
|
+
# CMDx::Chain.current #=> nil
|
118
79
|
def clear
|
119
80
|
Thread.current[THREAD_KEY] = nil
|
120
81
|
end
|
121
82
|
|
122
|
-
#
|
83
|
+
# Builds or extends the current execution chain with a new result.
|
123
84
|
#
|
124
|
-
#
|
125
|
-
# and should not be used directly in application code.
|
85
|
+
# @param result [CMDx::Result] the result to add to the chain
|
126
86
|
#
|
127
|
-
# @
|
128
|
-
# @return [CMDx::Chain] the chain containing the result
|
87
|
+
# @return [Chain] the current chain with the result added
|
129
88
|
#
|
130
|
-
# @
|
89
|
+
# @raise [TypeError] if result is not a Result instance
|
90
|
+
#
|
91
|
+
# @example Build chain with result
|
92
|
+
# task = MyTask.new
|
93
|
+
# result = CMDx::Result.new(task)
|
94
|
+
# chain = CMDx::Chain.build(result)
|
95
|
+
# chain.results.size #=> 1
|
131
96
|
def build(result)
|
132
97
|
raise TypeError, "must be a Result" unless result.is_a?(Result)
|
133
98
|
|
@@ -138,50 +103,28 @@ module CMDx
|
|
138
103
|
|
139
104
|
end
|
140
105
|
|
141
|
-
# Converts the chain to a hash representation.
|
142
|
-
#
|
143
|
-
# Serializes the chain and all its results into a structured hash
|
144
|
-
# suitable for logging, debugging, and data interchange.
|
106
|
+
# Converts the chain to a hash representation using the serializer.
|
145
107
|
#
|
146
|
-
# @return [Hash]
|
108
|
+
# @return [Hash] serialized hash representation of the chain
|
147
109
|
#
|
148
|
-
# @example
|
149
|
-
# chain.to_h
|
150
|
-
# # => {
|
151
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
152
|
-
# # state: "complete",
|
153
|
-
# # status: "success",
|
154
|
-
# # outcome: "success",
|
155
|
-
# # runtime: 0.5,
|
156
|
-
# # results: [
|
157
|
-
# # { class: "ProcessOrderTask", state: "complete", status: "success", ... },
|
158
|
-
# # { class: "SendEmailTask", state: "complete", status: "success", ... }
|
159
|
-
# # ]
|
160
|
-
# # }
|
110
|
+
# @example Convert to hash
|
111
|
+
# chain.to_h #=> { id: "abc123", results: [...], state: "complete" }
|
161
112
|
def to_h
|
162
113
|
ChainSerializer.call(self)
|
163
114
|
end
|
164
115
|
alias to_a to_h
|
165
116
|
|
166
|
-
# Converts the chain to a string representation
|
167
|
-
#
|
168
|
-
# Creates a comprehensive, human-readable summary of the chain including
|
169
|
-
# all task results with formatted headers and footers.
|
117
|
+
# Converts the chain to a formatted string representation.
|
170
118
|
#
|
171
|
-
# @return [String]
|
119
|
+
# @return [String] formatted string representation of the chain
|
172
120
|
#
|
173
|
-
# @example
|
174
|
-
# chain.to_s
|
175
|
-
# #
|
176
|
-
# #
|
177
|
-
# #
|
178
|
-
# #
|
179
|
-
# #
|
180
|
-
# # SendEmailTask: index=1 state=complete status=success ...
|
181
|
-
# #
|
182
|
-
# # ================================================
|
183
|
-
# # state: complete | status: success | outcome: success | runtime: 0.5
|
184
|
-
# # "
|
121
|
+
# @example Convert to string
|
122
|
+
# puts chain.to_s
|
123
|
+
# # chain: abc123
|
124
|
+
# # ===================
|
125
|
+
# # {...}
|
126
|
+
# # ===================
|
127
|
+
# # state: complete | status: success | outcome: success | runtime: 0.001
|
185
128
|
def to_s
|
186
129
|
ChainInspector.call(self)
|
187
130
|
end
|
data/lib/cmdx/chain_inspector.rb
CHANGED
@@ -1,137 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
4
|
+
# Provides formatted inspection and display functionality for execution chains.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# with visual separators for easy debugging and monitoring.
|
10
|
-
#
|
11
|
-
# @example Basic chain inspection
|
12
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
13
|
-
# chain = result.chain
|
14
|
-
#
|
15
|
-
# ChainInspector.call(chain)
|
16
|
-
# # => "
|
17
|
-
# # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
|
18
|
-
# # ================================================
|
19
|
-
# #
|
20
|
-
# # {
|
21
|
-
# # class: "ProcessOrderTask",
|
22
|
-
# # type: "Task",
|
23
|
-
# # index: 0,
|
24
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
25
|
-
# # tags: [],
|
26
|
-
# # state: "complete",
|
27
|
-
# # status: "success",
|
28
|
-
# # outcome: "success",
|
29
|
-
# # metadata: {},
|
30
|
-
# # runtime: 0.5
|
31
|
-
# # }
|
32
|
-
# #
|
33
|
-
# # ================================================
|
34
|
-
# # state: complete | status: success | outcome: success | runtime: 0.5
|
35
|
-
# # "
|
36
|
-
#
|
37
|
-
# @example Chain with multiple tasks
|
38
|
-
# class ComplexTask < CMDx::Task
|
39
|
-
# def call
|
40
|
-
# SubTask1.call(context)
|
41
|
-
# SubTask2.call(context)
|
42
|
-
# end
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# result = ComplexTask.call
|
46
|
-
# ChainInspector.call(result.chain)
|
47
|
-
# # => Shows formatted output with all three task results and summary
|
48
|
-
#
|
49
|
-
# @example Failed chain inspection
|
50
|
-
# # When a chain contains failed tasks, the summary reflects the failure state
|
51
|
-
# ChainInspector.call(failed_chain)
|
52
|
-
# # => Shows all task results with failure information and failed summary
|
53
|
-
#
|
54
|
-
# @see CMDx::Chain Chain execution context and result tracking
|
55
|
-
# @see CMDx::Result Individual result inspection via to_h
|
6
|
+
# This module formats chain execution information into a human-readable string
|
7
|
+
# representation, including the chain ID, individual task results, and summary
|
8
|
+
# information about the chain's final state.
|
56
9
|
module ChainInspector
|
57
10
|
|
58
|
-
# Keys to display in the chain summary footer.
|
59
|
-
#
|
60
|
-
# These keys represent the most important chain-level information
|
61
|
-
# that should be displayed in the summary footer for quick reference.
|
62
11
|
FOOTER_KEYS = %i[
|
63
12
|
state status outcome runtime
|
64
13
|
].freeze
|
65
14
|
|
66
15
|
module_function
|
67
16
|
|
68
|
-
#
|
69
|
-
#
|
70
|
-
# Creates a formatted summary that includes:
|
71
|
-
# - Chain header with unique ID
|
72
|
-
# - Visual separator line
|
73
|
-
# - Pretty-printed hash representation of each result
|
74
|
-
# - Visual separator line
|
75
|
-
# - Summary footer with key chain statistics
|
17
|
+
# Formats a chain into a human-readable inspection string.
|
76
18
|
#
|
77
|
-
#
|
78
|
-
#
|
19
|
+
# Creates a formatted display showing the chain ID, individual task results,
|
20
|
+
# and summary footer with execution state information. The output includes
|
21
|
+
# visual separators and structured formatting for easy reading.
|
79
22
|
#
|
80
|
-
# @
|
81
|
-
# chain = SimpleTask.call.chain
|
82
|
-
# ChainInspector.call(chain)
|
83
|
-
# # => "
|
84
|
-
# # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
|
85
|
-
# # ================================================
|
86
|
-
# #
|
87
|
-
# # {
|
88
|
-
# # class: "SimpleTask",
|
89
|
-
# # type: "Task",
|
90
|
-
# # index: 0,
|
91
|
-
# # state: "complete",
|
92
|
-
# # status: "success",
|
93
|
-
# # outcome: "success",
|
94
|
-
# # runtime: 0.1
|
95
|
-
# # }
|
96
|
-
# #
|
97
|
-
# # ================================================
|
98
|
-
# # state: complete | status: success | outcome: success | runtime: 0.1
|
99
|
-
# # "
|
23
|
+
# @param chain [CMDx::Chain] the chain object to inspect
|
100
24
|
#
|
101
|
-
# @
|
102
|
-
# class ParentTask < CMDx::Task
|
103
|
-
# def call
|
104
|
-
# ChildTask1.call(context)
|
105
|
-
# ChildTask2.call(context)
|
106
|
-
# end
|
107
|
-
# end
|
25
|
+
# @return [String] formatted multi-line string representation of the chain
|
108
26
|
#
|
109
|
-
#
|
110
|
-
# ChainInspector.call(chain)
|
111
|
-
# # => "
|
112
|
-
# # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
|
113
|
-
# # ================================================
|
114
|
-
# #
|
115
|
-
# # { class: "ParentTask", index: 0, state: "complete", status: "success", ... }
|
116
|
-
# # { class: "ChildTask1", index: 1, state: "complete", status: "success", ... }
|
117
|
-
# # { class: "ChildTask2", index: 2, state: "complete", status: "success", ... }
|
118
|
-
# #
|
119
|
-
# # ================================================
|
120
|
-
# # state: complete | status: success | outcome: success | runtime: 0.5
|
121
|
-
# # "
|
27
|
+
# @raise [NoMethodError] if chain doesn't respond to required methods (id, results, state, status, outcome, runtime)
|
122
28
|
#
|
123
|
-
# @example
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
# #
|
29
|
+
# @example Inspect a simple chain
|
30
|
+
# chain = CMDx::Chain.new(id: "abc123")
|
31
|
+
# result = CMDx::Result.new(task)
|
32
|
+
# chain.results << result
|
33
|
+
# puts CMDx::ChainInspector.call(chain)
|
34
|
+
# # Output:
|
35
|
+
# # chain: abc123
|
36
|
+
# # ===================
|
129
37
|
# #
|
130
|
-
# #
|
38
|
+
# # {:state=>"complete", :status=>"success", ...}
|
131
39
|
# #
|
132
|
-
# #
|
133
|
-
# #
|
134
|
-
# # "
|
40
|
+
# # ===================
|
41
|
+
# # state: complete | status: success | outcome: success | runtime: 0.001
|
135
42
|
def call(chain)
|
136
43
|
header = "\nchain: #{chain.id}"
|
137
44
|
footer = FOOTER_KEYS.map { |key| "#{key}: #{chain.send(key)}" }.join(" | ")
|
@@ -1,164 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CMDx
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# into structured hash representations suitable for inspection, logging,
|
8
|
-
# debugging, and data interchange. It creates comprehensive data structures
|
9
|
-
# that include chain metadata and all associated task results.
|
10
|
-
#
|
11
|
-
# @example Basic chain serialization
|
12
|
-
# result = ProcessOrderTask.call(order_id: 123)
|
13
|
-
# chain = result.chain
|
14
|
-
#
|
15
|
-
# ChainSerializer.call(chain)
|
16
|
-
# # => {
|
17
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
18
|
-
# # state: "complete",
|
19
|
-
# # status: "success",
|
20
|
-
# # outcome: "success",
|
21
|
-
# # runtime: 0.5,
|
22
|
-
# # results: [
|
23
|
-
# # {
|
24
|
-
# # class: "ProcessOrderTask",
|
25
|
-
# # type: "Task",
|
26
|
-
# # index: 0,
|
27
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
28
|
-
# # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
29
|
-
# # tags: [],
|
30
|
-
# # state: "complete",
|
31
|
-
# # status: "success",
|
32
|
-
# # outcome: "success",
|
33
|
-
# # metadata: {},
|
34
|
-
# # runtime: 0.5
|
35
|
-
# # }
|
36
|
-
# # ]
|
37
|
-
# # }
|
38
|
-
#
|
39
|
-
# @example Chain with multiple tasks
|
40
|
-
# class ComplexTask < CMDx::Task
|
41
|
-
# def call
|
42
|
-
# SubTask1.call(context)
|
43
|
-
# SubTask2.call(context)
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# result = ComplexTask.call
|
48
|
-
# chain = result.chain
|
49
|
-
#
|
50
|
-
# ChainSerializer.call(chain)
|
51
|
-
# # => {
|
52
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
53
|
-
# # state: "complete",
|
54
|
-
# # status: "success",
|
55
|
-
# # outcome: "success",
|
56
|
-
# # runtime: 1.2,
|
57
|
-
# # results: [
|
58
|
-
# # { class: "ComplexTask", index: 0, state: "complete", status: "success", ... },
|
59
|
-
# # { class: "SubTask1", index: 1, state: "complete", status: "success", ... },
|
60
|
-
# # { class: "SubTask2", index: 2, state: "complete", status: "success", ... }
|
61
|
-
# # ]
|
62
|
-
# # }
|
63
|
-
#
|
64
|
-
# @example Failed chain serialization
|
65
|
-
# failed_result = FailingTask.call
|
66
|
-
# failed_chain = failed_result.chain
|
67
|
-
#
|
68
|
-
# ChainSerializer.call(failed_chain)
|
69
|
-
# # => {
|
70
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
71
|
-
# # state: "interrupted",
|
72
|
-
# # status: "failed",
|
73
|
-
# # outcome: "failed",
|
74
|
-
# # runtime: 0.1,
|
75
|
-
# # results: [
|
76
|
-
# # {
|
77
|
-
# # class: "FailingTask",
|
78
|
-
# # state: "interrupted",
|
79
|
-
# # status: "failed",
|
80
|
-
# # outcome: "failed",
|
81
|
-
# # metadata: { reason: "Something went wrong" },
|
82
|
-
# # runtime: 0.1,
|
83
|
-
# # ...
|
84
|
-
# # }
|
85
|
-
# # ]
|
86
|
-
# # }
|
87
|
-
#
|
88
|
-
# @see CMDx::Chain Chain execution context and result tracking
|
89
|
-
# @see CMDx::ResultSerializer Individual result serialization
|
90
|
-
# @see CMDx::ChainInspector Human-readable chain formatting
|
4
|
+
# Serializes Chain objects into hash representations for external consumption.
|
5
|
+
# Provides a consistent interface for converting chain execution data into
|
6
|
+
# structured format suitable for logging, API responses, or persistence.
|
91
7
|
module ChainSerializer
|
92
8
|
|
93
9
|
module_function
|
94
10
|
|
95
|
-
# Converts a
|
11
|
+
# Converts a chain object into a hash representation containing execution metadata.
|
12
|
+
# Extracts key chain attributes and serializes all contained results for complete
|
13
|
+
# execution state capture.
|
96
14
|
#
|
97
|
-
#
|
98
|
-
# and all associated task results. The chain-level data is derived from the
|
99
|
-
# first result in the collection, while all individual results are included
|
100
|
-
# in their full serialized form.
|
15
|
+
# @param chain [Chain] the chain instance to serialize
|
101
16
|
#
|
102
|
-
# @
|
103
|
-
# @return [Hash] Structured hash representation of the chain and all results
|
17
|
+
# @return [Hash] hash containing chain metadata and serialized results
|
104
18
|
#
|
105
|
-
# @
|
106
|
-
# chain = SimpleTask.call.chain
|
107
|
-
# ChainSerializer.call(chain)
|
108
|
-
# # => {
|
109
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
110
|
-
# # state: "complete",
|
111
|
-
# # status: "success",
|
112
|
-
# # outcome: "success",
|
113
|
-
# # runtime: 0.1,
|
114
|
-
# # results: [
|
115
|
-
# # {
|
116
|
-
# # class: "SimpleTask",
|
117
|
-
# # type: "Task",
|
118
|
-
# # index: 0,
|
119
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
120
|
-
# # chain_id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
121
|
-
# # tags: [],
|
122
|
-
# # state: "complete",
|
123
|
-
# # status: "success",
|
124
|
-
# # outcome: "success",
|
125
|
-
# # metadata: {},
|
126
|
-
# # runtime: 0.1
|
127
|
-
# # }
|
128
|
-
# # ]
|
129
|
-
# # }
|
130
|
-
#
|
131
|
-
# @example Multi-task chain serialization
|
132
|
-
# class ParentTask < CMDx::Task
|
133
|
-
# def call
|
134
|
-
# ChildTask.call(context)
|
135
|
-
# end
|
136
|
-
# end
|
19
|
+
# @raise [NoMethodError] if chain doesn't respond to required methods
|
137
20
|
#
|
138
|
-
#
|
21
|
+
# @example Serializing a workflow chain
|
22
|
+
# chain = UserWorkflow.call(user_id: 123)
|
139
23
|
# ChainSerializer.call(chain)
|
140
24
|
# # => {
|
141
|
-
# # id: "
|
142
|
-
# # state:
|
143
|
-
# # status:
|
144
|
-
# # outcome:
|
145
|
-
# # runtime: 0.
|
146
|
-
# # results: [
|
147
|
-
# # { class: "ParentTask", index: 0, ... },
|
148
|
-
# # { class: "ChildTask", index: 1, ... }
|
149
|
-
# # ]
|
150
|
-
# # }
|
151
|
-
#
|
152
|
-
# @example Empty chain serialization
|
153
|
-
# empty_chain = Chain.new
|
154
|
-
# ChainSerializer.call(empty_chain)
|
155
|
-
# # => {
|
156
|
-
# # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
|
157
|
-
# # state: nil,
|
158
|
-
# # status: nil,
|
159
|
-
# # outcome: nil,
|
160
|
-
# # runtime: nil,
|
161
|
-
# # results: []
|
25
|
+
# # id: "abc123",
|
26
|
+
# # state: :complete,
|
27
|
+
# # status: :success,
|
28
|
+
# # outcome: :good,
|
29
|
+
# # runtime: 0.045,
|
30
|
+
# # results: [...]
|
162
31
|
# # }
|
163
32
|
def call(chain)
|
164
33
|
{
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMDx
|
4
|
+
# Base class for implementing type coercion functionality in parameter processing.
|
5
|
+
#
|
6
|
+
# Coercions are used to convert parameter values from one type to another,
|
7
|
+
# supporting both built-in types and custom coercion logic. All coercion
|
8
|
+
# implementations must inherit from this class and implement the abstract call method.
|
9
|
+
class Coercion
|
10
|
+
|
11
|
+
# Executes a coercion by creating a new instance and calling it.
|
12
|
+
#
|
13
|
+
# @param value [Object] the value to be coerced
|
14
|
+
# @param options [Hash] additional options for the coercion
|
15
|
+
#
|
16
|
+
# @return [Object] the coerced value
|
17
|
+
#
|
18
|
+
# @raise [UndefinedCallError] when the coercion subclass doesn't implement call
|
19
|
+
#
|
20
|
+
# @example Execute a coercion on a value
|
21
|
+
# IntegerCoercion.call("42")
|
22
|
+
# # => 42
|
23
|
+
def self.call(value, options = {})
|
24
|
+
new.call(value, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Abstract method that must be implemented by coercion subclasses.
|
28
|
+
#
|
29
|
+
# This method contains the actual coercion logic to convert the input
|
30
|
+
# value to the desired type. Subclasses must override this method to
|
31
|
+
# provide their specific coercion implementation.
|
32
|
+
#
|
33
|
+
# @param _value [Object] the value to be coerced
|
34
|
+
# @param _options [Hash] additional options for the coercion
|
35
|
+
#
|
36
|
+
# @return [Object] the coerced value
|
37
|
+
#
|
38
|
+
# @raise [UndefinedCallError] always raised in the base class
|
39
|
+
#
|
40
|
+
# @example Implement in a subclass
|
41
|
+
# def call(value, options = {})
|
42
|
+
# Integer(value)
|
43
|
+
# end
|
44
|
+
def call(_value, _options = {})
|
45
|
+
raise UndefinedCallError, "call method not defined in #{self.class.name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|