cmdx 1.0.1 → 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.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +17 -2
  6. data/README.md +1 -1
  7. data/docs/basics/call.md +2 -2
  8. data/docs/basics/chain.md +1 -1
  9. data/docs/callbacks.md +3 -36
  10. data/docs/configuration.md +58 -12
  11. data/docs/interruptions/exceptions.md +1 -1
  12. data/docs/interruptions/faults.md +2 -2
  13. data/docs/logging.md +4 -4
  14. data/docs/middlewares.md +43 -43
  15. data/docs/parameters/coercions.md +49 -38
  16. data/docs/parameters/defaults.md +1 -1
  17. data/docs/parameters/validations.md +0 -39
  18. data/docs/testing.md +11 -12
  19. data/docs/workflows.md +4 -4
  20. data/lib/cmdx/.DS_Store +0 -0
  21. data/lib/cmdx/callback.rb +36 -56
  22. data/lib/cmdx/callback_registry.rb +82 -73
  23. data/lib/cmdx/chain.rb +65 -122
  24. data/lib/cmdx/chain_inspector.rb +22 -115
  25. data/lib/cmdx/chain_serializer.rb +17 -148
  26. data/lib/cmdx/coercion.rb +49 -0
  27. data/lib/cmdx/coercion_registry.rb +94 -0
  28. data/lib/cmdx/coercions/array.rb +18 -36
  29. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  30. data/lib/cmdx/coercions/boolean.rb +21 -40
  31. data/lib/cmdx/coercions/complex.rb +18 -31
  32. data/lib/cmdx/coercions/date.rb +20 -39
  33. data/lib/cmdx/coercions/date_time.rb +22 -39
  34. data/lib/cmdx/coercions/float.rb +19 -32
  35. data/lib/cmdx/coercions/hash.rb +22 -41
  36. data/lib/cmdx/coercions/integer.rb +20 -33
  37. data/lib/cmdx/coercions/rational.rb +20 -32
  38. data/lib/cmdx/coercions/string.rb +23 -31
  39. data/lib/cmdx/coercions/time.rb +24 -40
  40. data/lib/cmdx/coercions/virtual.rb +14 -31
  41. data/lib/cmdx/configuration.rb +57 -171
  42. data/lib/cmdx/context.rb +22 -165
  43. data/lib/cmdx/core_ext/hash.rb +42 -67
  44. data/lib/cmdx/core_ext/module.rb +35 -79
  45. data/lib/cmdx/core_ext/object.rb +63 -98
  46. data/lib/cmdx/correlator.rb +40 -156
  47. data/lib/cmdx/error.rb +37 -202
  48. data/lib/cmdx/errors.rb +165 -202
  49. data/lib/cmdx/fault.rb +55 -158
  50. data/lib/cmdx/faults.rb +26 -137
  51. data/lib/cmdx/immutator.rb +22 -109
  52. data/lib/cmdx/lazy_struct.rb +103 -187
  53. data/lib/cmdx/log_formatters/json.rb +14 -40
  54. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  55. data/lib/cmdx/log_formatters/line.rb +14 -48
  56. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  57. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  58. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  59. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  60. data/lib/cmdx/log_formatters/raw.rb +19 -49
  61. data/lib/cmdx/logger.rb +20 -82
  62. data/lib/cmdx/logger_ansi.rb +18 -75
  63. data/lib/cmdx/logger_serializer.rb +24 -114
  64. data/lib/cmdx/middleware.rb +38 -60
  65. data/lib/cmdx/middleware_registry.rb +81 -77
  66. data/lib/cmdx/middlewares/correlate.rb +41 -226
  67. data/lib/cmdx/middlewares/timeout.rb +46 -185
  68. data/lib/cmdx/parameter.rb +120 -198
  69. data/lib/cmdx/parameter_evaluator.rb +231 -0
  70. data/lib/cmdx/parameter_inspector.rb +25 -56
  71. data/lib/cmdx/parameter_registry.rb +59 -84
  72. data/lib/cmdx/parameter_serializer.rb +23 -74
  73. data/lib/cmdx/railtie.rb +24 -107
  74. data/lib/cmdx/result.rb +254 -260
  75. data/lib/cmdx/result_ansi.rb +19 -85
  76. data/lib/cmdx/result_inspector.rb +27 -68
  77. data/lib/cmdx/result_logger.rb +18 -81
  78. data/lib/cmdx/result_serializer.rb +28 -132
  79. data/lib/cmdx/rspec/matchers.rb +28 -0
  80. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  81. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  82. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  83. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  84. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  85. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  86. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  87. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  88. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  89. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  90. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  91. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  92. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  93. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  94. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  95. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  96. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  97. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  98. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  99. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  100. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  101. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  102. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  103. data/lib/cmdx/task.rb +213 -425
  104. data/lib/cmdx/task_deprecator.rb +55 -0
  105. data/lib/cmdx/task_processor.rb +245 -0
  106. data/lib/cmdx/task_serializer.rb +22 -70
  107. data/lib/cmdx/utils/ansi_color.rb +13 -89
  108. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  109. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  110. data/lib/cmdx/utils/name_affix.rb +21 -71
  111. data/lib/cmdx/validator.rb +48 -0
  112. data/lib/cmdx/validator_registry.rb +86 -0
  113. data/lib/cmdx/validators/exclusion.rb +55 -94
  114. data/lib/cmdx/validators/format.rb +31 -85
  115. data/lib/cmdx/validators/inclusion.rb +65 -110
  116. data/lib/cmdx/validators/length.rb +117 -133
  117. data/lib/cmdx/validators/numeric.rb +123 -130
  118. data/lib/cmdx/validators/presence.rb +38 -79
  119. data/lib/cmdx/version.rb +1 -7
  120. data/lib/cmdx/workflow.rb +46 -339
  121. data/lib/cmdx.rb +1 -1
  122. data/lib/generators/cmdx/install_generator.rb +14 -31
  123. data/lib/generators/cmdx/task_generator.rb +39 -55
  124. data/lib/generators/cmdx/templates/install.rb +24 -6
  125. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  126. data/lib/locales/ar.yml +0 -1
  127. data/lib/locales/cs.yml +0 -1
  128. data/lib/locales/da.yml +0 -1
  129. data/lib/locales/de.yml +0 -1
  130. data/lib/locales/el.yml +0 -1
  131. data/lib/locales/en.yml +0 -1
  132. data/lib/locales/es.yml +0 -1
  133. data/lib/locales/fi.yml +0 -1
  134. data/lib/locales/fr.yml +0 -1
  135. data/lib/locales/he.yml +0 -1
  136. data/lib/locales/hi.yml +0 -1
  137. data/lib/locales/it.yml +0 -1
  138. data/lib/locales/ja.yml +0 -1
  139. data/lib/locales/ko.yml +0 -1
  140. data/lib/locales/nl.yml +0 -1
  141. data/lib/locales/no.yml +0 -1
  142. data/lib/locales/pl.yml +0 -1
  143. data/lib/locales/pt.yml +0 -1
  144. data/lib/locales/ru.yml +0 -1
  145. data/lib/locales/sv.yml +0 -1
  146. data/lib/locales/th.yml +0 -1
  147. data/lib/locales/tr.yml +0 -1
  148. data/lib/locales/vi.yml +0 -1
  149. data/lib/locales/zh.yml +0 -1
  150. metadata +34 -8
  151. data/lib/cmdx/parameter_validator.rb +0 -81
  152. data/lib/cmdx/parameter_value.rb +0 -244
  153. data/lib/cmdx/parameters_inspector.rb +0 -72
  154. data/lib/cmdx/parameters_serializer.rb +0 -115
  155. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  156. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  157. data/lib/cmdx/validators/custom.rb +0 -102
@@ -1,137 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Chain inspection utility for generating comprehensive chain summaries.
4
+ # Provides formatted inspection and display functionality for execution chains.
5
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
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
- # 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
17
+ # Formats a chain into a human-readable inspection string.
76
18
  #
77
- # @param chain [CMDx::Chain] The chain instance to inspect
78
- # @return [String] Formatted chain summary with task details and statistics
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
- # @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
- # # "
23
+ # @param chain [CMDx::Chain] the chain object to inspect
100
24
  #
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
25
+ # @return [String] formatted multi-line string representation of the chain
108
26
  #
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
- # # "
27
+ # @raise [NoMethodError] if chain doesn't respond to required methods (id, results, state, status, outcome, runtime)
122
28
  #
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
- # # ================================================
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
- # # { class: "FailingTask", state: "interrupted", status: "failed", metadata: { reason: "Error" }, ... }
38
+ # # {:state=>"complete", :status=>"success", ...}
131
39
  # #
132
- # # ================================================
133
- # # state: interrupted | status: failed | outcome: failed | runtime: 0.1
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
- # 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
+ # 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 Chain object to a hash representation.
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
- # 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.
15
+ # @param chain [Chain] the chain instance to serialize
101
16
  #
102
- # @param chain [CMDx::Chain] The chain object to serialize
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
- # @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
19
+ # @raise [NoMethodError] if chain doesn't respond to required methods
137
20
  #
138
- # chain = ParentTask.call.chain
21
+ # @example Serializing a workflow chain
22
+ # chain = UserWorkflow.call(user_id: 123)
139
23
  # ChainSerializer.call(chain)
140
24
  # # => {
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: []
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
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Registry for managing type coercion definitions and execution within tasks.
5
+ #
6
+ # This registry handles the registration and execution of coercions that convert
7
+ # parameter values from one type to another, supporting both built-in types and
8
+ # custom coercion logic.
9
+ class CoercionRegistry
10
+
11
+ # The internal hash storing coercion definitions.
12
+ #
13
+ # @return [Hash] hash containing coercion type keys and coercion class/callable values
14
+ attr_reader :registry
15
+
16
+ # Initializes a new coercion registry with default type coercions.
17
+ #
18
+ # Sets up the registry with built-in coercions for standard Ruby types
19
+ # including primitives, numerics, dates, and collections.
20
+ #
21
+ # @return [CoercionRegistry] a new coercion registry instance
22
+ #
23
+ # @example Creating a new registry
24
+ # registry = CoercionRegistry.new
25
+ # registry.registry[:string] # => Coercions::String
26
+ def initialize
27
+ @registry = {
28
+ array: Coercions::Array,
29
+ big_decimal: Coercions::BigDecimal,
30
+ boolean: Coercions::Boolean,
31
+ complex: Coercions::Complex,
32
+ date: Coercions::Date,
33
+ datetime: Coercions::DateTime,
34
+ float: Coercions::Float,
35
+ hash: Coercions::Hash,
36
+ integer: Coercions::Integer,
37
+ rational: Coercions::Rational,
38
+ string: Coercions::String,
39
+ time: Coercions::Time,
40
+ virtual: Coercions::Virtual
41
+ }
42
+ end
43
+
44
+ # Registers a custom coercion for a specific type.
45
+ #
46
+ # @param type [Symbol] the type identifier for the coercion
47
+ # @param coercion [Object] the coercion callable (class, proc, symbol, or string)
48
+ #
49
+ # @return [CoercionRegistry] returns self for method chaining
50
+ #
51
+ # @example Registering a custom coercion class
52
+ # registry.register(:uuid, UUIDCoercion)
53
+ #
54
+ # @example Registering a proc coercion
55
+ # registry.register(:upcase, ->(value, options) { value.to_s.upcase })
56
+ #
57
+ # @example Chaining registrations
58
+ # registry.register(:custom1, MyCoercion).register(:custom2, AnotherCoercion)
59
+ def register(type, coercion)
60
+ registry[type] = coercion
61
+ self
62
+ end
63
+
64
+ # Executes a coercion for the specified type and value.
65
+ #
66
+ # @param task [Task] the task instance executing the coercion
67
+ # @param type [Symbol] the coercion type to execute
68
+ # @param value [Object] the value to be coerced
69
+ # @param options [Hash] additional options for the coercion
70
+ #
71
+ # @return [Object] the coerced value
72
+ #
73
+ # @raise [UnknownCoercionError] when the coercion type is not registered
74
+ #
75
+ # @example Coercing a string to integer
76
+ # registry.call(task, :integer, "42")
77
+ # # => 42
78
+ #
79
+ # @example Coercing with options
80
+ # registry.call(task, :array, "a,b,c", delimiter: ",")
81
+ # # => ["a", "b", "c"]
82
+ def call(task, type, value, options = {})
83
+ raise UnknownCoercionError, "unknown coercion #{type}" unless registry.key?(type)
84
+
85
+ case coercion = registry[type]
86
+ when Symbol, String, Proc
87
+ task.cmdx_try(coercion, value, options)
88
+ else
89
+ coercion.call(value, options)
90
+ end
91
+ end
92
+
93
+ end
94
+ 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