domainic-command 0.1.0.alpha.1.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CHANGELOG.md +19 -0
  4. data/LICENSE +1 -1
  5. data/README.md +93 -2
  6. data/lib/domainic/command/class_methods.rb +181 -0
  7. data/lib/domainic/command/context/attribute.rb +157 -0
  8. data/lib/domainic/command/context/attribute_set.rb +96 -0
  9. data/lib/domainic/command/context/behavior.rb +132 -0
  10. data/lib/domainic/command/context/input_context.rb +55 -0
  11. data/lib/domainic/command/context/output_context.rb +55 -0
  12. data/lib/domainic/command/context/runtime_context.rb +126 -0
  13. data/lib/domainic/command/errors/error.rb +23 -0
  14. data/lib/domainic/command/errors/execution_error.rb +40 -0
  15. data/lib/domainic/command/instance_methods.rb +92 -0
  16. data/lib/domainic/command/result/error_set.rb +272 -0
  17. data/lib/domainic/command/result/status.rb +49 -0
  18. data/lib/domainic/command/result.rb +194 -0
  19. data/lib/domainic/command.rb +77 -0
  20. data/lib/domainic-command.rb +3 -0
  21. data/sig/domainic/command/class_methods.rbs +100 -0
  22. data/sig/domainic/command/context/attribute.rbs +104 -0
  23. data/sig/domainic/command/context/attribute_set.rbs +69 -0
  24. data/sig/domainic/command/context/behavior.rbs +82 -0
  25. data/sig/domainic/command/context/input_context.rbs +40 -0
  26. data/sig/domainic/command/context/output_context.rbs +40 -0
  27. data/sig/domainic/command/context/runtime_context.rbs +90 -0
  28. data/sig/domainic/command/errors/error.rbs +21 -0
  29. data/sig/domainic/command/errors/execution_error.rbs +32 -0
  30. data/sig/domainic/command/instance_methods.rbs +56 -0
  31. data/sig/domainic/command/result/error_set.rbs +186 -0
  32. data/sig/domainic/command/result/status.rbs +47 -0
  33. data/sig/domainic/command/result.rbs +149 -0
  34. data/sig/domainic/command.rbs +67 -0
  35. data/sig/domainic-command.rbs +1 -0
  36. data/sig/manifest.yaml +1 -0
  37. metadata +50 -13
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Command
5
+ class Result
6
+ # A flexible container for managing and formatting command errors. The ErrorSet provides a consistent
7
+ # interface for working with errors from various sources including simple strings, arrays, hashes,
8
+ # standard errors, and objects implementing a compatible `to_h` interface (like ActiveModel::Errors).
9
+ #
10
+ # @example Basic usage
11
+ # errors = ErrorSet.new("Something went wrong")
12
+ # errors[:generic] #=> ["Something went wrong"]
13
+ # errors.full_messages #=> ["generic Something went wrong"]
14
+ #
15
+ # @example Hash-style errors
16
+ # errors = ErrorSet.new(
17
+ # name: "can't be blank",
18
+ # email: ["invalid format", "already taken"]
19
+ # )
20
+ # errors[:name] #=> ["can't be blank"]
21
+ # errors[:email] #=> ["invalid format", "already taken"]
22
+ #
23
+ # @example ActiveModel compatibility
24
+ # user = User.new
25
+ # user.valid? #=> false
26
+ # errors = ErrorSet.new(user.errors)
27
+ # errors[:email] #=> ["can't be blank"]
28
+ #
29
+ # @author {https://aaronmallen.me Aaron Allen}
30
+ # @since 0.1.0
31
+ class ErrorSet
32
+ # @rbs @lookup: Hash[Symbol, Array[String]]
33
+
34
+ # Creates a new ErrorSet instance
35
+ #
36
+ # @param errors [String, Array, Hash, StandardError, #to_h, nil] The errors to parse
37
+ #
38
+ # @raise [ArgumentError] If the errors cannot be parsed
39
+ # @return [ErrorSet] the new ErrorSet instance
40
+ # @rbs (?untyped? errors) -> void
41
+ def initialize(errors = nil)
42
+ @lookup = Parser.new(errors).parse!
43
+ end
44
+
45
+ # Retrieves error messages for a specific key
46
+ #
47
+ # @param key [String, Symbol] The error key to lookup
48
+ # @return [Array<String>, nil] The error messages for the key
49
+ # @rbs (String | Symbol key) -> Array[String]?
50
+ def [](key)
51
+ @lookup[key.to_sym]
52
+ end
53
+
54
+ # Adds a new error message for a specific key
55
+ #
56
+ # @param key [String, Symbol] The error key
57
+ # @param message [String, Array<String>] The error message(s)
58
+ #
59
+ # @return [void]
60
+ # @rbs (String | Symbol key, Array[String] | String message) -> void
61
+ def add(key, message)
62
+ key = key.to_sym
63
+ @lookup[key] ||= []
64
+ @lookup[key].concat(Array(message)) # steep:ignore ArgumentTypeMismatch
65
+ end
66
+
67
+ # Clear all errors from the set
68
+ #
69
+ # @return [void]
70
+ # @rbs () -> void
71
+ def clear
72
+ @lookup = {}
73
+ end
74
+
75
+ # Check if the error set is empty
76
+ #
77
+ # @return [Boolean] `true` if the error set is empty, `false` otherwise
78
+ # @rbs () -> bool
79
+ def empty?
80
+ @lookup.empty?
81
+ end
82
+
83
+ # Returns all error messages with their keys
84
+ #
85
+ # @return [Array<String>] All error messages prefixed with their keys
86
+ # @rbs () -> Array[String]
87
+ def full_messages
88
+ @lookup.each_with_object([]) do |(key, messages), result|
89
+ result.concat(messages.map { |message| "#{key} #{message}" })
90
+ end
91
+ end
92
+ alias to_a full_messages
93
+ alias to_array full_messages
94
+ alias to_ary full_messages
95
+
96
+ # Returns a hash of all error messages
97
+ #
98
+ # @return [Hash{Symbol => Array<String>}] All error messages grouped by key
99
+ # @rbs () -> Hash[Symbol, Array[String]]
100
+ def messages
101
+ @lookup.dup.freeze
102
+ end
103
+ alias to_h messages
104
+ alias to_hash messages
105
+
106
+ # A utility class for parsing various error formats into a consistent structure
107
+ #
108
+ # @!visibility private
109
+ # @api private
110
+ #
111
+ # @author {https://aaronmallen.me Aaron Allen}
112
+ # @since 0.1.0
113
+ class Parser
114
+ # Mapping of classes to their parsing strategy methods
115
+ #
116
+ # @return [Hash{Class, Module => Symbol}]
117
+ TYPE_STRATEGIES = {
118
+ Array => :parse_array,
119
+ Hash => :parse_hash,
120
+ String => :parse_generic_error,
121
+ StandardError => :parse_standard_error
122
+ }.freeze #: Hash[Class | Module, Symbol]
123
+
124
+ # Mapping of method names to their parsing strategy methods
125
+ #
126
+ # @return [Hash{Symbol => Symbol}]
127
+ RESPOND_TO_STRATEGIES = { to_h: :parse_to_h }.freeze #: Hash[Symbol, Symbol]
128
+
129
+ # Create a new Parser instance
130
+ #
131
+ # @param errors [Object] the errors to parse
132
+ #
133
+ # @return [Parser] the new Parser instance
134
+ # @rbs (untyped errors) -> void
135
+ def initialize(errors)
136
+ @errors = errors
137
+ @parsed = {}
138
+ end
139
+
140
+ # Parses the errors into a consistent format
141
+ #
142
+ # @raise [ArgumentError] If the errors cannot be parsed
143
+ # @return [Hash{Symbol => Array<String>}]
144
+ # @rbs () -> Hash[Symbol, Array[String]]
145
+ def parse!
146
+ parse_errors!
147
+ @parsed.transform_values { |errors| Array(errors) }
148
+ end
149
+
150
+ private
151
+
152
+ # Parses an array of errors into the generic error category
153
+ #
154
+ # @param errors [Array<String, StandardError>] Array of errors to parse
155
+ #
156
+ # @raise [ArgumentError] If any array element is not a String or StandardError
157
+ # @return [void]
158
+ # @rbs (Array[String | StandardError] errors) -> void
159
+ def parse_array(errors)
160
+ errors.each do |error|
161
+ case error
162
+ when String then parse_generic_error(error)
163
+ when StandardError then parse_standard_error(error)
164
+ else raise_invalid_errors!
165
+ end
166
+ end
167
+ end
168
+
169
+ # Determines the appropriate parsing strategy and executes it
170
+ #
171
+ # @raise [ArgumentError] If no valid parsing strategy is found
172
+ # @return [void]
173
+ # @rbs () -> void
174
+ def parse_errors!
175
+ return if @errors.nil?
176
+
177
+ TYPE_STRATEGIES.each_pair { |type, strategy| return send(strategy, @errors) if @errors.is_a?(type) }
178
+
179
+ RESPOND_TO_STRATEGIES.each_pair do |method, strategy|
180
+ return send(strategy, @errors) if @errors.respond_to?(method)
181
+ end
182
+
183
+ raise_invalid_errors!
184
+ end
185
+
186
+ # Parses a string or array of strings into the generic error category
187
+ #
188
+ # @param errors [String, Array<String>] The error(s) to parse
189
+ #
190
+ # @return [void]
191
+ # @rbs (Array[String] | String errors) -> void
192
+ def parse_generic_error(errors)
193
+ @parsed[:generic] ||= []
194
+ @parsed[:generic].concat(Array(errors))
195
+ end
196
+
197
+ # Parses a hash of errors into categorized messages
198
+ #
199
+ # @param errors [Hash{String, Symbol => Array<String>, String}] Hash of errors
200
+ #
201
+ # @raise [ArgumentError] If any value cannot be parsed
202
+ # @return [void]
203
+ # @rbs (Hash[String | Symbol, Array[StandardError] | Array[String] | StandardError | String] errors) -> void
204
+ def parse_hash(errors)
205
+ @parsed.merge!(errors.transform_keys(&:to_sym).transform_values { |value| parse_hash_value(value) })
206
+ end
207
+
208
+ # Parses a single value from a hash array
209
+ #
210
+ # @param value [String, StandardError] The value to parse
211
+ #
212
+ # @raise [ArgumentError] If the value is neither a String nor StandardError
213
+ # @return [String] The parsed error message
214
+ # @rbs (String | StandardError value) -> String
215
+ def parse_hash_array_value(value)
216
+ case value
217
+ when String then value
218
+ when StandardError then value.message
219
+ else raise_invalid_errors!
220
+ end
221
+ end
222
+
223
+ # Parses a value from a hash of errors
224
+ #
225
+ # @param value [String, StandardError, Array<String, StandardError>] The value to parse
226
+ #
227
+ # @raise [ArgumentError] If the value cannot be parsed
228
+ # @return [Array<String>] The parsed error message(s)
229
+ # @rbs (String | StandardError | Array[String | StandardError] value) -> Array[String]
230
+ def parse_hash_value(value)
231
+ case value
232
+ when String then [value]
233
+ when StandardError then [value.message]
234
+ when Array then value.map { |array_value| parse_hash_array_value(array_value) }
235
+ else raise_invalid_errors!
236
+ end
237
+ end
238
+
239
+ # Parses a StandardError into the generic error category
240
+ #
241
+ # @param errors [StandardError] The error to parse
242
+ #
243
+ # @return [void]
244
+ # @rbs (StandardError errors) -> void
245
+ def parse_standard_error(errors)
246
+ parse_generic_error(errors.message)
247
+ end
248
+
249
+ # Parses an object that responds to to_h
250
+ #
251
+ # @param errors [#to_h] The object to parse
252
+ #
253
+ # @raise [ArgumentError] If the hash cannot be parsed
254
+ # @return [void]
255
+ # @rbs (untyped errors) -> void
256
+ def parse_to_h(errors)
257
+ parse_hash(errors.to_h)
258
+ end
259
+
260
+ # Raises an invalid errors exception
261
+ #
262
+ # @raise [ArgumentError] Always raises with an invalid errors message
263
+ # @return [void]
264
+ # @rbs () -> void
265
+ def raise_invalid_errors!
266
+ raise ArgumentError, "invalid errors: #{@errors}"
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Domainic
4
+ module Command
5
+ class Result
6
+ # Defines status codes for command execution results. These codes follow Unix exit code conventions,
7
+ # making them suitable for CLI applications while remaining useful for other contexts.
8
+ #
9
+ # The status codes are specifically chosen to provide meaningful feedback about where in the command
10
+ # lifecycle a failure occurred:
11
+ # * 0 (SUCCESS) - The command completed successfully
12
+ # * 1 (FAILED_AT_RUNTIME) - The command failed during execution
13
+ # * 2 (FAILED_AT_INPUT) - The command failed during input validation
14
+ # * 3 (FAILED_AT_OUTPUT) - The command failed during output validation
15
+ #
16
+ # @example Using with CLI
17
+ # class MyCLI
18
+ # def self.run
19
+ # result = MyCommand.call(args)
20
+ # exit(result.status_code)
21
+ # end
22
+ # end
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.1.0
26
+ module STATUS
27
+ # Indicates successful command execution
28
+ #
29
+ # @return [Integer] status code 0
30
+ SUCCESS = 0 #: Integer
31
+
32
+ # Indicates a failure during command execution
33
+ #
34
+ # @return [Integer] status code 1
35
+ FAILED_AT_RUNTIME = 1 #: Integer
36
+
37
+ # Indicates a failure during input validation
38
+ #
39
+ # @return [Integer] status code 2
40
+ FAILED_AT_INPUT = 2 #: Integer
41
+
42
+ # Indicates a failure during output validation
43
+ #
44
+ # @return [Integer] status code 3
45
+ FAILED_AT_OUTPUT = 3 #: Integer
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/command/result/error_set'
4
+ require 'domainic/command/result/status'
5
+
6
+ module Domainic
7
+ module Command
8
+ # A value object representing the outcome of a command execution. The Result class provides
9
+ # a consistent interface for handling both successful and failed command executions, including
10
+ # return data and error information.
11
+ #
12
+ # Results are created through factory methods rather than direct instantiation, making the
13
+ # intent of the result clear:
14
+ #
15
+ # @example Creating a success result
16
+ # result = Result.success(value: 42)
17
+ # result.successful? #=> true
18
+ # result.value #=> 42
19
+ #
20
+ # @example Creating a failure result
21
+ # result = Result.failure_at_input(
22
+ # { name: "can't be blank" },
23
+ # context: { attempted_name: nil }
24
+ # )
25
+ # result.failure? #=> true
26
+ # result.errors[:name] #=> ["can't be blank"]
27
+ #
28
+ # Results use status codes that align with Unix exit codes, making them suitable for
29
+ # CLI applications:
30
+ # * 0 - Successful execution
31
+ # * 1 - Runtime failure
32
+ # * 2 - Input validation failure
33
+ # * 3 - Output validation failure
34
+ #
35
+ # @example CLI usage
36
+ # def self.run
37
+ # result = MyCommand.call(args)
38
+ # puts result.errors.full_messages if result.failure?
39
+ # exit(result.status_code)
40
+ # end
41
+ #
42
+ # @author {https://aaronmallen.me Aaron Allen}
43
+ # @since 0.1.0
44
+ class Result
45
+ # @rbs @data: Struct
46
+ # @rbs @errors: ErrorSet
47
+ # @rbs @status_code: Integer
48
+
49
+ # The structured data returned by the command
50
+ #
51
+ # @return [Struct] A frozen struct containing the command's output data
52
+ attr_reader :data #: Struct
53
+
54
+ # The errors that occurred during command execution
55
+ #
56
+ # @return [ErrorSet] The set of errors from the command
57
+ attr_reader :errors #: ErrorSet
58
+
59
+ # The status code indicating the result of the command execution
60
+ #
61
+ # @return [Integer] The status code (0 for success, non-zero for failures)
62
+ attr_reader :status_code #: Integer
63
+
64
+ # Creates a new failure result with the given status
65
+ #
66
+ # @param errors [Object] The errors that caused the failure
67
+ # @param context [Hash] Optional context data for the failure
68
+ # @param status [Integer] The status code for the failure (defaults to FAILED_AT_RUNTIME)
69
+ #
70
+ # @return [Result] A new failure result
71
+ # @rbs (untyped errors, ?Hash[String | Symbol, untyped] context, ?status: Integer) -> Result
72
+ def self.failure(errors, context = {}, status: STATUS::FAILED_AT_RUNTIME)
73
+ new(status, context:, errors:)
74
+ end
75
+
76
+ # Creates a new input validation failure result
77
+ #
78
+ # @param errors [Object] The validation errors
79
+ # @param context [Hash] Optional context data for the failure
80
+ #
81
+ # @return [Result] A new input validation failure result
82
+ # @rbs (untyped errors, ?Hash[String | Symbol, untyped] context) -> Result
83
+ def self.failure_at_input(errors, context = {})
84
+ new(STATUS::FAILED_AT_INPUT, context:, errors:)
85
+ end
86
+
87
+ # Creates a new output validation failure result
88
+ #
89
+ # @param errors [Object] The validation errors
90
+ # @param context [Hash] Optional context data for the failure
91
+ #
92
+ # @return [Result] A new output validation failure result
93
+ # @rbs (untyped errors, ?Hash[String | Symbol, untyped] context) -> Result
94
+ def self.failure_at_output(errors, context = {})
95
+ new(STATUS::FAILED_AT_OUTPUT, context:, errors:)
96
+ end
97
+
98
+ # Creates a new success result
99
+ #
100
+ # @param context [Hash] The successful result data
101
+ #
102
+ # @return [Result] A new success result
103
+ # @rbs (Hash[String | Symbol, untyped] context) -> Result
104
+ def self.success(context)
105
+ new(STATUS::SUCCESS, context:)
106
+ end
107
+
108
+ # Creates a new result instance
109
+ #
110
+ # @param status_code [Integer] The status code for the result
111
+ # @param context [Hash] The data context for the result
112
+ # @param errors [Object, nil] Any errors that occurred
113
+ #
114
+ # @raise [ArgumentError] If status_code is invalid or context is not a Hash
115
+ # @return [void]
116
+ # @rbs (Integer status, ?context: Hash[String | Symbol, untyped], ?errors: untyped) -> void
117
+ def initialize(status_code, context: {}, errors: nil)
118
+ initialize_status_code(status_code)
119
+ initialize_data(context)
120
+ @errors = ErrorSet.new(errors)
121
+ end
122
+ private_class_method :new
123
+
124
+ # Indicates whether the command failed
125
+ #
126
+ # @return [Boolean] true if the command failed; false otherwise
127
+ # @rbs () -> bool
128
+ def failure?
129
+ status_code != STATUS::SUCCESS
130
+ end
131
+ alias failed? failure?
132
+
133
+ # Indicates whether the command succeeded
134
+ #
135
+ # @return [Boolean] true if the command succeeded; false otherwise
136
+ # @rbs () -> bool
137
+ def successful?
138
+ status_code == STATUS::SUCCESS
139
+ end
140
+ alias success? successful?
141
+
142
+ private
143
+
144
+ # Initializes the data struct from the context hash
145
+ #
146
+ # @param context [Hash] The context hash to convert to a struct
147
+ # @raise [ArgumentError] If context is not a Hash
148
+ # @return [void]
149
+ def initialize_data(context)
150
+ raise ArgumentError, ':context must be a Hash' unless context.is_a?(Hash)
151
+
152
+ context = context.transform_keys(&:to_sym)
153
+ @data = Struct.new(nil).new.freeze if context.empty?
154
+ @data = Struct.new(*context.keys, keyword_init: true).new(**context).freeze unless context.empty?
155
+ end
156
+
157
+ # Validates and initializes the status code
158
+ #
159
+ # @param status_code [Integer] The status code to validate
160
+ # @raise [ArgumentError] If the status code is not valid
161
+ # @return [void]
162
+ def initialize_status_code(status_code)
163
+ unless STATUS.constants.map { |c| STATUS.const_get(c) }.include?(status_code)
164
+ raise ArgumentError, "invalid status code: #{status_code}"
165
+ end
166
+
167
+ @status_code = status_code
168
+ end
169
+
170
+ # Delegate method calls to the data struct
171
+ #
172
+ # @param method_name [String, Symbol] The method name to call
173
+ #
174
+ # @return [Object] The result of the method call
175
+ # @rbs override
176
+ def method_missing(method_name, ...)
177
+ return super unless respond_to_missing?(method_name)
178
+
179
+ data.public_send(method_name.to_sym)
180
+ end
181
+
182
+ # Indicates whether the data struct responds to the given method
183
+ #
184
+ # @param method_name [String, Symbol] The method name to check
185
+ # @param _include_private [Boolean] Whether to include private methods
186
+ #
187
+ # @return [Boolean] `true` if the data struct responds to the method; `false` otherwise
188
+ # @rbs (String | Symbol method_name, ?bool include_private) -> bool
189
+ def respond_to_missing?(method_name, _include_private = false)
190
+ data.respond_to?(method_name.to_sym) || super
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/command/class_methods'
4
+ require 'domainic/command/instance_methods'
5
+
6
+ module Domainic
7
+ # A module that implements the Command pattern, providing a structured way to encapsulate business operations.
8
+ # Commands are single-purpose objects that perform a specific action, validating their inputs and outputs while
9
+ # maintaining a consistent interface for error handling and result reporting.
10
+ #
11
+ # @abstract Including classes must implement an {#execute} method that defines the command's business logic.
12
+ # The {#execute} method has access to validated inputs via the {#context} accessor and should set any output values
13
+ # on the context before returning.
14
+ #
15
+ # @example Basic command definition
16
+ # class CreateUser
17
+ # include Domainic::Command
18
+ #
19
+ # argument :login, String, "The user's login", required: true
20
+ # argument :password, String, "The user's password", required: true
21
+ #
22
+ # output :user, User, "The created user", required: true
23
+ # output :created_at, Time, "When the user was created"
24
+ #
25
+ # def execute
26
+ # user = User.create!(login: context.login, password: context.password)
27
+ # context.user = user
28
+ # context.created_at = Time.current
29
+ # end
30
+ # end
31
+ #
32
+ # @example Using external context classes
33
+ # class CreateUserInput < Domainic::Command::Context::InputContext
34
+ # argument :login, String, "The user's login", required: true
35
+ # argument :password, String, "The user's password", required: true
36
+ # end
37
+ #
38
+ # class CreateUserOutput < Domainic::Command::Context::OutputContext
39
+ # field :user, User, "The created user", required: true
40
+ # field :created_at, Time, "When the user was created"
41
+ # end
42
+ #
43
+ # class CreateUser
44
+ # include Domainic::Command
45
+ #
46
+ # accepts_arguments_matching CreateUserInput
47
+ # returns_output_matching CreateUserOutput
48
+ #
49
+ # def execute
50
+ # user = User.create!(login: context.login, password: context.password)
51
+ # context.user = user
52
+ # context.created_at = Time.current
53
+ # end
54
+ # end
55
+ #
56
+ # @example Command usage
57
+ # # Successful execution
58
+ # result = CreateUser.call(login: "user@example.com", password: "secret123")
59
+ # result.successful? #=> true
60
+ # result.user #=> #<User id: 1, login: "user@example.com">
61
+ #
62
+ # # Failed execution
63
+ # result = CreateUser.call(login: "invalid")
64
+ # result.failure? #=> true
65
+ # result.errors[:password] #=> ["is required"]
66
+ #
67
+ # @author {https://aaronmallen.me Aaron Allen}
68
+ # @since 0.1.0
69
+ module Command
70
+ # @rbs override
71
+ def self.included(base)
72
+ super
73
+ base.include(InstanceMethods)
74
+ base.extend(ClassMethods)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/command'
@@ -0,0 +1,100 @@
1
+ module Domainic
2
+ module Command
3
+ # Class methods that are extended onto any class that includes {Command}. These methods provide
4
+ # the DSL for defining command inputs and outputs, as well as class-level execution methods.
5
+ #
6
+ # @author {https://aaronmallen.me Aaron Allen}
7
+ # @since 0.1.0
8
+ module ClassMethods
9
+ @input_context_class: singleton(Context::InputContext)
10
+
11
+ @runtime_context_class: singleton(Context::RuntimeContext)
12
+
13
+ @output_context_class: singleton(Context::OutputContext)
14
+
15
+ # Specifies an external input context class for the command
16
+ #
17
+ # @param input_context_class [Class] A subclass of {Context::InputContext}
18
+ #
19
+ # @raise [ArgumentError] If the provided class is not a subclass of {Context::InputContext}
20
+ # @return [void]
21
+ def accepts_arguments_matching: (singleton(Context::InputContext) input_context_class) -> void
22
+
23
+ # Defines an input argument for the command
24
+ #
25
+ # @overload argument(name, *type_validator_and_description, **options)
26
+ # @param name [String, Symbol] The name of the argument
27
+ # @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
28
+ # description arguments
29
+ # @param options [Hash] Configuration options for the argument
30
+ # @option options [Object] :default A static default value
31
+ # @option options [Proc] :default_generator A proc that generates the default value
32
+ # @option options [Object] :default_value Alias for :default
33
+ # @option options [String, nil] :desc Short description of the argument
34
+ # @option options [String, nil] :description Full description of the argument
35
+ # @option options [Boolean] :required Whether the argument is required
36
+ # @option options [Class, Module, Object, Proc] :type A type validator
37
+ #
38
+ # @return [void]
39
+ def argument: (String | Symbol name, *(Class | Module | Object | Proc | String)? type_validator_and_description, ?default: untyped, ?default_generator: untyped, ?default_value: untyped, ?desc: String?, ?description: String?, ?required: bool, ?type: Class | Module | Object | Proc) -> void
40
+
41
+ # Executes the command with the given context, handling any errors
42
+ #
43
+ # @param context [Hash] The input context for the command
44
+ #
45
+ # @return [Result] The result of the command execution
46
+ def call: (**untyped context) -> Result
47
+
48
+ # Executes the command with the given context, raising any errors
49
+ #
50
+ # @param context [Hash] The input context for the command
51
+ #
52
+ # @raise [ExecutionError] If the command execution fails
53
+ # @return [Result] The result of the command execution
54
+ def call!: (**untyped context) -> Result
55
+
56
+ # Defines an output field for the command
57
+ #
58
+ # @overload output(name, *type_validator_and_description, **options)
59
+ # @param name [String, Symbol] The name of the output field
60
+ # @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
61
+ # description arguments
62
+ # @param options [Hash] Configuration options for the output
63
+ # @option options [Object] :default A static default value
64
+ # @option options [Proc] :default_generator A proc that generates the default value
65
+ # @option options [Object] :default_value Alias for :default
66
+ # @option options [String, nil] :desc Short description of the output
67
+ # @option options [String, nil] :description Full description of the output
68
+ # @option options [Boolean] :required Whether the output is required
69
+ # @option options [Class, Module, Object, Proc] :type A type validator
70
+ #
71
+ # @return [void]
72
+ def output: (String | Symbol name, *(Class | Module | Object | Proc | String)? type_validator_and_description, ?default: untyped, ?default_generator: untyped, ?default_value: untyped, ?desc: String?, ?description: String?, ?required: bool, ?type: Class | Module | Object | Proc) -> void
73
+
74
+ # Specifies an external output context class for the command
75
+ #
76
+ # @param output_context_class [Class] A subclass of {Context::OutputContext}
77
+ #
78
+ # @raise [ArgumentError] If the provided class is not a subclass of {Context::OutputContext}
79
+ # @return [void]
80
+ def returns_data_matching: (singleton(Context::OutputContext) output_context_class) -> void
81
+
82
+ private
83
+
84
+ # Returns the input context class for the command
85
+ #
86
+ # @return [Class] A subclass of {Context::InputContext}
87
+ def input_context_class: () -> singleton(Context::InputContext)
88
+
89
+ # Returns the output context class for the command
90
+ #
91
+ # @return [Class] A subclass of {Context::OutputContext}
92
+ def output_context_class: () -> singleton(Context::OutputContext)
93
+
94
+ # Returns the runtime context class for the command
95
+ #
96
+ # @return [Class] A subclass of {Context::RuntimeContext}
97
+ def runtime_context_class: () -> singleton(Context::RuntimeContext)
98
+ end
99
+ end
100
+ end