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.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/docs.md +9 -0
  3. data/.cursor/prompts/rspec.md +21 -0
  4. data/.cursor/prompts/yardoc.md +13 -0
  5. data/.rubocop.yml +2 -0
  6. data/CHANGELOG.md +29 -3
  7. data/README.md +2 -1
  8. data/docs/ai_prompts.md +269 -195
  9. data/docs/basics/call.md +126 -60
  10. data/docs/basics/chain.md +190 -160
  11. data/docs/basics/context.md +242 -154
  12. data/docs/basics/setup.md +302 -32
  13. data/docs/callbacks.md +382 -119
  14. data/docs/configuration.md +211 -49
  15. data/docs/deprecation.md +245 -0
  16. data/docs/getting_started.md +161 -39
  17. data/docs/internationalization.md +590 -70
  18. data/docs/interruptions/exceptions.md +135 -118
  19. data/docs/interruptions/faults.md +152 -127
  20. data/docs/interruptions/halt.md +134 -80
  21. data/docs/logging.md +183 -120
  22. data/docs/middlewares.md +165 -392
  23. data/docs/outcomes/result.md +140 -112
  24. data/docs/outcomes/states.md +134 -99
  25. data/docs/outcomes/statuses.md +204 -146
  26. data/docs/parameters/coercions.md +251 -289
  27. data/docs/parameters/defaults.md +224 -169
  28. data/docs/parameters/definitions.md +289 -141
  29. data/docs/parameters/namespacing.md +250 -161
  30. data/docs/parameters/validations.md +247 -159
  31. data/docs/testing.md +196 -203
  32. data/docs/workflows.md +146 -101
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +39 -55
  35. data/lib/cmdx/callback_registry.rb +80 -73
  36. data/lib/cmdx/chain.rb +65 -122
  37. data/lib/cmdx/chain_inspector.rb +23 -116
  38. data/lib/cmdx/chain_serializer.rb +34 -146
  39. data/lib/cmdx/coercion.rb +57 -0
  40. data/lib/cmdx/coercion_registry.rb +113 -0
  41. data/lib/cmdx/coercions/array.rb +18 -36
  42. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  43. data/lib/cmdx/coercions/boolean.rb +21 -40
  44. data/lib/cmdx/coercions/complex.rb +18 -31
  45. data/lib/cmdx/coercions/date.rb +20 -39
  46. data/lib/cmdx/coercions/date_time.rb +22 -39
  47. data/lib/cmdx/coercions/float.rb +19 -32
  48. data/lib/cmdx/coercions/hash.rb +22 -41
  49. data/lib/cmdx/coercions/integer.rb +20 -33
  50. data/lib/cmdx/coercions/rational.rb +20 -32
  51. data/lib/cmdx/coercions/string.rb +23 -31
  52. data/lib/cmdx/coercions/time.rb +24 -40
  53. data/lib/cmdx/coercions/virtual.rb +14 -31
  54. data/lib/cmdx/configuration.rb +101 -162
  55. data/lib/cmdx/context.rb +34 -166
  56. data/lib/cmdx/core_ext/hash.rb +42 -67
  57. data/lib/cmdx/core_ext/module.rb +35 -79
  58. data/lib/cmdx/core_ext/object.rb +63 -98
  59. data/lib/cmdx/correlator.rb +59 -154
  60. data/lib/cmdx/error.rb +37 -202
  61. data/lib/cmdx/errors.rb +153 -216
  62. data/lib/cmdx/fault.rb +68 -150
  63. data/lib/cmdx/faults.rb +26 -137
  64. data/lib/cmdx/immutator.rb +22 -110
  65. data/lib/cmdx/lazy_struct.rb +110 -186
  66. data/lib/cmdx/log_formatters/json.rb +14 -40
  67. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  68. data/lib/cmdx/log_formatters/line.rb +14 -48
  69. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  70. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  71. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  72. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  73. data/lib/cmdx/log_formatters/raw.rb +19 -49
  74. data/lib/cmdx/logger.rb +22 -79
  75. data/lib/cmdx/logger_ansi.rb +31 -72
  76. data/lib/cmdx/logger_serializer.rb +74 -103
  77. data/lib/cmdx/middleware.rb +56 -60
  78. data/lib/cmdx/middleware_registry.rb +82 -77
  79. data/lib/cmdx/middlewares/correlate.rb +41 -226
  80. data/lib/cmdx/middlewares/timeout.rb +46 -185
  81. data/lib/cmdx/parameter.rb +167 -183
  82. data/lib/cmdx/parameter_evaluator.rb +231 -0
  83. data/lib/cmdx/parameter_inspector.rb +37 -55
  84. data/lib/cmdx/parameter_registry.rb +65 -84
  85. data/lib/cmdx/parameter_serializer.rb +32 -76
  86. data/lib/cmdx/railtie.rb +24 -107
  87. data/lib/cmdx/result.rb +254 -259
  88. data/lib/cmdx/result_ansi.rb +28 -80
  89. data/lib/cmdx/result_inspector.rb +34 -70
  90. data/lib/cmdx/result_logger.rb +23 -77
  91. data/lib/cmdx/result_serializer.rb +59 -125
  92. data/lib/cmdx/rspec/matchers.rb +28 -0
  93. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  94. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  95. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  96. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  97. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  98. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  99. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  100. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  101. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  102. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  103. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  104. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  105. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  106. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  107. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  108. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  109. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  110. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  111. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  112. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  113. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  114. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  115. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  116. data/lib/cmdx/task.rb +336 -427
  117. data/lib/cmdx/task_deprecator.rb +52 -0
  118. data/lib/cmdx/task_processor.rb +246 -0
  119. data/lib/cmdx/task_serializer.rb +34 -69
  120. data/lib/cmdx/utils/ansi_color.rb +13 -89
  121. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  122. data/lib/cmdx/utils/monotonic_runtime.rb +11 -63
  123. data/lib/cmdx/utils/name_affix.rb +21 -71
  124. data/lib/cmdx/validator.rb +57 -0
  125. data/lib/cmdx/validator_registry.rb +108 -0
  126. data/lib/cmdx/validators/exclusion.rb +55 -94
  127. data/lib/cmdx/validators/format.rb +31 -85
  128. data/lib/cmdx/validators/inclusion.rb +65 -110
  129. data/lib/cmdx/validators/length.rb +117 -133
  130. data/lib/cmdx/validators/numeric.rb +123 -130
  131. data/lib/cmdx/validators/presence.rb +38 -79
  132. data/lib/cmdx/version.rb +1 -7
  133. data/lib/cmdx/workflow.rb +58 -330
  134. data/lib/cmdx.rb +1 -1
  135. data/lib/generators/cmdx/install_generator.rb +14 -31
  136. data/lib/generators/cmdx/task_generator.rb +39 -55
  137. data/lib/generators/cmdx/templates/install.rb +24 -6
  138. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  139. data/lib/locales/ar.yml +0 -1
  140. data/lib/locales/cs.yml +0 -1
  141. data/lib/locales/da.yml +0 -1
  142. data/lib/locales/de.yml +0 -1
  143. data/lib/locales/el.yml +0 -1
  144. data/lib/locales/en.yml +0 -1
  145. data/lib/locales/es.yml +0 -1
  146. data/lib/locales/fi.yml +0 -1
  147. data/lib/locales/fr.yml +0 -1
  148. data/lib/locales/he.yml +0 -1
  149. data/lib/locales/hi.yml +0 -1
  150. data/lib/locales/it.yml +0 -1
  151. data/lib/locales/ja.yml +0 -1
  152. data/lib/locales/ko.yml +0 -1
  153. data/lib/locales/nl.yml +0 -1
  154. data/lib/locales/no.yml +0 -1
  155. data/lib/locales/pl.yml +0 -1
  156. data/lib/locales/pt.yml +0 -1
  157. data/lib/locales/ru.yml +0 -1
  158. data/lib/locales/sv.yml +0 -1
  159. data/lib/locales/th.yml +0 -1
  160. data/lib/locales/tr.yml +0 -1
  161. data/lib/locales/vi.yml +0 -1
  162. data/lib/locales/zh.yml +0 -1
  163. metadata +36 -8
  164. data/lib/cmdx/parameter_validator.rb +0 -81
  165. data/lib/cmdx/parameter_value.rb +0 -244
  166. data/lib/cmdx/parameters_inspector.rb +0 -72
  167. data/lib/cmdx/parameters_serializer.rb +0 -115
  168. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  169. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  170. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,165 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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
4
+ # Serialization module for converting chain objects to hash representation.
5
+ #
6
+ # ChainSerializer provides functionality to serialize chain objects into a
7
+ # standardized hash format that includes essential metadata about the chain
8
+ # execution including unique identification, execution state, status, outcome,
9
+ # runtime, and all contained task results. The serialized format is commonly
10
+ # used for debugging, logging, introspection, and data exchange throughout
11
+ # the task execution pipeline.
91
12
  module ChainSerializer
92
13
 
93
14
  module_function
94
15
 
95
- # Converts a Chain object to a hash representation.
16
+ # Serializes a chain object into a hash representation.
96
17
  #
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.
18
+ # Converts a chain instance into a standardized hash format containing
19
+ # key metadata about the chain's execution context and all contained results.
20
+ # The serialization includes information delegated from the first result in
21
+ # the chain (state, status, outcome, runtime) along with the chain's unique
22
+ # identifier and complete collection of task results converted to hashes.
101
23
  #
102
- # @param chain [CMDx::Chain] The chain object to serialize
103
- # @return [Hash] Structured hash representation of the chain and all results
24
+ # @param chain [CMDx::Chain] the chain object to serialize
104
25
  #
105
- # @example Simple chain serialization
106
- # chain = SimpleTask.call.chain
107
- # ChainSerializer.call(chain)
108
- # # => {
109
- # # id: "018c2b95-b764-7615-a924-cc5b910ed1e5",
26
+ # @return [Hash] a hash containing the chain's metadata and execution information
27
+ # @option return [String] :id the unique identifier of the chain
28
+ # @option return [String] :state the execution state delegated from first result
29
+ # @option return [String] :status the execution status delegated from first result
30
+ # @option return [String] :outcome the execution outcome delegated from first result
31
+ # @option return [Float] :runtime the execution runtime in seconds delegated from first result
32
+ # @option return [Array<Hash>] :results array of serialized result hashes from all tasks in the chain
33
+ #
34
+ # @raise [NoMethodError] if the chain doesn't respond to required methods (id, state, status, outcome, runtime, results)
35
+ #
36
+ # @example Serialize a workflow chain with multiple tasks
37
+ # workflow = DataProcessingWorkflow.call(input: "data")
38
+ # ChainSerializer.call(workflow.chain)
39
+ # #=> {
40
+ # # id: "def456",
110
41
  # # state: "complete",
111
42
  # # status: "success",
112
43
  # # outcome: "success",
113
- # # runtime: 0.1,
44
+ # # runtime: 0.123,
114
45
  # # 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
- # # }
46
+ # # { index: 0, class: "ValidateDataTask", status: "success", ... },
47
+ # # { index: 1, class: "ProcessDataTask", status: "success", ... },
48
+ # # { index: 2, class: "SaveDataTask", status: "success", ... }
128
49
  # # ]
129
50
  # # }
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
51
  def call(chain)
164
52
  {
165
53
  id: chain.id,
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Base class for implementing parameter coercion functionality in task processing.
5
+ #
6
+ # Coercions are used to convert parameter values from one type to another during
7
+ # task execution, enabling automatic type conversion and normalization. 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
+ # @raise [CoercionError] when coercion fails in subclass implementations
20
+ #
21
+ # @example Execute a coercion on a value
22
+ # StringCoercion.call(123) #=> "123"
23
+ #
24
+ # @example Execute with options
25
+ # CustomCoercion.call("value", strict: true) #=> processed_value
26
+ def self.call(value, options = {})
27
+ new.call(value, options)
28
+ end
29
+
30
+ # Abstract method that must be implemented by coercion subclasses.
31
+ #
32
+ # This method contains the actual coercion logic to convert the input
33
+ # value to the desired type. Subclasses must override this method
34
+ # to provide their specific coercion implementation.
35
+ #
36
+ # @param value [Object] the value to be coerced (unused in base class)
37
+ # @param options [Hash] additional options for the coercion (unused in base class)
38
+ #
39
+ # @return [Object] the coerced value
40
+ #
41
+ # @raise [UndefinedCallError] always raised in the base class
42
+ # @raise [CoercionError] when coercion fails in subclass implementations
43
+ #
44
+ # @example Implement in a subclass
45
+ # class StringCoercion < CMDx::Coercion
46
+ # def call(value, _options = {})
47
+ # String(value)
48
+ # rescue ArgumentError, TypeError
49
+ # raise CoercionError, "could not coerce into a string"
50
+ # end
51
+ # end
52
+ def call(value, options = {}) # rubocop:disable Lint/UnusedMethodArgument
53
+ raise UndefinedCallError, "call method not defined in #{self.class.name}"
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Registry for managing parameter type coercion functionality.
5
+ #
6
+ # CoercionRegistry provides a centralized system for storing, accessing, and
7
+ # executing type coercions during task parameter processing. It maintains an
8
+ # internal registry of coercion type keys mapped to their corresponding coercion
9
+ # classes or callables, supporting both built-in framework coercions and custom
10
+ # user-defined coercions for flexible type conversion during task execution.
11
+ class CoercionRegistry
12
+
13
+ # @return [Hash] hash containing coercion type keys and coercion class/callable values
14
+ attr_reader :registry
15
+
16
+ # Creates a new coercion registry with built-in coercion types.
17
+ #
18
+ # Initializes the registry with all standard framework coercions including
19
+ # primitive types (string, integer, float, boolean), date/time types,
20
+ # collection types (array, hash), numeric types (big_decimal, rational, complex),
21
+ # and the virtual coercion type for parameter definitions without type conversion.
22
+ #
23
+ # @return [CoercionRegistry] a new registry instance with built-in coercions
24
+ #
25
+ # @example Create a new coercion registry
26
+ # registry = CoercionRegistry.new
27
+ # registry.registry.keys
28
+ # #=> [:array, :big_decimal, :boolean, :complex, :date, :datetime, :float, :hash, :integer, :rational, :string, :time, :virtual]
29
+ def initialize
30
+ @registry = {
31
+ array: Coercions::Array,
32
+ big_decimal: Coercions::BigDecimal,
33
+ boolean: Coercions::Boolean,
34
+ complex: Coercions::Complex,
35
+ date: Coercions::Date,
36
+ datetime: Coercions::DateTime,
37
+ float: Coercions::Float,
38
+ hash: Coercions::Hash,
39
+ integer: Coercions::Integer,
40
+ rational: Coercions::Rational,
41
+ string: Coercions::String,
42
+ time: Coercions::Time,
43
+ virtual: Coercions::Virtual
44
+ }
45
+ end
46
+
47
+ # Registers a new coercion type in the registry.
48
+ #
49
+ # Adds or overwrites a coercion type mapping in the registry, allowing custom
50
+ # coercions to be used during task parameter processing. The coercion can be
51
+ # a class that responds to `call`, a callable object, or a symbol/string
52
+ # representing a method to invoke on the task instance.
53
+ #
54
+ # @param type [Symbol] the coercion type identifier to register
55
+ # @param coercion [Class, Proc, Symbol, String] the coercion implementation
56
+ #
57
+ # @return [CoercionRegistry] self for method chaining
58
+ #
59
+ # @example Register a custom coercion class
60
+ # registry.register(:temperature, TemperatureCoercion)
61
+ #
62
+ # @example Register a coercion proc
63
+ # registry.register(:upcase, proc { |value, options| value.to_s.upcase })
64
+ #
65
+ # @example Register a method symbol
66
+ # registry.register(:custom_parse, :parse_custom_format)
67
+ def register(type, coercion)
68
+ registry[type] = coercion
69
+ self
70
+ end
71
+
72
+ # Executes a coercion by type on the provided value.
73
+ #
74
+ # Looks up and executes the coercion implementation for the specified type,
75
+ # applying it to the provided value with optional configuration. Handles
76
+ # different coercion implementation types including callable objects,
77
+ # method symbols/strings, and coercion classes.
78
+ #
79
+ # @param task [CMDx::Task] the task instance for context when calling methods
80
+ # @param type [Symbol] the coercion type to execute
81
+ # @param value [Object] the value to be coerced
82
+ # @param options [Hash] additional options passed to the coercion
83
+ # @option options [Object] any any additional configuration for the coercion
84
+ #
85
+ # @return [Object] the coerced value
86
+ #
87
+ # @raise [UnknownCoercionError] when the specified coercion type is not registered
88
+ # @raise [CoercionError] when the coercion fails to convert the value
89
+ #
90
+ # @example Execute a built-in coercion
91
+ # registry.call(task, :integer, "123")
92
+ # #=> 123
93
+ #
94
+ # @example Execute with options
95
+ # registry.call(task, :date, "2024-01-15", format: "%Y-%m-%d")
96
+ # #=> #<Date: 2024-01-15>
97
+ #
98
+ # @example Handle unknown coercion type
99
+ # registry.call(task, :unknown_type, "value")
100
+ # #=> raises UnknownCoercionError
101
+ def call(task, type, value, options = {})
102
+ raise UnknownCoercionError, "unknown coercion #{type}" unless registry.key?(type)
103
+
104
+ case coercion = registry[type]
105
+ when Symbol, String, Proc
106
+ task.cmdx_try(coercion, value, options)
107
+ else
108
+ coercion.call(value, options)
109
+ end
110
+ end
111
+
112
+ end
113
+ end
@@ -2,47 +2,29 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
- # Coerces values to Array type.
5
+ # Coercion class for converting values to arrays.
6
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
27
- module Array
7
+ # This coercion handles conversion of various types to arrays, with special
8
+ # handling for JSON-formatted strings that start with "[".
9
+ class Array < Coercion
28
10
 
29
- module_function
30
-
31
- # Coerce a value to Array.
11
+ # Converts the given value to an array.
12
+ #
13
+ # @param value [Object] the value to convert to an array
14
+ # @param _options [Hash] optional configuration (currently unused)
15
+ #
16
+ # @return [Array] the converted array value
32
17
  #
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.
18
+ # @raise [JSON::ParserError] if value is a JSON string that cannot be parsed
19
+ # @raise [TypeError] if the value cannot be converted to an array
36
20
  #
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
21
+ # @example Converting a JSON string
22
+ # Coercions::Array.call('["a", "b", "c"]') #=> ["a", "b", "c"]
41
23
  #
42
- # @example
43
- # Coercions::Array.call("test") # => ["test"]
44
- # Coercions::Array.call('["a","b"]') # => ["a", "b"]
45
- # Coercions::Array.call([1, 2]) # => [1, 2]
24
+ # @example Converting other values
25
+ # Coercions::Array.call("hello") #=> ["hello"]
26
+ # Coercions::Array.call(123) #=> [123]
27
+ # Coercions::Array.call(nil) #=> []
46
28
  def call(value, _options = {})
47
29
  if value.is_a?(::String) && value.start_with?("[")
48
30
  JSON.parse(value)
@@ -2,45 +2,33 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
- # Coerces values to BigDecimal type.
5
+ # Coercion class for converting values to BigDecimal.
6
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
24
- module BigDecimal
7
+ # This coercion handles conversion of various types to BigDecimal with
8
+ # configurable precision. It provides precise decimal arithmetic capabilities
9
+ # for financial calculations and other use cases requiring exact decimal representation.
10
+ class BigDecimal < Coercion
25
11
 
26
- # Default precision for BigDecimal calculations
27
- # @return [Integer] default precision value
28
12
  DEFAULT_PRECISION = 14
29
13
 
30
- module_function
31
-
32
- # Coerce a value to BigDecimal.
14
+ # Converts the given value to a BigDecimal.
15
+ #
16
+ # @param value [Object] the value to convert to a BigDecimal
17
+ # @param options [Hash] optional configuration
18
+ # @option options [Integer] :precision the precision for the BigDecimal (defaults to 14)
19
+ #
20
+ # @return [BigDecimal] the converted BigDecimal value
21
+ #
22
+ # @raise [CoercionError] if the value cannot be converted to a BigDecimal
23
+ #
24
+ # @example Converting a string
25
+ # Coercions::BigDecimal.call('123.45') #=> #<BigDecimal:...,'0.12345E3',18(27)>
33
26
  #
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
27
+ # @example Converting with custom precision
28
+ # Coercions::BigDecimal.call('123.456789', precision: 10) #=> #<BigDecimal:...,'0.123456789E3',18(27)>
39
29
  #
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
30
+ # @example Converting an integer
31
+ # Coercions::BigDecimal.call(100) #=> #<BigDecimal:...,'0.1E3',9(18)>
44
32
  def call(value, options = {})
45
33
  BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
46
34
  rescue ArgumentError, TypeError
@@ -2,53 +2,34 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
- # Coerces values to Boolean type (true/false).
5
+ # Coercion class for converting values to booleans.
6
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
28
- module Boolean
7
+ # This coercion handles conversion of various string representations to
8
+ # boolean values, supporting common true/false variations like "yes/no",
9
+ # "1/0", and "t/f".
10
+ class Boolean < Coercion
29
11
 
30
- # Pattern matching false-like values (case insensitive)
31
- # @return [Regexp] regex for falsey string values
32
12
  FALSEY = /^(false|f|no|n|0)$/i
33
-
34
- # Pattern matching true-like values (case insensitive)
35
- # @return [Regexp] regex for truthy string values
36
13
  TRUTHY = /^(true|t|yes|y|1)$/i
37
14
 
38
- module_function
39
-
40
- # Coerce a value to Boolean.
15
+ # Converts the given value to a boolean.
16
+ #
17
+ # @param value [Object] the value to convert to a boolean
18
+ # @param _options [Hash] optional configuration (currently unused)
19
+ #
20
+ # @return [Boolean] the converted boolean value
21
+ #
22
+ # @raise [CoercionError] if the value cannot be converted to a boolean
41
23
  #
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
24
+ # @example Converting truthy values
25
+ # Coercions::Boolean.call('true') #=> true
26
+ # Coercions::Boolean.call('yes') #=> true
27
+ # Coercions::Boolean.call('1') #=> true
46
28
  #
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
29
+ # @example Converting falsey values
30
+ # Coercions::Boolean.call('false') #=> false
31
+ # Coercions::Boolean.call('no') #=> false
32
+ # Coercions::Boolean.call('0') #=> false
52
33
  def call(value, _options = {})
53
34
  case value.to_s.downcase
54
35
  when FALSEY then false
@@ -2,41 +2,28 @@
2
2
 
3
3
  module CMDx
4
4
  module Coercions
5
- # Coerces values to Complex type.
5
+ # Coercion class for converting values to complex numbers.
6
6
  #
7
- # The Complex coercion converts parameter values to Complex number objects
8
- # using Ruby's built-in Complex() method, with proper error handling
9
- # for values that cannot be converted to complex numbers.
10
- #
11
- # @example Basic complex coercion
12
- # class MathTask < CMDx::Task
13
- # required :complex_number, type: :complex
14
- # optional :coefficient, type: :complex, default: Complex(1, 0)
15
- # end
16
- #
17
- # @example Coercion behavior
18
- # Coercions::Complex.call("1+2i") # => (1+2i)
19
- # Coercions::Complex.call("3-4i") # => (3-4i)
20
- # Coercions::Complex.call(5) # => (5+0i)
21
- # Coercions::Complex.call("invalid") # => raises CoercionError
22
- #
23
- # @see ParameterValue Parameter value coercion
24
- # @see Parameter Parameter type definitions
25
- module Complex
26
-
27
- module_function
7
+ # This coercion handles conversion of various types to complex numbers,
8
+ # including strings, integers, floats, and other numeric types.
9
+ class Complex < Coercion
28
10
 
29
- # Coerce a value to Complex.
11
+ # Converts the given value to a complex number.
12
+ #
13
+ # @param value [Object] the value to convert to a complex number
14
+ # @param _options [Hash] optional configuration (currently unused)
15
+ #
16
+ # @return [Complex] the converted complex number value
17
+ #
18
+ # @raise [CoercionError] if the value cannot be converted to a complex number
30
19
  #
31
- # @param value [Object] value to coerce to complex number
32
- # @param _options [Hash] coercion options (unused)
33
- # @return [Complex] coerced complex number value
34
- # @raise [CoercionError] if coercion fails
20
+ # @example Converting numeric values
21
+ # Coercions::Complex.call(5) #=> (5+0i)
22
+ # Coercions::Complex.call(3.14) #=> (3.14+0i)
35
23
  #
36
- # @example
37
- # Coercions::Complex.call("1+2i") # => (1+2i)
38
- # Coercions::Complex.call(42) # => (42+0i)
39
- # Coercions::Complex.call("3.5-2i") # => (3.5-2i)
24
+ # @example Converting string representations
25
+ # Coercions::Complex.call("2+3i") #=> (2+3i)
26
+ # Coercions::Complex.call("1-2i") #=> (1-2i)
40
27
  def call(value, _options = {})
41
28
  Complex(value)
42
29
  rescue ArgumentError, TypeError