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
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ # Parameter evaluation system for task execution context.
5
+ #
6
+ # ParameterEvaluator processes parameter definitions by extracting values from
7
+ # task context sources, applying type coercions, performing validations, and
8
+ # handling optional parameters with default values. It ensures parameter values
9
+ # meet the requirements defined in parameter specifications before task execution.
10
+ class ParameterEvaluator
11
+
12
+ cmdx_attr_delegator :parent, :method_source, :name, :options, :required?, :optional?, :type,
13
+ to: :parameter,
14
+ private: true
15
+
16
+ # @return [CMDx::Task] The task instance being processed
17
+ attr_reader :task
18
+
19
+ # @return [CMDx::Parameter] The parameter definition being processed
20
+ attr_reader :parameter
21
+
22
+ # Creates a new parameter evaluator instance.
23
+ #
24
+ # @param task [CMDx::Task] the task instance containing parameter context
25
+ # @param parameter [CMDx::Parameter] the parameter definition to evaluate
26
+ #
27
+ # @example Create evaluator for a task parameter
28
+ # evaluator = ParameterEvaluator.new(task, parameter)
29
+ def initialize(task, parameter)
30
+ @task = task
31
+ @parameter = parameter
32
+ end
33
+
34
+ # Evaluates a parameter by creating a new evaluator instance and calling it.
35
+ #
36
+ # @param task [CMDx::Task] the task instance containing parameter context
37
+ # @param parameter [CMDx::Parameter] the parameter definition to evaluate
38
+ #
39
+ # @return [Object] the coerced and validated parameter value
40
+ #
41
+ # @raise [ValidationError] when parameter source is undefined or required parameter is missing
42
+ # @raise [CoercionError] when parameter value cannot be coerced to expected type
43
+ #
44
+ # @example Evaluate a parameter value
45
+ # value = ParameterEvaluator.call(task, parameter)
46
+ def self.call(task, parameter)
47
+ new(task, parameter).call
48
+ end
49
+
50
+ # Evaluates the parameter by applying coercion and validation.
51
+ #
52
+ # @return [Object] the coerced and validated parameter value
53
+ #
54
+ # @raise [ValidationError] when parameter source is undefined or required parameter is missing
55
+ # @raise [CoercionError] when parameter value cannot be coerced to expected type
56
+ #
57
+ # @example Evaluate parameter with coercion and validation
58
+ # evaluator = ParameterEvaluator.new(task, parameter)
59
+ # value = evaluator.call
60
+ def call
61
+ coerce!.tap { validate! }
62
+ end
63
+
64
+ private
65
+
66
+ # Checks if the parameter source method is defined on the task.
67
+ #
68
+ # @return [Boolean] true if the source method exists, false otherwise
69
+ #
70
+ # @example Check if parameter source is defined
71
+ # evaluator.send(:source_defined?) #=> true
72
+ def source_defined?
73
+ task.respond_to?(method_source, true) || task.cmdx_try(method_source)
74
+ end
75
+
76
+ # Retrieves the parameter source object from the task.
77
+ #
78
+ # @return [Object] the source object containing parameter values
79
+ #
80
+ # @raise [ValidationError] when the source method is not defined on the task
81
+ #
82
+ # @example Get parameter source
83
+ # evaluator.send(:source) #=> #<Context:...>
84
+ def source
85
+ return @source if defined?(@source)
86
+
87
+ unless source_defined?
88
+ raise ValidationError, I18n.t(
89
+ "cmdx.parameters.undefined",
90
+ default: "delegates to undefined method #{method_source}",
91
+ source: method_source
92
+ )
93
+ end
94
+
95
+ @source = task.cmdx_try(method_source)
96
+ end
97
+
98
+ # Checks if the parameter value exists in the source object.
99
+ #
100
+ # @return [Boolean] true if the parameter value exists, false otherwise
101
+ #
102
+ # @example Check if parameter value exists
103
+ # evaluator.send(:source_value?) #=> true
104
+ def source_value?
105
+ return false if source.nil?
106
+
107
+ source.cmdx_respond_to?(name, true)
108
+ end
109
+
110
+ # Checks if a required parameter value is missing from the source.
111
+ #
112
+ # @return [Boolean] true if required parameter is missing, false otherwise
113
+ #
114
+ # @example Check if required parameter is missing
115
+ # evaluator.send(:source_value_required?) #=> false
116
+ def source_value_required?
117
+ return false if parent&.optional? && source.nil?
118
+
119
+ required? && !source_value?
120
+ end
121
+
122
+ # Extracts the parameter value from the source with default handling.
123
+ #
124
+ # @return [Object] the parameter value or default value
125
+ #
126
+ # @raise [ValidationError] when a required parameter is missing
127
+ #
128
+ # @example Get parameter value with default
129
+ # evaluator.send(:value) #=> "default_value"
130
+ def value
131
+ return @value if defined?(@value)
132
+
133
+ if source_value_required?
134
+ raise ValidationError, I18n.t(
135
+ "cmdx.parameters.required",
136
+ default: "is a required parameter"
137
+ )
138
+ end
139
+
140
+ @value = source.cmdx_try(name)
141
+ return @value unless @value.nil? && options.key?(:default)
142
+
143
+ @value = task.cmdx_yield(options[:default])
144
+ end
145
+
146
+ # Applies type coercion to the parameter value.
147
+ #
148
+ # @return [Object] the coerced parameter value
149
+ #
150
+ # @raise [CoercionError] when value cannot be coerced to expected type
151
+ #
152
+ # @example Coerce parameter value
153
+ # evaluator.send(:coerce!) #=> 42
154
+ def coerce!
155
+ types = Array(type)
156
+ tsize = types.size - 1
157
+
158
+ types.each_with_index do |key, i|
159
+ break CMDx.configuration.coercions.call(task, key, value, options)
160
+ rescue CoercionError => e
161
+ next if tsize != i
162
+
163
+ raise(e) if tsize.zero?
164
+
165
+ values = types.map(&:to_s).join(", ")
166
+ raise CoercionError, I18n.t(
167
+ "cmdx.coercions.into_any",
168
+ values:,
169
+ default: "could not coerce into one of: #{values}"
170
+ )
171
+ end
172
+ end
173
+
174
+ # Checks if validations should be skipped for optional missing arguments.
175
+ #
176
+ # @return [Boolean] true if validations should be skipped, false otherwise
177
+ #
178
+ # @example Check if validations should be skipped
179
+ # evaluator.send(:skip_validations_due_to_optional_missing_argument?) #=> false
180
+ def skip_validations_due_to_optional_missing_argument?
181
+ optional? && value.nil? && !source.nil? && !source.cmdx_respond_to?(name, true)
182
+ end
183
+
184
+ # Checks if validator should be skipped due to conditional options.
185
+ #
186
+ # @param opts [Hash] the validator options
187
+ #
188
+ # @return [Boolean] true if validator should be skipped, false otherwise
189
+ #
190
+ # @example Check if validator should be skipped
191
+ # evaluator.send(:skip_validator_due_to_conditional?, :presence) #=> false
192
+ def skip_validator_due_to_conditional?(opts)
193
+ opts.is_a?(Hash) && !task.cmdx_eval(opts)
194
+ end
195
+
196
+ # Checks if validator should be skipped due to allow_nil option.
197
+ #
198
+ # @param opts [Symbol] the validator options
199
+ #
200
+ # @return [Boolean] true if validator should be skipped, false otherwise
201
+ #
202
+ # @example Check if validator should be skipped for nil
203
+ # evaluator.send(:skip_validator_due_to_allow_nil?, :presence) #=> true
204
+ def skip_validator_due_to_allow_nil?(opts)
205
+ opts.is_a?(Hash) && opts[:allow_nil] && value.nil?
206
+ end
207
+
208
+ # Applies all configured validations to the parameter value.
209
+ #
210
+ # @return [void]
211
+ #
212
+ # @raise [ValidationError] when parameter value fails validation
213
+ #
214
+ # @example Validate parameter value
215
+ # evaluator.send(:validate!)
216
+ def validate!
217
+ return if skip_validations_due_to_optional_missing_argument?
218
+
219
+ types = CMDx.configuration.validators.registry.keys
220
+
221
+ options.slice(*types).each_key do |key|
222
+ opts = options[key]
223
+ next if skip_validator_due_to_allow_nil?(opts)
224
+ next if skip_validator_due_to_conditional?(opts)
225
+
226
+ CMDx.configuration.validators.call(task, key, value, opts)
227
+ end
228
+ end
229
+
230
+ end
231
+ end
@@ -1,75 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Parameter inspection utility for generating human-readable parameter descriptions.
4
+ # Provides formatted inspection and display functionality for parameter objects.
5
5
  #
6
- # The ParameterInspector module provides functionality to convert parameter
7
- # hash representations into formatted, human-readable strings. It handles
8
- # nested parameter structures with proper indentation and ordering.
9
- #
10
- # @example Basic parameter inspection
11
- # parameter_hash = {
12
- # name: :user_id,
13
- # type: :integer,
14
- # source: :context,
15
- # required: true,
16
- # options: { numeric: { min: 1 } },
17
- # children: []
18
- # }
19
- # ParameterInspector.call(parameter_hash)
20
- # # => "Parameter: name=user_id type=integer source=context required=true options={numeric: {min: 1}}"
21
- #
22
- # @example Nested parameter inspection
23
- # nested_parameter = {
24
- # name: :address,
25
- # type: :virtual,
26
- # source: :context,
27
- # required: true,
28
- # options: {},
29
- # children: [
30
- # { name: :street, type: :string, source: :address, required: true, options: {}, children: [] }
31
- # ]
32
- # }
33
- # ParameterInspector.call(nested_parameter)
34
- # # => "Parameter: name=address type=virtual source=context required=true options={}
35
- # # ↳ Parameter: name=street type=string source=address required=true options={}"
36
- #
37
- # @see CMDx::Parameter Parameter hash serialization via to_h
38
- # @see CMDx::ParameterSerializer Parameter-to-hash conversion
6
+ # This module formats parameter information into human-readable string representations,
7
+ # including nested parameter structures with proper indentation. It processes parameter
8
+ # hashes in a consistent order and handles child parameter relationships for complex
9
+ # parameter hierarchies.
39
10
  module ParameterInspector
40
11
 
41
- # Ordered keys for consistent parameter inspection output.
42
- #
43
- # Defines the order in which parameter attributes are displayed
44
- # in the inspection string, with children handled specially.
45
12
  ORDERED_KEYS = %i[
46
13
  name type source required options children
47
14
  ].freeze
48
15
 
49
16
  module_function
50
17
 
51
- # Converts a parameter hash to a human-readable string representation.
18
+ # Formats a parameter hash into a human-readable inspection string.
52
19
  #
53
- # Formats parameter data into a structured string with proper ordering
54
- # and indentation for nested parameters. Child parameters are displayed
55
- # with increased indentation and arrow prefixes.
20
+ # Creates a formatted string representation of parameter information,
21
+ # displaying attributes in a consistent order with proper indentation
22
+ # for nested child parameters. The method recursively processes child
23
+ # parameters with increased indentation depth for visual hierarchy.
56
24
  #
57
- # @param parameter [Hash] The parameter hash to inspect
58
- # @param depth [Integer] The current nesting depth for indentation (default: 1)
59
- # @return [String] Formatted parameter description
25
+ # @param parameter [Hash] the parameter hash to format
26
+ # @option parameter [Symbol, String] :name the parameter name
27
+ # @option parameter [Symbol, Array<Symbol>] :type the parameter type(s)
28
+ # @option parameter [Symbol] :source the parameter source context
29
+ # @option parameter [Boolean] :required whether the parameter is required
30
+ # @option parameter [Hash] :options additional parameter configuration options
31
+ # @option parameter [Array<Hash>] :children nested child parameter definitions
32
+ # @param depth [Integer] the indentation depth for nested parameters (defaults to 1)
60
33
  #
61
- # @example Single parameter inspection
62
- # ParameterInspector.call(param_hash)
63
- # # => "Parameter: name=user_id type=integer source=context required=true"
34
+ # @return [String] formatted multi-line string representation of the parameter
64
35
  #
65
- # @example Nested parameter inspection with custom depth
66
- # ParameterInspector.call(param_hash, 2)
67
- # # => "Parameter: name=address type=virtual source=context required=true
68
- # # ↳ Parameter: name=street type=string source=address required=true"
36
+ # @example Format a simple parameter
37
+ # parameter = { name: :user_id, type: :integer, required: true }
38
+ # ParameterInspector.call(parameter)
39
+ # #=> "Parameter: name=user_id type=integer required=true"
69
40
  #
70
- # @example Parameter with options
71
- # ParameterInspector.call(param_with_validation)
72
- # # => "Parameter: name=email type=string source=context required=true options={format: {with: /@/}}"
41
+ # @example Format a parameter with children
42
+ # parameter = {
43
+ # name: :payment,
44
+ # type: :hash,
45
+ # required: true,
46
+ # children: [
47
+ # { name: :amount, type: :big_decimal, required: true },
48
+ # { name: :currency, type: :string, required: true }
49
+ # ]
50
+ # }
51
+ # ParameterInspector.call(parameter)
52
+ # #=> "Parameter: name=payment type=hash required=true
53
+ # # ↳ Parameter: name=amount type=big_decimal required=true
54
+ # # ↳ Parameter: name=currency type=string required=true"
73
55
  def call(parameter, depth = 1)
74
56
  ORDERED_KEYS.filter_map do |key|
75
57
  value = parameter[key]
@@ -1,124 +1,105 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Parameter collection class for managing multiple parameter definitions.
4
+ # Registry for managing parameter definitions within tasks.
5
5
  #
6
- # The ParameterRegistry class extends Array to provide specialized functionality for
7
- # managing collections of Parameter instances within CMDx tasks. It handles
8
- # validation coordination, serialization, and inspection of parameter groups.
9
- #
10
- # @example Basic parameter collection usage
11
- # parameter_registry = ParameterRegistry.new
12
- # parameter_registry << Parameter.new(:user_id, klass: Task, type: :integer)
13
- # parameter_registry << Parameter.new(:email, klass: Task, type: :string)
14
- # parameter_registry.valid? # => true (if all parameters are valid)
15
- #
16
- # @example Parameter collection validation
17
- # parameter_registry.validate!(task_instance) # Validates all parameters
18
- # parameter_registry.invalid? # => true if any parameter failed validation
19
- #
20
- # @example Parameter collection serialization
21
- # parameter_registry.to_h # => Array of parameter hash representations
22
- # parameter_registry.to_s # => Human-readable parameter descriptions
23
- #
24
- # @see CMDx::Parameter Individual parameter definitions
25
- # @see CMDx::Task Task parameter integration
26
- class ParameterRegistry < Array
6
+ # This registry maintains a collection of parameter definitions and provides
7
+ # validation functionality to ensure all parameters are properly configured
8
+ # and accessible on their associated tasks. It supports both flat and nested
9
+ # parameter structures through recursive validation.
10
+ class ParameterRegistry
27
11
 
28
- # Checks if any parameters in the collection are invalid.
12
+ # @return [Array<Parameter>] array containing parameter definition objects
13
+ attr_reader :registry
14
+
15
+ # Initializes a new parameter registry with an empty parameter collection.
29
16
  #
30
- # @return [Boolean] true if any parameter has validation errors, false otherwise
17
+ # @return [ParameterRegistry] a new parameter registry instance
31
18
  #
32
- # @example
33
- # parameter_registry.invalid? # => true if validation errors exist
34
- def invalid?
35
- !valid?
19
+ # @example Creating a new registry
20
+ # registry = ParameterRegistry.new
21
+ # registry.registry #=> []
22
+ def initialize
23
+ @registry = []
36
24
  end
37
25
 
38
- # Checks if all parameters in the collection are valid.
26
+ # Creates a duplicate of the parameter registry with deep-copied parameters.
27
+ #
28
+ # This method creates a new registry instance with duplicated parameter
29
+ # definitions, ensuring changes to the duplicate don't affect the original.
30
+ #
31
+ # @return [ParameterRegistry] a new registry instance with duplicated parameters
32
+ #
33
+ # @example Duplicate a registry
34
+ # original = ParameterRegistry.new
35
+ # duplicate = original.dup
36
+ # duplicate.object_id != original.object_id #=> true
37
+ def dup
38
+ new_registry = self.class.new
39
+ new_registry.instance_variable_set(:@registry, registry.map(&:dup))
40
+ new_registry
41
+ end
42
+
43
+ # Checks if all parameters in the registry are valid.
39
44
  #
40
45
  # @return [Boolean] true if all parameters are valid, false otherwise
41
46
  #
42
- # @example
43
- # parameter_registry.valid? # => true if no validation errors exist
47
+ # @example Check registry validity
48
+ # registry.valid? #=> true
44
49
  def valid?
45
- all?(&:valid?)
50
+ registry.all?(&:valid?)
46
51
  end
47
52
 
48
- # Validates all parameters in the collection against a task instance.
53
+ # Validates all parameters in the registry against a task instance.
49
54
  #
50
- # Recursively validates each parameter and its children by calling the
51
- # parameter accessor methods on the task instance, which triggers
52
- # value resolution, coercion, and validation.
55
+ # This method ensures that each parameter is properly defined and accessible
56
+ # on the provided task, including nested parameters through recursive validation.
57
+ #
58
+ # @param task [Task] the task instance to validate parameters against
53
59
  #
54
- # @param task [CMDx::Task] The task instance to validate parameters against
55
60
  # @return [void]
56
61
  #
57
- # @example Validating parameters
58
- # task = ProcessOrderTask.new
59
- # parameter_registry.validate!(task) # Validates all parameters
62
+ # @raise [NoMethodError] if a parameter method is not defined on the task
60
63
  #
61
- # @example Validation with nested parameters
62
- # # Validates parent parameters and all nested child parameters
63
- # parameter_registry.validate!(task_with_nested_params)
64
+ # @example Validate parameters against a task
65
+ # registry.validate!(task_instance)
64
66
  def validate!(task)
65
- each { |p| recursive_validate!(task, p) }
67
+ registry.each { |p| recursive_validate!(task, p) }
66
68
  end
67
69
 
68
- # Converts the parameter collection to a hash representation.
69
- #
70
- # Serializes all parameters in the collection to their hash representations
71
- # using the ParametersSerializer.
72
- #
73
- # @return [Array<Hash>] Array of serialized parameter data
74
- #
75
- # @example
76
- # parameter_registry.to_h
77
- # # => [
78
- # # {
79
- # # source: :context,
80
- # # name: :user_id,
81
- # # type: :integer,
82
- # # required: true,
83
- # # options: {},
84
- # # children: []
85
- # # },
86
- # # { ... }
87
- # # ]
70
+ # Converts the parameter registry to a hash representation.
71
+ #
72
+ # @return [Array<Hash>] array of parameter hash representations
73
+ #
74
+ # @example Convert registry to hash
75
+ # registry.to_h #=> [{name: :user_id, type: :integer}, {name: :email, type: :string}]
88
76
  def to_h
89
- ParametersSerializer.call(self)
77
+ registry.map(&:to_h)
90
78
  end
91
- alias to_a to_h
92
79
 
93
- # Converts the parameter collection to a string representation.
94
- #
95
- # Creates a human-readable string representation of all parameters
96
- # in the collection using the ParametersInspector.
80
+ # Converts the parameter registry to a string representation.
97
81
  #
98
- # @return [String] Multi-line parameter descriptions
82
+ # @return [String] string representation of all parameters, joined by newlines
99
83
  #
100
- # @example
101
- # parameter_registry.to_s
102
- # # => "Parameter: name=user_id type=integer source=context required=true
103
- # # Parameter: name=email type=string source=context required=false"
84
+ # @example Convert registry to string
85
+ # registry.to_s #=> "user_id: integer\nemail: string"
104
86
  def to_s
105
- ParametersInspector.call(self)
87
+ registry.map(&:to_s).join("\n")
106
88
  end
107
89
 
108
90
  private
109
91
 
110
- # Recursively validates a parameter and all its children.
92
+ # Recursively validates a parameter and its children against a task.
111
93
  #
112
- # Calls the parameter accessor method on the task to trigger validation,
113
- # then recursively validates all child parameters for nested parameter
114
- # structures.
94
+ # @param task [Task] the task instance to validate the parameter against
95
+ # @param parameter [Parameter] the parameter to validate
115
96
  #
116
- # @param task [CMDx::Task] The task instance to validate against
117
- # @param parameter [CMDx::Parameter] The parameter to validate
118
97
  # @return [void]
98
+ #
99
+ # @raise [NoMethodError] if the parameter method is not defined on the task
119
100
  def recursive_validate!(task, parameter)
120
- task.send(parameter.method_name)
121
- parameter.children.each { |cp| recursive_validate!(task, cp) }
101
+ task.send(parameter.method_name) # Make sure parameter is defined on task
102
+ parameter.children.each { |child| recursive_validate!(task, child) }
122
103
  end
123
104
 
124
105
  end
@@ -1,92 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CMDx
4
- # Parameter serialization utility for converting Parameter objects to hash representations.
4
+ # Parameter serialization utilities for converting parameter objects to hash representations.
5
5
  #
6
- # The ParameterSerializer module provides functionality to serialize Parameter
7
- # instances into structured hash representations suitable for inspection,
8
- # logging, debugging, and data interchange.
9
- #
10
- # @example Basic parameter serialization
11
- # parameter = Parameter.new(:user_id, klass: Task, type: :integer, required: true)
12
- # ParameterSerializer.call(parameter)
13
- # # => {
14
- # # source: :context,
15
- # # name: :user_id,
16
- # # type: :integer,
17
- # # required: true,
18
- # # options: {},
19
- # # children: []
20
- # # }
21
- #
22
- # @example Parameter with validation options
23
- # parameter = Parameter.new(:email, klass: Task, type: :string,
24
- # format: { with: /@/ }, presence: true)
25
- # ParameterSerializer.call(parameter)
26
- # # => {
27
- # # source: :context,
28
- # # name: :email,
29
- # # type: :string,
30
- # # required: false,
31
- # # options: { format: { with: /@/ }, presence: true },
32
- # # children: []
33
- # # }
34
- #
35
- # @example Nested parameter serialization
36
- # parent = Parameter.new(:address, klass: Task) do
37
- # required :street, :city
38
- # end
39
- # ParameterSerializer.call(parent)
40
- # # => {
41
- # # source: :context,
42
- # # name: :address,
43
- # # type: :virtual,
44
- # # required: false,
45
- # # options: {},
46
- # # children: [
47
- # # { source: :address, name: :street, type: :virtual, required: true, options: {}, children: [] },
48
- # # { source: :address, name: :city, type: :virtual, required: true, options: {}, children: [] }
49
- # # ]
50
- # # }
51
- #
52
- # @see CMDx::Parameter Parameter object creation and configuration
53
- # @see CMDx::ParameterInspector Human-readable parameter formatting
6
+ # ParameterSerializer provides functionality to convert parameter definition objects
7
+ # into structured hash format for serialization, introspection, and data exchange.
8
+ # It extracts essential parameter metadata including source context, method names,
9
+ # type information, requirement status, options, and nested child parameters.
54
10
  module ParameterSerializer
55
11
 
56
12
  module_function
57
13
 
58
- # Converts a Parameter object to a hash representation.
14
+ # Converts a parameter object into a hash representation for serialization.
59
15
  #
60
- # Serializes a Parameter instance into a structured hash containing
61
- # all relevant parameter information including source, name, type,
62
- # requirement status, options, and recursively serialized children.
16
+ # This method extracts key metadata from a parameter definition and structures
17
+ # it into a hash format suitable for serialization, storage, or transmission.
18
+ # Child parameters are recursively serialized to maintain nested structure.
63
19
  #
64
- # @param parameter [CMDx::Parameter] The parameter object to serialize
65
- # @return [Hash] Structured hash representation of the parameter
20
+ # @param parameter [CMDx::Parameter] the parameter object to serialize
66
21
  #
67
- # @example Simple parameter serialization
68
- # param = Parameter.new(:age, klass: Task, type: :integer, required: true)
69
- # ParameterSerializer.call(param)
70
- # # => {
71
- # # source: :context,
72
- # # name: :age,
73
- # # type: :integer,
74
- # # required: true,
75
- # # options: {},
76
- # # children: []
77
- # # }
22
+ # @return [Hash] a hash containing the parameter's metadata and configuration
23
+ # @option return [Symbol] :source the source context for parameter resolution
24
+ # @option return [Symbol] :name the method name generated for this parameter
25
+ # @option return [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
26
+ # @option return [Boolean] :required whether the parameter is required for execution
27
+ # @option return [Hash] :options the parameter configuration options
28
+ # @option return [Array<Hash>] :children serialized child parameters for nested structures
78
29
  #
79
- # @example Parameter with custom source and options
80
- # param = Parameter.new(:name, klass: Task, source: :user,
81
- # type: :string, length: { min: 2 })
82
- # ParameterSerializer.call(param)
83
- # # => {
84
- # # source: :user,
85
- # # name: :name,
86
- # # type: :string,
30
+ # @example Serialize a nested parameter with children
31
+ # user_param = Parameter.new(:user, klass: MyTask, type: :hash) do
32
+ # required :name, type: :string
33
+ # optional :age, type: :integer
34
+ # end
35
+ # ParameterSerializer.call(user_param)
36
+ # #=> {
37
+ # # source: :context,
38
+ # # name: :user,
39
+ # # type: :hash,
87
40
  # # required: false,
88
- # # options: { length: { min: 2 } },
89
- # # children: []
41
+ # # options: {},
42
+ # # children: [
43
+ # # { source: :user, name: :name, type: :string, required: true, options: {}, children: [] },
44
+ # # { source: :user, name: :age, type: :integer, required: false, options: {}, children: [] }
45
+ # # ]
90
46
  # # }
91
47
  def call(parameter)
92
48
  {