cmdx 1.1.1 → 1.5.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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +6 -128
  12. data/LLM.md +3317 -0
  13. data/README.md +68 -44
  14. data/docs/attributes/coercions.md +162 -0
  15. data/docs/attributes/defaults.md +90 -0
  16. data/docs/attributes/definitions.md +281 -0
  17. data/docs/attributes/naming.md +78 -0
  18. data/docs/attributes/validations.md +309 -0
  19. data/docs/basics/chain.md +56 -249
  20. data/docs/basics/context.md +56 -289
  21. data/docs/basics/execution.md +114 -0
  22. data/docs/basics/setup.md +37 -334
  23. data/docs/callbacks.md +89 -467
  24. data/docs/deprecation.md +91 -174
  25. data/docs/getting_started.md +212 -202
  26. data/docs/internationalization.md +11 -647
  27. data/docs/interruptions/exceptions.md +23 -198
  28. data/docs/interruptions/faults.md +71 -151
  29. data/docs/interruptions/halt.md +109 -186
  30. data/docs/logging.md +44 -256
  31. data/docs/middlewares.md +113 -426
  32. data/docs/outcomes/result.md +81 -228
  33. data/docs/outcomes/states.md +33 -221
  34. data/docs/outcomes/statuses.md +21 -311
  35. data/docs/tips_and_tricks.md +120 -70
  36. data/docs/workflows.md +99 -283
  37. data/lib/cmdx/.DS_Store +0 -0
  38. data/lib/cmdx/attribute.rb +229 -0
  39. data/lib/cmdx/attribute_registry.rb +94 -0
  40. data/lib/cmdx/attribute_value.rb +193 -0
  41. data/lib/cmdx/callback_registry.rb +69 -77
  42. data/lib/cmdx/chain.rb +56 -73
  43. data/lib/cmdx/coercion_registry.rb +52 -68
  44. data/lib/cmdx/coercions/array.rb +19 -18
  45. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  46. data/lib/cmdx/coercions/boolean.rb +26 -25
  47. data/lib/cmdx/coercions/complex.rb +21 -22
  48. data/lib/cmdx/coercions/date.rb +25 -23
  49. data/lib/cmdx/coercions/date_time.rb +24 -25
  50. data/lib/cmdx/coercions/float.rb +25 -22
  51. data/lib/cmdx/coercions/hash.rb +31 -32
  52. data/lib/cmdx/coercions/integer.rb +30 -24
  53. data/lib/cmdx/coercions/rational.rb +29 -24
  54. data/lib/cmdx/coercions/string.rb +19 -22
  55. data/lib/cmdx/coercions/symbol.rb +37 -0
  56. data/lib/cmdx/coercions/time.rb +26 -25
  57. data/lib/cmdx/configuration.rb +49 -108
  58. data/lib/cmdx/context.rb +222 -44
  59. data/lib/cmdx/deprecator.rb +61 -0
  60. data/lib/cmdx/errors.rb +42 -252
  61. data/lib/cmdx/exceptions.rb +39 -0
  62. data/lib/cmdx/faults.rb +78 -39
  63. data/lib/cmdx/freezer.rb +51 -0
  64. data/lib/cmdx/identifier.rb +30 -0
  65. data/lib/cmdx/locale.rb +52 -0
  66. data/lib/cmdx/log_formatters/json.rb +21 -22
  67. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  68. data/lib/cmdx/log_formatters/line.rb +15 -22
  69. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  70. data/lib/cmdx/log_formatters/raw.rb +16 -22
  71. data/lib/cmdx/middleware_registry.rb +70 -74
  72. data/lib/cmdx/middlewares/correlate.rb +90 -54
  73. data/lib/cmdx/middlewares/runtime.rb +58 -0
  74. data/lib/cmdx/middlewares/timeout.rb +48 -68
  75. data/lib/cmdx/railtie.rb +12 -45
  76. data/lib/cmdx/result.rb +229 -314
  77. data/lib/cmdx/task.rb +194 -366
  78. data/lib/cmdx/utils/call.rb +49 -0
  79. data/lib/cmdx/utils/condition.rb +71 -0
  80. data/lib/cmdx/utils/format.rb +61 -0
  81. data/lib/cmdx/validator_registry.rb +63 -72
  82. data/lib/cmdx/validators/exclusion.rb +38 -67
  83. data/lib/cmdx/validators/format.rb +48 -49
  84. data/lib/cmdx/validators/inclusion.rb +43 -74
  85. data/lib/cmdx/validators/length.rb +91 -154
  86. data/lib/cmdx/validators/numeric.rb +87 -162
  87. data/lib/cmdx/validators/presence.rb +37 -50
  88. data/lib/cmdx/version.rb +1 -1
  89. data/lib/cmdx/worker.rb +178 -0
  90. data/lib/cmdx/workflow.rb +85 -81
  91. data/lib/cmdx.rb +19 -13
  92. data/lib/generators/cmdx/install_generator.rb +14 -13
  93. data/lib/generators/cmdx/task_generator.rb +25 -50
  94. data/lib/generators/cmdx/templates/install.rb +11 -46
  95. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  96. data/lib/locales/en.yml +18 -4
  97. data/src/cmdx-logo.png +0 -0
  98. metadata +32 -116
  99. data/docs/ai_prompts.md +0 -393
  100. data/docs/basics/call.md +0 -317
  101. data/docs/configuration.md +0 -344
  102. data/docs/parameters/coercions.md +0 -396
  103. data/docs/parameters/defaults.md +0 -335
  104. data/docs/parameters/definitions.md +0 -446
  105. data/docs/parameters/namespacing.md +0 -378
  106. data/docs/parameters/validations.md +0 -405
  107. data/docs/testing.md +0 -553
  108. data/lib/cmdx/callback.rb +0 -53
  109. data/lib/cmdx/chain_inspector.rb +0 -56
  110. data/lib/cmdx/chain_serializer.rb +0 -63
  111. data/lib/cmdx/coercion.rb +0 -57
  112. data/lib/cmdx/coercions/virtual.rb +0 -29
  113. data/lib/cmdx/core_ext/hash.rb +0 -83
  114. data/lib/cmdx/core_ext/module.rb +0 -98
  115. data/lib/cmdx/core_ext/object.rb +0 -125
  116. data/lib/cmdx/correlator.rb +0 -122
  117. data/lib/cmdx/error.rb +0 -60
  118. data/lib/cmdx/fault.rb +0 -140
  119. data/lib/cmdx/immutator.rb +0 -52
  120. data/lib/cmdx/lazy_struct.rb +0 -246
  121. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  122. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  123. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  124. data/lib/cmdx/logger.rb +0 -49
  125. data/lib/cmdx/logger_ansi.rb +0 -68
  126. data/lib/cmdx/logger_serializer.rb +0 -116
  127. data/lib/cmdx/middleware.rb +0 -70
  128. data/lib/cmdx/parameter.rb +0 -312
  129. data/lib/cmdx/parameter_evaluator.rb +0 -231
  130. data/lib/cmdx/parameter_inspector.rb +0 -66
  131. data/lib/cmdx/parameter_registry.rb +0 -106
  132. data/lib/cmdx/parameter_serializer.rb +0 -59
  133. data/lib/cmdx/result_ansi.rb +0 -71
  134. data/lib/cmdx/result_inspector.rb +0 -71
  135. data/lib/cmdx/result_logger.rb +0 -59
  136. data/lib/cmdx/result_serializer.rb +0 -104
  137. data/lib/cmdx/rspec/matchers.rb +0 -28
  138. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  139. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  141. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  142. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  143. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  144. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  145. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  146. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  147. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  148. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  149. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  150. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  151. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  152. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  153. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  154. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  155. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  156. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  157. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  158. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  159. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  160. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  161. data/lib/cmdx/task_deprecator.rb +0 -52
  162. data/lib/cmdx/task_processor.rb +0 -246
  163. data/lib/cmdx/task_serializer.rb +0 -57
  164. data/lib/cmdx/utils/ansi_color.rb +0 -73
  165. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  166. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  167. data/lib/cmdx/utils/name_affix.rb +0 -52
  168. data/lib/cmdx/validator.rb +0 -57
  169. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  170. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  171. data/lib/locales/ar.yml +0 -35
  172. data/lib/locales/cs.yml +0 -35
  173. data/lib/locales/da.yml +0 -35
  174. data/lib/locales/de.yml +0 -35
  175. data/lib/locales/el.yml +0 -35
  176. data/lib/locales/es.yml +0 -35
  177. data/lib/locales/fi.yml +0 -35
  178. data/lib/locales/fr.yml +0 -35
  179. data/lib/locales/he.yml +0 -35
  180. data/lib/locales/hi.yml +0 -35
  181. data/lib/locales/it.yml +0 -35
  182. data/lib/locales/ja.yml +0 -35
  183. data/lib/locales/ko.yml +0 -35
  184. data/lib/locales/nl.yml +0 -35
  185. data/lib/locales/no.yml +0 -35
  186. data/lib/locales/pl.yml +0 -35
  187. data/lib/locales/pt.yml +0 -35
  188. data/lib/locales/ru.yml +0 -35
  189. data/lib/locales/sv.yml +0 -35
  190. data/lib/locales/th.yml +0 -35
  191. data/lib/locales/tr.yml +0 -35
  192. data/lib/locales/vi.yml +0 -35
  193. data/lib/locales/zh.yml +0 -35
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Logger serialization module for converting messages and task data into structured log format.
5
- #
6
- # LoggerSerializer provides functionality to serialize task execution messages into a
7
- # standardized hash representation suitable for logging systems. It handles both result
8
- # objects and arbitrary messages, applying consistent formatting with optional ANSI
9
- # colorization for terminal output. The serializer intelligently processes different
10
- # message types and enriches log data with task metadata and origin information.
11
- module LoggerSerializer
12
-
13
- COLORED_KEYS = %i[
14
- state status outcome
15
- ].freeze
16
-
17
- module_function
18
-
19
- # Serializes a log message with task context into structured hash format.
20
- #
21
- # Converts log messages into a standardized hash representation suitable for
22
- # various logging systems and output formats. When the message is a Result object,
23
- # it extracts the result's hash representation and optionally applies ANSI colors
24
- # to specific keys for enhanced terminal visibility. For non-result messages,
25
- # it enriches the log entry with task metadata from TaskSerializer. All log
26
- # entries are tagged with CMDx origin for source identification.
27
- #
28
- # @param severity [Symbol] the log severity level (not used in current implementation)
29
- # @param time [Time] the timestamp of the log entry (not used in current implementation)
30
- # @param task [CMDx::Task, CMDx::Workflow] the task or workflow instance providing context
31
- # @param message [CMDx::Result, Object] the primary message content to serialize
32
- # @param options [Hash] additional options for serialization behavior
33
- # @option options [Boolean] :ansi_colorize whether to apply ANSI colors to result keys
34
- #
35
- # @return [Hash] a structured hash containing the serialized log message and metadata
36
- # @option return [String] :origin always set to "CMDx" for source identification
37
- # @option return [Integer] :index the task's position index in the execution chain (when message is not Result)
38
- # @option return [String] :chain_id the unique identifier of the task's execution chain (when message is not Result)
39
- # @option return [String] :type the task type, either "Task" or "Workflow" (when message is not Result)
40
- # @option return [String] :class the full class name of the task (when message is not Result)
41
- # @option return [String] :id the unique identifier of the task instance (when message is not Result)
42
- # @option return [Array] :tags the tags associated with the task from cmd settings (when message is not Result)
43
- # @option return [Object] :message the original message content (when message is not Result)
44
- # @option return [Symbol] :state the execution state with optional ANSI colors (when message is Result)
45
- # @option return [Symbol] :status the execution status with optional ANSI colors (when message is Result)
46
- # @option return [Symbol] :outcome the execution outcome with optional ANSI colors (when message is Result)
47
- # @option return [Hash] :metadata additional metadata from result (when message is Result)
48
- # @option return [Float] :runtime execution runtime in seconds (when message is Result)
49
- #
50
- # @raise [NoMethodError] if task doesn't respond to required methods for TaskSerializer
51
- # @raise [NoMethodError] if result message doesn't respond to to_h method
52
- #
53
- # @example Serialize a result message with ANSI colors
54
- # task = ProcessDataTask.call(data: "test")
55
- # LoggerSerializer.call(:info, Time.now, task, task.result, ansi_colorize: true)
56
- # #=> {
57
- # # origin: "CMDx",
58
- # # index: 0,
59
- # # chain_id: "abc123",
60
- # # type: "Task",
61
- # # class: "ProcessDataTask",
62
- # # id: "def456",
63
- # # tags: [],
64
- # # state: "\e[0;32;49mcomplete\e[0m",
65
- # # status: "\e[0;32;49msuccess\e[0m",
66
- # # outcome: "\e[0;32;49mgood\e[0m",
67
- # # metadata: {},
68
- # # runtime: 0.045
69
- # # }
70
- #
71
- # @example Serialize a string message with task context
72
- # task = MyTask.new(context: {data: "test"})
73
- # LoggerSerializer.call(:warn, Time.now, task, "Processing started")
74
- # #=> {
75
- # # origin: "CMDx",
76
- # # index: 0,
77
- # # chain_id: "abc123",
78
- # # type: "Task",
79
- # # class: "MyTask",
80
- # # id: "def456",
81
- # # tags: [],
82
- # # message: "Processing started"
83
- # # }
84
- #
85
- # @example Serialize a result message without colors
86
- # task = ValidationTask.call(email: "invalid")
87
- # LoggerSerializer.call(:error, Time.now, task, task.result)
88
- # #=> {
89
- # # origin: "CMDx",
90
- # # index: 1,
91
- # # chain_id: "xyz789",
92
- # # type: "Task",
93
- # # class: "ValidationTask",
94
- # # id: "ghi012",
95
- # # tags: [],
96
- # # state: :interrupted,
97
- # # status: :failed,
98
- # # outcome: :bad,
99
- # # metadata: { reason: "Invalid email format" },
100
- # # runtime: 0.012
101
- # # }
102
- def call(severity, time, task, message, **options) # rubocop:disable Lint/UnusedMethodArgument
103
- m = message.is_a?(Result) ? message.to_h : {}
104
-
105
- if options.delete(:ansi_colorize) && message.is_a?(Result)
106
- COLORED_KEYS.each { |k| m[k] = ResultAnsi.call(m[k]) if m.key?(k) }
107
- elsif !message.is_a?(Result)
108
- m.merge!(TaskSerializer.call(task), message: message)
109
- end
110
-
111
- m[:origin] ||= "CMDx"
112
- m
113
- end
114
-
115
- end
116
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Base class for implementing middleware functionality in task processing pipelines.
5
- #
6
- # Middleware provides a way to wrap task execution with custom logic that runs before
7
- # and after task processing. Middleware can be used for cross-cutting concerns such as
8
- # logging, authentication, caching, error handling, and other aspects that should be
9
- # applied consistently across multiple tasks. All middleware implementations must
10
- # inherit from this class and implement the abstract call method.
11
- class Middleware
12
-
13
- # Executes middleware by creating a new instance and calling it.
14
- #
15
- # This class method provides a convenient way to execute middleware without
16
- # manually instantiating the middleware class. It creates a new instance
17
- # and delegates to the instance call method with the provided arguments.
18
- #
19
- # @param task [CMDx::Task] the task instance being processed
20
- # @param callable [Proc] the callable that executes the next middleware or task logic
21
- #
22
- # @return [Object] the result returned by the middleware implementation
23
- #
24
- # @raise [UndefinedCallError] when the middleware subclass doesn't implement call
25
- #
26
- # @example Execute middleware on a task
27
- # class LoggingMiddleware < CMDx::Middleware
28
- # def call(task, callable)
29
- # task.logger.info "Starting #{task.class.name}"
30
- # result = callable.call
31
- # task.logger.info "Completed #{task.class.name}"
32
- # result
33
- # end
34
- # end
35
- #
36
- # LoggingMiddleware.call(my_task, -> { my_task.process })
37
- def self.call(task, callable)
38
- new.call(task, callable)
39
- end
40
-
41
- # Abstract method that must be implemented by middleware subclasses.
42
- #
43
- # This method contains the actual middleware logic that wraps task execution.
44
- # Subclasses must override this method to provide their specific middleware
45
- # implementation. The method should call the provided callable to continue
46
- # the middleware chain or execute the task logic.
47
- #
48
- # @param _task [CMDx::Task] the task instance being processed
49
- # @param _callable [Proc] the callable that executes the next middleware or task logic
50
- #
51
- # @return [Object] the result of the middleware processing
52
- #
53
- # @raise [UndefinedCallError] always raised in the base class
54
- #
55
- # @example Implement middleware in a subclass
56
- # class TimingMiddleware < CMDx::Middleware
57
- # def call(task, callable)
58
- # start_time = Time.now
59
- # result = callable.call
60
- # duration = Time.now - start_time
61
- # task.logger.info "Task completed in #{duration}s"
62
- # result
63
- # end
64
- # end
65
- def call(_task, _callable)
66
- raise UndefinedCallError, "call method not defined in #{self.class.name}"
67
- end
68
-
69
- end
70
- end
@@ -1,312 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Parameter definition and management for task attribute configuration.
5
- #
6
- # Parameter provides a flexible system for defining, validating, and managing
7
- # task parameters with support for type coercion, nested parameter structures,
8
- # validation rules, and dynamic attribute generation. Parameters can be defined
9
- # as required or optional with various configuration options including custom
10
- # naming, source specification, and child parameter definitions.
11
- class Parameter
12
-
13
- cmdx_attr_delegator :invalid?, :valid?,
14
- to: :errors
15
-
16
- # @return [CMDx::Task] The task class this parameter belongs to
17
- attr_accessor :task
18
-
19
- # @return [Class] The task class this parameter is defined in
20
- attr_reader :klass
21
-
22
- # @return [Parameter, nil] The parent parameter for nested parameters
23
- attr_reader :parent
24
-
25
- # @return [Symbol] The parameter name
26
- attr_reader :name
27
-
28
- # @return [Symbol, Array<Symbol>] The parameter type(s) for coercion
29
- attr_reader :type
30
-
31
- # @return [Hash] The parameter configuration options
32
- attr_reader :options
33
-
34
- # @return [Array<Parameter>] Child parameters for nested parameter definitions
35
- attr_reader :children
36
-
37
- # @return [CMDx::Errors] Validation errors for this parameter
38
- attr_reader :errors
39
-
40
- # Creates a new parameter definition with the specified configuration.
41
- #
42
- # @param name [Symbol, String] the parameter name
43
- # @param options [Hash] parameter configuration options
44
- # @option options [Class] :klass the task class this parameter belongs to (required)
45
- # @option options [Parameter] :parent the parent parameter for nested definitions
46
- # @option options [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
47
- # @option options [Boolean] :required whether the parameter is required for task execution
48
- # @option options [Symbol] :source the source context for parameter resolution
49
- # @option options [Symbol, String] :as custom method name for the parameter
50
- # @option options [Hash] :validates validation rules to apply to the parameter
51
- # @option options [Object] :default default value when parameter is not provided
52
- # @param block [Proc] optional block for defining nested parameters
53
- #
54
- # @return [Parameter] a new parameter instance
55
- #
56
- # @raise [KeyError] if the :klass option is not provided
57
- #
58
- # @example Create a simple required parameter
59
- # Parameter.new(:user_id, klass: MyTask, type: :integer, required: true)
60
- #
61
- # @example Create parameter with validation
62
- # Parameter.new(:email, klass: MyTask, type: :string, validates: { format: /@/ })
63
- #
64
- # @example Create nested parameter with children
65
- # Parameter.new(:user, klass: MyTask, type: :hash) do
66
- # required :name, type: :string
67
- # optional :age, type: :integer
68
- # end
69
- def initialize(name, **options, &)
70
- @klass = options.delete(:klass) || raise(KeyError, "klass option required")
71
- @parent = options.delete(:parent)
72
- @type = options.delete(:type) || :virtual
73
- @required = options.delete(:required) || false
74
-
75
- @name = name
76
- @options = options
77
- @children = []
78
- @errors = Errors.new
79
-
80
- define_attribute(self)
81
- instance_eval(&) if block_given?
82
- end
83
-
84
- class << self
85
-
86
- # Creates one or more optional parameter definitions.
87
- #
88
- # @param names [Array<Symbol>] parameter names to define as optional
89
- # @param options [Hash] parameter configuration options
90
- # @option options [Class] :klass the task class this parameter belongs to
91
- # @option options [Parameter] :parent the parent parameter for nested definitions
92
- # @option options [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
93
- # @option options [Symbol] :source the source context for parameter resolution
94
- # @option options [Symbol, String] :as custom method name (only allowed for single parameter)
95
- # @option options [Hash] :validates validation rules to apply to the parameter
96
- # @option options [Object] :default default value when parameter is not provided
97
- # @param block [Proc] optional block for defining nested parameters
98
- #
99
- # @return [Array<Parameter>] array of created optional parameter instances
100
- #
101
- # @raise [ArgumentError] if no parameter names are provided
102
- # @raise [ArgumentError] if :as option is used with multiple parameter names
103
- #
104
- # @example Define single optional parameter
105
- # Parameter.optional(:description, klass: MyTask, type: :string)
106
- #
107
- # @example Define multiple optional parameters
108
- # Parameter.optional(:name, :email, klass: MyTask, type: :string)
109
- #
110
- # @example Define optional parameter with custom name
111
- # Parameter.optional(:user_id, klass: MyTask, type: :integer, as: :current_user_id)
112
- def optional(*names, **options, &)
113
- if names.none?
114
- raise ArgumentError, "no parameters given"
115
- elsif !names.one? && options.key?(:as)
116
- raise ArgumentError, ":as option only supports one parameter per definition"
117
- end
118
-
119
- names.filter_map { |n| new(n, **options, &) }
120
- end
121
-
122
- # Creates one or more required parameter definitions.
123
- #
124
- # @param names [Array<Symbol>] parameter names to define as required
125
- # @param options [Hash] parameter configuration options
126
- # @option options [Class] :klass the task class this parameter belongs to
127
- # @option options [Parameter] :parent the parent parameter for nested definitions
128
- # @option options [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
129
- # @option options [Symbol] :source the source context for parameter resolution
130
- # @option options [Symbol, String] :as custom method name (only allowed for single parameter)
131
- # @option options [Hash] :validates validation rules to apply to the parameter
132
- # @option options [Object] :default default value when parameter is not provided
133
- # @param block [Proc] optional block for defining nested parameters
134
- #
135
- # @return [Array<Parameter>] array of created required parameter instances
136
- #
137
- # @raise [ArgumentError] if no parameter names are provided
138
- # @raise [ArgumentError] if :as option is used with multiple parameter names
139
- #
140
- # @example Define single required parameter
141
- # Parameter.required(:user_id, klass: MyTask, type: :integer)
142
- #
143
- # @example Define multiple required parameters
144
- # Parameter.required(:name, :email, klass: MyTask, type: :string)
145
- #
146
- # @example Define required parameter with validation
147
- # Parameter.required(:email, klass: MyTask, type: :string, validates: { format: /@/ })
148
- def required(*names, **options, &)
149
- optional(*names, **options.merge(required: true), &)
150
- end
151
-
152
- end
153
-
154
- # Defines optional child parameters for nested parameter structures.
155
- #
156
- # @param names [Array<Symbol>] parameter names to define as optional children
157
- # @param options [Hash] parameter configuration options
158
- # @option options [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
159
- # @option options [Symbol] :source the source context for parameter resolution
160
- # @option options [Symbol, String] :as custom method name (only allowed for single parameter)
161
- # @option options [Hash] :validates validation rules to apply to the parameter
162
- # @option options [Object] :default default value when parameter is not provided
163
- # @param block [Proc] optional block for defining nested parameters
164
- #
165
- # @return [Array<Parameter>] array of created optional child parameter instances
166
- #
167
- # @raise [ArgumentError] if no parameter names are provided
168
- # @raise [ArgumentError] if :as option is used with multiple parameter names
169
- #
170
- # @example Define optional child parameters
171
- # user_param = Parameter.new(:user, klass: MyTask, type: :hash)
172
- # user_param.optional(:description, :bio, type: :string)
173
- def optional(*names, **options, &)
174
- parameters = Parameter.optional(*names, **options.merge(klass: @klass, parent: self), &)
175
- children.concat(parameters)
176
- end
177
-
178
- # Defines required child parameters for nested parameter structures.
179
- #
180
- # @param names [Array<Symbol>] parameter names to define as required children
181
- # @param options [Hash] parameter configuration options
182
- # @option options [Symbol, Array<Symbol>] :type the parameter type(s) for coercion
183
- # @option options [Symbol] :source the source context for parameter resolution
184
- # @option options [Symbol, String] :as custom method name (only allowed for single parameter)
185
- # @option options [Hash] :validates validation rules to apply to the parameter
186
- # @option options [Object] :default default value when parameter is not provided
187
- # @param block [Proc] optional block for defining nested parameters
188
- #
189
- # @return [Array<Parameter>] array of created required child parameter instances
190
- #
191
- # @raise [ArgumentError] if no parameter names are provided
192
- # @raise [ArgumentError] if :as option is used with multiple parameter names
193
- #
194
- # @example Define required child parameters
195
- # user_param = Parameter.new(:user, klass: MyTask, type: :hash)
196
- # user_param.required(:name, :email, type: :string)
197
- def required(*names, **options, &)
198
- parameters = Parameter.required(*names, **options.merge(klass: @klass, parent: self), &)
199
- children.concat(parameters)
200
- end
201
-
202
- # Checks if the parameter is marked as required for task execution.
203
- #
204
- # @return [Boolean] true if the parameter is required, false otherwise
205
- #
206
- # @example Check if parameter is required
207
- # param = Parameter.new(:name, klass: MyTask, required: true)
208
- # param.required? #=> true
209
- def required?
210
- !!@required
211
- end
212
-
213
- # Checks if the parameter is marked as optional for task execution.
214
- #
215
- # @return [Boolean] true if the parameter is optional, false otherwise
216
- #
217
- # @example Check if parameter is optional
218
- # param = Parameter.new(:description, klass: MyTask, required: false)
219
- # param.optional? #=> true
220
- def optional?
221
- !required?
222
- end
223
-
224
- # Generates the method name that will be created on the task class for this parameter.
225
- #
226
- # @return [Symbol] the method name with any configured prefix, suffix, or custom naming
227
- #
228
- # @example Get method name for simple parameter
229
- # param = Parameter.new(:user_id, klass: MyTask)
230
- # param.method_name #=> :user_id
231
- #
232
- # @example Get method name with custom naming
233
- # param = Parameter.new(:user_id, klass: MyTask, as: :current_user_id)
234
- # param.method_name #=> :current_user_id
235
- def method_name
236
- @method_name ||= Utils::NameAffix.call(name, method_source, options)
237
- end
238
-
239
- # Determines the source context for parameter resolution and method name generation.
240
- #
241
- # @return [Symbol] the source identifier used for parameter resolution
242
- #
243
- # @example Get method source for simple parameter
244
- # param = Parameter.new(:user_id, klass: MyTask)
245
- # param.method_source #=> :context
246
- #
247
- # @example Get method source for nested parameter
248
- # parent = Parameter.new(:user, klass: MyTask)
249
- # child = Parameter.new(:name, klass: MyTask, parent: parent)
250
- # child.method_source #=> :user
251
- def method_source
252
- @method_source ||= options[:source] || parent&.method_name || :context
253
- end
254
-
255
- # Converts the parameter to a hash representation for serialization.
256
- #
257
- # @return [Hash] hash containing all parameter metadata and configuration
258
- #
259
- # @example Convert parameter to hash
260
- # param = Parameter.new(:user_id, klass: MyTask, type: :integer, required: true)
261
- # param.to_h
262
- # #=> { name: :user_id, type: :integer, required: true, ... }
263
- def to_h
264
- ParameterSerializer.call(self)
265
- end
266
-
267
- # Converts the parameter to a formatted string representation for inspection.
268
- #
269
- # @return [String] human-readable string representation of the parameter
270
- #
271
- # @example Convert parameter to string
272
- # param = Parameter.new(:user_id, klass: MyTask, type: :integer, required: true)
273
- # param.to_s
274
- # #=> "Parameter: name=user_id type=integer required=true ..."
275
- def to_s
276
- ParameterInspector.call(to_h)
277
- end
278
-
279
- private
280
-
281
- # Dynamically defines a method on the task class for parameter value access.
282
- #
283
- # @param parameter [Parameter] the parameter to create a method for
284
- #
285
- # @return [void]
286
- #
287
- # @example Define parameter method on task class
288
- # # Creates a private method that evaluates and caches parameter values
289
- # # with automatic error handling for coercion and validation failures
290
- def define_attribute(parameter)
291
- klass.send(:define_method, parameter.method_name) do
292
- @cmd_parameter_value_cache ||= {}
293
-
294
- unless @cmd_parameter_value_cache.key?(parameter.method_name)
295
- begin
296
- parameter_value = ParameterEvaluator.call(self, parameter)
297
- rescue CoercionError, ValidationError => e
298
- parameter.errors.add(parameter.method_name, e.message)
299
- errors.merge!(parameter.errors.to_hash)
300
- ensure
301
- @cmd_parameter_value_cache[parameter.method_name] = parameter_value
302
- end
303
- end
304
-
305
- @cmd_parameter_value_cache[parameter.method_name]
306
- end
307
-
308
- klass.send(:private, parameter.method_name)
309
- end
310
-
311
- end
312
- end