cmdx 0.4.0 → 1.0.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +16 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +42 -1
  7. data/README.md +72 -25
  8. data/docs/ai_prompts.md +309 -0
  9. data/docs/basics/call.md +225 -14
  10. data/docs/basics/chain.md +271 -0
  11. data/docs/basics/context.md +232 -33
  12. data/docs/basics/setup.md +76 -12
  13. data/docs/callbacks.md +273 -0
  14. data/docs/configuration.md +158 -28
  15. data/docs/getting_started.md +134 -22
  16. data/docs/interruptions/exceptions.md +189 -11
  17. data/docs/interruptions/faults.md +187 -44
  18. data/docs/interruptions/halt.md +179 -35
  19. data/docs/logging.md +194 -53
  20. data/docs/middlewares.md +735 -0
  21. data/docs/outcomes/result.md +296 -10
  22. data/docs/outcomes/states.md +212 -19
  23. data/docs/outcomes/statuses.md +284 -18
  24. data/docs/parameters/coercions.md +402 -29
  25. data/docs/parameters/defaults.md +249 -25
  26. data/docs/parameters/definitions.md +238 -72
  27. data/docs/parameters/namespacing.md +250 -27
  28. data/docs/parameters/validations.md +193 -168
  29. data/docs/testing.md +550 -0
  30. data/docs/tips_and_tricks.md +95 -43
  31. data/docs/workflows.md +319 -0
  32. data/lib/cmdx/.DS_Store +0 -0
  33. data/lib/cmdx/callback.rb +69 -0
  34. data/lib/cmdx/callback_registry.rb +106 -0
  35. data/lib/cmdx/chain.rb +190 -0
  36. data/lib/cmdx/chain_inspector.rb +149 -0
  37. data/lib/cmdx/chain_serializer.rb +175 -0
  38. data/lib/cmdx/coercions/array.rb +37 -0
  39. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  40. data/lib/cmdx/coercions/boolean.rb +41 -1
  41. data/lib/cmdx/coercions/complex.rb +31 -0
  42. data/lib/cmdx/coercions/date.rb +39 -0
  43. data/lib/cmdx/coercions/date_time.rb +39 -0
  44. data/lib/cmdx/coercions/float.rb +31 -0
  45. data/lib/cmdx/coercions/hash.rb +42 -0
  46. data/lib/cmdx/coercions/integer.rb +32 -0
  47. data/lib/cmdx/coercions/rational.rb +31 -0
  48. data/lib/cmdx/coercions/string.rb +31 -0
  49. data/lib/cmdx/coercions/time.rb +39 -0
  50. data/lib/cmdx/coercions/virtual.rb +31 -0
  51. data/lib/cmdx/configuration.rb +217 -9
  52. data/lib/cmdx/context.rb +173 -2
  53. data/lib/cmdx/core_ext/hash.rb +72 -0
  54. data/lib/cmdx/core_ext/module.rb +94 -0
  55. data/lib/cmdx/core_ext/object.rb +105 -0
  56. data/lib/cmdx/correlator.rb +217 -0
  57. data/lib/cmdx/error.rb +210 -8
  58. data/lib/cmdx/errors.rb +256 -1
  59. data/lib/cmdx/fault.rb +177 -2
  60. data/lib/cmdx/faults.rb +158 -2
  61. data/lib/cmdx/immutator.rb +121 -2
  62. data/lib/cmdx/lazy_struct.rb +261 -18
  63. data/lib/cmdx/log_formatters/json.rb +46 -0
  64. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  65. data/lib/cmdx/log_formatters/line.rb +54 -0
  66. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  67. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  68. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  69. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  70. data/lib/cmdx/log_formatters/raw.rb +54 -0
  71. data/lib/cmdx/logger.rb +85 -0
  72. data/lib/cmdx/logger_ansi.rb +93 -7
  73. data/lib/cmdx/logger_serializer.rb +116 -0
  74. data/lib/cmdx/middleware.rb +74 -0
  75. data/lib/cmdx/middleware_registry.rb +106 -0
  76. data/lib/cmdx/middlewares/correlate.rb +266 -0
  77. data/lib/cmdx/middlewares/timeout.rb +232 -0
  78. data/lib/cmdx/parameter.rb +228 -1
  79. data/lib/cmdx/parameter_inspector.rb +61 -0
  80. data/lib/cmdx/parameter_registry.rb +125 -0
  81. data/lib/cmdx/parameter_serializer.rb +83 -0
  82. data/lib/cmdx/parameter_validator.rb +62 -0
  83. data/lib/cmdx/parameter_value.rb +109 -1
  84. data/lib/cmdx/parameters_inspector.rb +59 -0
  85. data/lib/cmdx/parameters_serializer.rb +102 -0
  86. data/lib/cmdx/railtie.rb +123 -3
  87. data/lib/cmdx/result.rb +399 -20
  88. data/lib/cmdx/result_ansi.rb +105 -9
  89. data/lib/cmdx/result_inspector.rb +76 -0
  90. data/lib/cmdx/result_logger.rb +90 -3
  91. data/lib/cmdx/result_serializer.rb +137 -0
  92. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  93. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  94. data/lib/cmdx/task.rb +409 -34
  95. data/lib/cmdx/task_serializer.rb +74 -2
  96. data/lib/cmdx/utils/ansi_color.rb +95 -0
  97. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  98. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  99. data/lib/cmdx/utils/name_affix.rb +78 -0
  100. data/lib/cmdx/validators/custom.rb +82 -0
  101. data/lib/cmdx/validators/exclusion.rb +94 -0
  102. data/lib/cmdx/validators/format.rb +102 -8
  103. data/lib/cmdx/validators/inclusion.rb +104 -0
  104. data/lib/cmdx/validators/length.rb +128 -0
  105. data/lib/cmdx/validators/numeric.rb +128 -0
  106. data/lib/cmdx/validators/presence.rb +93 -7
  107. data/lib/cmdx/version.rb +7 -1
  108. data/lib/cmdx/workflow.rb +394 -0
  109. data/lib/cmdx.rb +25 -64
  110. data/lib/generators/cmdx/install_generator.rb +37 -1
  111. data/lib/generators/cmdx/task_generator.rb +69 -1
  112. data/lib/generators/cmdx/templates/install.rb +8 -12
  113. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  114. metadata +54 -15
  115. data/docs/basics/run.md +0 -34
  116. data/docs/batch.md +0 -53
  117. data/docs/example.md +0 -82
  118. data/docs/hooks.md +0 -59
  119. data/lib/cmdx/batch.rb +0 -43
  120. data/lib/cmdx/parameters.rb +0 -34
  121. data/lib/cmdx/run.rb +0 -38
  122. data/lib/cmdx/run_inspector.rb +0 -26
  123. data/lib/cmdx/run_serializer.rb +0 -16
  124. data/lib/cmdx/task_hook.rb +0 -18
  125. data/lib/generators/cmdx/batch_generator.rb +0 -30
  126. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
data/lib/cmdx/chain.rb ADDED
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Thread-local chain that tracks task execution results within a correlation context.
5
+ #
6
+ # A Chain represents a sequence of task executions that are logically related,
7
+ # typically within the same request or operation flow. It provides thread-local
8
+ # storage to ensure that tasks executing in the same thread share the same chain
9
+ # while maintaining isolation across different threads.
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
48
+ class Chain
49
+
50
+ # Thread-local storage key for the current chain
51
+ THREAD_KEY = :cmdx_correlation_chain
52
+
53
+ __cmdx_attr_delegator :index, :first, :last, :size,
54
+ to: :results
55
+ __cmdx_attr_delegator :state, :status, :outcome, :runtime,
56
+ to: :first
57
+
58
+ # @!attribute [r] id
59
+ # @return [String] the unique identifier for this chain
60
+ # @!attribute [r] results
61
+ # @return [Array<CMDx::Result>] the collection of task results in this chain
62
+ attr_reader :id, :results
63
+
64
+ # Creates a new chain instance.
65
+ #
66
+ # @param attributes [Hash] configuration options for the chain
67
+ # @option attributes [String] :id custom identifier for the chain.
68
+ # If not provided, uses the current correlator ID or generates a new UUID.
69
+ #
70
+ # @example Create chain with default ID
71
+ # chain = CMDx::Chain.new
72
+ # chain.id #=> "018c2b95-b764-7615-a924-cc5b910ed1e5"
73
+ #
74
+ # @example Create chain with custom ID
75
+ # chain = CMDx::Chain.new(id: "user-session-123")
76
+ # chain.id #=> "user-session-123"
77
+ def initialize(attributes = {})
78
+ @id = attributes[:id] || CMDx::Correlator.id || CMDx::Correlator.generate
79
+ @results = []
80
+ end
81
+
82
+ class << self
83
+
84
+ # Returns the current thread-local chain.
85
+ #
86
+ # @return [CMDx::Chain, nil] the chain for the current thread, or nil if none exists
87
+ #
88
+ # @example
89
+ # CMDx::Chain.current #=> nil (no chain set)
90
+ #
91
+ # MyTask.call(data: "test")
92
+ # CMDx::Chain.current #=> #<CMDx::Chain:0x... @id="018c2b95...">
93
+ def current
94
+ Thread.current[THREAD_KEY]
95
+ end
96
+
97
+ # Sets the current thread-local chain.
98
+ #
99
+ # @param chain [CMDx::Chain, nil] the chain to set for the current thread
100
+ # @return [CMDx::Chain, nil] the chain that was set
101
+ #
102
+ # @example
103
+ # chain = CMDx::Chain.new(id: "custom-id")
104
+ # CMDx::Chain.current = chain
105
+ # CMDx::Chain.current.id #=> "custom-id"
106
+ def current=(chain)
107
+ Thread.current[THREAD_KEY] = chain
108
+ end
109
+
110
+ # Clears the current thread-local chain.
111
+ #
112
+ # @return [nil]
113
+ #
114
+ # @example
115
+ # CMDx::Chain.current #=> #<CMDx::Chain:0x...>
116
+ # CMDx::Chain.clear
117
+ # CMDx::Chain.current #=> nil
118
+ def clear
119
+ Thread.current[THREAD_KEY] = nil
120
+ end
121
+
122
+ # Adds a result to the current chain, creating a new chain if none exists.
123
+ #
124
+ # This method is typically called internally by the task execution framework
125
+ # and should not be used directly in application code.
126
+ #
127
+ # @param result [CMDx::Result] the task result to add to the chain
128
+ # @return [CMDx::Chain] the chain containing the result
129
+ #
130
+ # @api private
131
+ def build(result)
132
+ raise TypeError, "must be a Result" unless result.is_a?(Result)
133
+
134
+ self.current ||= new
135
+ current.results << result
136
+ current
137
+ end
138
+
139
+ end
140
+
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.
145
+ #
146
+ # @return [Hash] Structured hash representation of the chain
147
+ #
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
+ # # }
161
+ def to_h
162
+ ChainSerializer.call(self)
163
+ end
164
+ alias to_a to_h
165
+
166
+ # Converts the chain to a string representation for inspection.
167
+ #
168
+ # Creates a comprehensive, human-readable summary of the chain including
169
+ # all task results with formatted headers and footers.
170
+ #
171
+ # @return [String] Formatted chain summary with task details
172
+ #
173
+ # @example
174
+ # chain.to_s
175
+ # # => "
176
+ # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
177
+ # # ================================================
178
+ # #
179
+ # # ProcessOrderTask: index=0 state=complete status=success ...
180
+ # # SendEmailTask: index=1 state=complete status=success ...
181
+ # #
182
+ # # ================================================
183
+ # # state: complete | status: success | outcome: success | runtime: 0.5
184
+ # # "
185
+ def to_s
186
+ ChainInspector.call(self)
187
+ end
188
+
189
+ end
190
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Chain inspection utility for generating comprehensive chain summaries.
5
+ #
6
+ # The ChainInspector module provides functionality to convert Chain instances
7
+ # into detailed, human-readable string representations. It creates formatted
8
+ # summaries that include chain metadata, all task results, and summary statistics
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
56
+ module ChainInspector
57
+
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
+ FOOTER_KEYS = %i[
63
+ state status outcome runtime
64
+ ].freeze
65
+
66
+ module_function
67
+
68
+ # Converts a Chain instance to a comprehensive string representation.
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
76
+ #
77
+ # @param chain [CMDx::Chain] The chain instance to inspect
78
+ # @return [String] Formatted chain summary with task details and statistics
79
+ #
80
+ # @example Single task chain
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
+ # # "
100
+ #
101
+ # @example Multi-task chain
102
+ # class ParentTask < CMDx::Task
103
+ # def call
104
+ # ChildTask1.call(context)
105
+ # ChildTask2.call(context)
106
+ # end
107
+ # end
108
+ #
109
+ # chain = ParentTask.call.chain
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
+ # # "
122
+ #
123
+ # @example Failed chain inspection
124
+ # failed_chain = FailingTask.call.chain
125
+ # ChainInspector.call(failed_chain)
126
+ # # => "
127
+ # # chain: 018c2b95-b764-7615-a924-cc5b910ed1e5
128
+ # # ================================================
129
+ # #
130
+ # # { class: "FailingTask", state: "interrupted", status: "failed", metadata: { reason: "Error" }, ... }
131
+ # #
132
+ # # ================================================
133
+ # # state: interrupted | status: failed | outcome: failed | runtime: 0.1
134
+ # # "
135
+ def call(chain)
136
+ header = "\nchain: #{chain.id}"
137
+ footer = FOOTER_KEYS.map { |key| "#{key}: #{chain.send(key)}" }.join(" | ")
138
+ spacer = "=" * [header.size, footer.size].max
139
+
140
+ chain
141
+ .results
142
+ .map { |r| r.to_h.except(:chain_id).pretty_inspect }
143
+ .unshift(header, "#{spacer}\n")
144
+ .push(spacer, "#{footer}\n\n")
145
+ .join("\n")
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Chain serialization utility for converting Chain objects to hash representations.
5
+ #
6
+ # The ChainSerializer module provides functionality to serialize Chain instances
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
91
+ module ChainSerializer
92
+
93
+ module_function
94
+
95
+ # Converts a Chain object to a hash representation.
96
+ #
97
+ # Serializes a Chain instance into a structured hash containing chain metadata
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.
101
+ #
102
+ # @param chain [CMDx::Chain] The chain object to serialize
103
+ # @return [Hash] Structured hash representation of the chain and all results
104
+ #
105
+ # @example Simple chain serialization
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
137
+ #
138
+ # chain = ParentTask.call.chain
139
+ # ChainSerializer.call(chain)
140
+ # # => {
141
+ # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
142
+ # # state: "complete", # From first result (ParentTask)
143
+ # # status: "success", # From first result (ParentTask)
144
+ # # outcome: "success", # From first result (ParentTask)
145
+ # # runtime: 0.5, # From first result (ParentTask)
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: []
162
+ # # }
163
+ def call(chain)
164
+ {
165
+ id: chain.id,
166
+ state: chain.state,
167
+ status: chain.status,
168
+ outcome: chain.outcome,
169
+ runtime: chain.runtime,
170
+ results: chain.results.map(&:to_h)
171
+ }
172
+ end
173
+
174
+ end
175
+ end
@@ -2,10 +2,47 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
+ # Coerces values to Array type.
6
+ #
7
+ # The Array coercion converts parameter values to Array objects,
8
+ # with special handling for JSON-formatted strings and general
9
+ # array conversion using Ruby's Array() method.
10
+ #
11
+ # @example Basic array coercion
12
+ # class ProcessOrderTask < CMDx::Task
13
+ # optional :tags, type: :array, default: []
14
+ # optional :item_ids, type: :array
15
+ # end
16
+ #
17
+ # @example Coercion behavior
18
+ # Coercions::Array.call([1, 2, 3]) # => [1, 2, 3]
19
+ # Coercions::Array.call("hello") # => ["hello"]
20
+ # Coercions::Array.call('["a","b"]') # => ["a", "b"] (JSON)
21
+ # Coercions::Array.call('[1,2,3]') # => [1, 2, 3] (JSON)
22
+ # Coercions::Array.call(nil) # => []
23
+ # Coercions::Array.call(42) # => [42]
24
+ #
25
+ # @see ParameterValue Parameter value coercion
26
+ # @see Parameter Parameter type definitions
5
27
  module Array
6
28
 
7
29
  module_function
8
30
 
31
+ # Coerce a value to Array.
32
+ #
33
+ # If the value is a JSON-formatted string (starts with '['), it will
34
+ # be parsed as JSON. Otherwise, it uses Ruby's Array() method for
35
+ # general array conversion.
36
+ #
37
+ # @param value [Object] value to coerce to array
38
+ # @param _options [Hash] coercion options (unused)
39
+ # @return [Array] coerced array value
40
+ # @raise [JSON::ParserError] if JSON parsing fails
41
+ #
42
+ # @example
43
+ # Coercions::Array.call("test") # => ["test"]
44
+ # Coercions::Array.call('["a","b"]') # => ["a", "b"]
45
+ # Coercions::Array.call([1, 2]) # => [1, 2]
9
46
  def call(value, _options = {})
10
47
  if value.is_a?(::String) && value.start_with?("[")
11
48
  JSON.parse(value)
@@ -2,12 +2,45 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
+ # Coerces values to BigDecimal type.
6
+ #
7
+ # The BigDecimal coercion converts parameter values to BigDecimal objects
8
+ # for high-precision decimal arithmetic. Supports configurable precision
9
+ # and handles various numeric input formats.
10
+ #
11
+ # @example Basic BigDecimal coercion
12
+ # class ProcessOrderTask < CMDx::Task
13
+ # required :total_amount, type: :big_decimal
14
+ # optional :tax_rate, type: :big_decimal, precision: 4
15
+ # end
16
+ #
17
+ # @example Coercion behavior
18
+ # Coercions::BigDecimal.call("123.45") # => #<BigDecimal:...,'0.12345E3',18(27)>
19
+ # Coercions::BigDecimal.call(42) # => #<BigDecimal:...,'0.42E2',9(18)>
20
+ # Coercions::BigDecimal.call("0.333333", precision: 6) # Custom precision
21
+ #
22
+ # @see ParameterValue Parameter value coercion
23
+ # @see Parameter Parameter type definitions
5
24
  module BigDecimal
6
25
 
26
+ # Default precision for BigDecimal calculations
27
+ # @return [Integer] default precision value
7
28
  DEFAULT_PRECISION = 14
8
29
 
9
30
  module_function
10
31
 
32
+ # Coerce a value to BigDecimal.
33
+ #
34
+ # @param value [Object] value to coerce to BigDecimal
35
+ # @param options [Hash] coercion options
36
+ # @option options [Integer] :precision decimal precision (default: 14)
37
+ # @return [BigDecimal] coerced BigDecimal value
38
+ # @raise [CoercionError] if coercion fails
39
+ #
40
+ # @example
41
+ # Coercions::BigDecimal.call("123.45") # => BigDecimal with default precision
42
+ # Coercions::BigDecimal.call("0.333", precision: 10) # => BigDecimal with custom precision
43
+ # Coercions::BigDecimal.call(42.5) # => BigDecimal from float
11
44
  def call(value, options = {})
12
45
  BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
13
46
  rescue ArgumentError, TypeError
@@ -2,15 +2,55 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
+ # Coerces values to Boolean type (true/false).
6
+ #
7
+ # The Boolean coercion converts parameter values to true or false
8
+ # based on string pattern matching for common boolean representations.
9
+ # It handles various textual representations of true and false values.
10
+ #
11
+ # @example Basic boolean coercion
12
+ # class ProcessOrderTask < CMDx::Task
13
+ # optional :send_email, type: :boolean, default: true
14
+ # optional :is_urgent, type: :boolean, default: false
15
+ # end
16
+ #
17
+ # @example Coercion behavior
18
+ # Coercions::Boolean.call("true") # => true
19
+ # Coercions::Boolean.call("yes") # => true
20
+ # Coercions::Boolean.call("1") # => true
21
+ # Coercions::Boolean.call("false") # => false
22
+ # Coercions::Boolean.call("no") # => false
23
+ # Coercions::Boolean.call("0") # => false
24
+ # Coercions::Boolean.call("invalid") # => raises CoercionError
25
+ #
26
+ # @see ParameterValue Parameter value coercion
27
+ # @see Parameter Parameter type definitions
5
28
  module Boolean
6
29
 
30
+ # Pattern matching false-like values (case insensitive)
31
+ # @return [Regexp] regex for falsey string values
7
32
  FALSEY = /^(false|f|no|n|0)$/i
33
+
34
+ # Pattern matching true-like values (case insensitive)
35
+ # @return [Regexp] regex for truthy string values
8
36
  TRUTHY = /^(true|t|yes|y|1)$/i
9
37
 
10
38
  module_function
11
39
 
40
+ # Coerce a value to Boolean.
41
+ #
42
+ # @param value [Object] value to coerce to boolean
43
+ # @param _options [Hash] coercion options (unused)
44
+ # @return [Boolean] coerced boolean value (true or false)
45
+ # @raise [CoercionError] if value cannot be coerced to boolean
46
+ #
47
+ # @example
48
+ # Coercions::Boolean.call("yes") # => true
49
+ # Coercions::Boolean.call("False") # => false
50
+ # Coercions::Boolean.call("1") # => true
51
+ # Coercions::Boolean.call("0") # => false
12
52
  def call(value, _options = {})
13
- case value.to_s
53
+ case value.to_s.downcase
14
54
  when FALSEY then false
15
55
  when TRUTHY then true
16
56
  else