domainic-command 0.1.0.alpha.1.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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