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.
- checksums.yaml +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +1 -1
- data/README.md +93 -2
- data/lib/domainic/command/class_methods.rb +181 -0
- data/lib/domainic/command/context/attribute.rb +157 -0
- data/lib/domainic/command/context/attribute_set.rb +96 -0
- data/lib/domainic/command/context/behavior.rb +132 -0
- data/lib/domainic/command/context/input_context.rb +55 -0
- data/lib/domainic/command/context/output_context.rb +55 -0
- data/lib/domainic/command/context/runtime_context.rb +126 -0
- data/lib/domainic/command/errors/error.rb +23 -0
- data/lib/domainic/command/errors/execution_error.rb +40 -0
- data/lib/domainic/command/instance_methods.rb +92 -0
- data/lib/domainic/command/result/error_set.rb +272 -0
- data/lib/domainic/command/result/status.rb +49 -0
- data/lib/domainic/command/result.rb +194 -0
- data/lib/domainic/command.rb +77 -0
- data/lib/domainic-command.rb +3 -0
- data/sig/domainic/command/class_methods.rbs +100 -0
- data/sig/domainic/command/context/attribute.rbs +104 -0
- data/sig/domainic/command/context/attribute_set.rbs +69 -0
- data/sig/domainic/command/context/behavior.rbs +82 -0
- data/sig/domainic/command/context/input_context.rbs +40 -0
- data/sig/domainic/command/context/output_context.rbs +40 -0
- data/sig/domainic/command/context/runtime_context.rbs +90 -0
- data/sig/domainic/command/errors/error.rbs +21 -0
- data/sig/domainic/command/errors/execution_error.rbs +32 -0
- data/sig/domainic/command/instance_methods.rbs +56 -0
- data/sig/domainic/command/result/error_set.rbs +186 -0
- data/sig/domainic/command/result/status.rbs +47 -0
- data/sig/domainic/command/result.rbs +149 -0
- data/sig/domainic/command.rbs +67 -0
- data/sig/domainic-command.rbs +1 -0
- data/sig/manifest.yaml +1 -0
- metadata +50 -13
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/context/attribute'
|
4
|
+
require 'domainic/command/context/attribute_set'
|
5
|
+
|
6
|
+
module Domainic
|
7
|
+
module Command
|
8
|
+
module Context
|
9
|
+
# A module that provides attribute management for command contexts. When included in a class, it provides
|
10
|
+
# a DSL for defining and managing typed attributes with validation, default values, and thread-safe access.
|
11
|
+
#
|
12
|
+
# ## Thread Safety
|
13
|
+
# The attribute system is designed to be thread-safe during class definition and inheritance. A class-level
|
14
|
+
# mutex protects the attribute registry during:
|
15
|
+
# * Definition of new attributes via the DSL
|
16
|
+
# * Inheritance of attributes to subclasses
|
17
|
+
#
|
18
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
19
|
+
# @since 0.1.0
|
20
|
+
module Behavior
|
21
|
+
# @rbs (Class | Module base) -> void
|
22
|
+
def self.included(base)
|
23
|
+
super
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Provides class-level methods for defining and managing attributes. These methods are
|
28
|
+
# automatically extended onto any class that includes {Behavior}.
|
29
|
+
#
|
30
|
+
# @since 0.1.0
|
31
|
+
module ClassMethods
|
32
|
+
# @rbs @attributes: AttributeSet
|
33
|
+
# @rbs @attribute_lock: Mutex
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Defines a new attribute for the context.
|
38
|
+
#
|
39
|
+
# @overload attribute(name, *type_validator_and_description, **options)
|
40
|
+
# @param name [String, Symbol] The name of the attribute
|
41
|
+
# @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
|
42
|
+
# description arguments
|
43
|
+
# @param options [Hash] Configuration options for the attribute
|
44
|
+
# @option options [Object] :default A static default value
|
45
|
+
# @option options [Proc] :default_generator A proc that generates the default value
|
46
|
+
# @option options [Object] :default_value Alias for :default
|
47
|
+
# @option options [String, nil] :desc Short description of the attribute
|
48
|
+
# @option options [String, nil] :description Full description of the attribute
|
49
|
+
# @option options [Boolean] :required Whether the attribute is required
|
50
|
+
# @option options [Class, Module, Object, Proc] :type A type validator
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
# @rbs (
|
54
|
+
# String | Symbol name,
|
55
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
56
|
+
# ?default: untyped,
|
57
|
+
# ?default_generator: untyped,
|
58
|
+
# ?default_value: untyped,
|
59
|
+
# ?desc: String?,
|
60
|
+
# ?description: String?,
|
61
|
+
# ?required: bool,
|
62
|
+
# ?type: Class | Module | Object | Proc
|
63
|
+
# ) -> void
|
64
|
+
def attribute(...)
|
65
|
+
# @type self: Class & Behavior & ClassMethods
|
66
|
+
attribute = Attribute.new(...)
|
67
|
+
attribute_lock.synchronize { attributes.add(attribute) }
|
68
|
+
attr_reader attribute.name
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the mutex used to synchronize attribute operations.
|
72
|
+
#
|
73
|
+
# @return [Mutex]
|
74
|
+
# @rbs () -> Mutex
|
75
|
+
def attribute_lock
|
76
|
+
@attribute_lock ||= Mutex.new
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the set of attributes defined for this context.
|
80
|
+
#
|
81
|
+
# @return [AttributeSet]
|
82
|
+
# @rbs () -> AttributeSet
|
83
|
+
def attributes
|
84
|
+
@attributes ||= AttributeSet.new
|
85
|
+
end
|
86
|
+
|
87
|
+
# Handles inheritance of attributes to subclasses.
|
88
|
+
#
|
89
|
+
# @param subclass [Class, Module] The inheriting class
|
90
|
+
#
|
91
|
+
# @return [void]
|
92
|
+
# @rbs (Class | Module subclass) -> void
|
93
|
+
def inherited(subclass)
|
94
|
+
super
|
95
|
+
attribute_lock.synchronize do
|
96
|
+
subclass.instance_variable_set(:@attributes, attributes.dup)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Initializes a new context instance with the given attributes.
|
102
|
+
#
|
103
|
+
# @param options [Hash{String, Symbol => Object}] Attribute values for initialization
|
104
|
+
#
|
105
|
+
# @raise [ArgumentError] If any attribute values are invalid
|
106
|
+
# @return [Behavior]
|
107
|
+
# @rbs (**untyped options) -> void
|
108
|
+
def initialize(**options)
|
109
|
+
options = options.transform_keys(&:to_sym)
|
110
|
+
|
111
|
+
self.class.send(:attributes).each do |attribute|
|
112
|
+
value = options.fetch(attribute.name) { attribute.default if attribute.default? }
|
113
|
+
raise ArgumentError, "Invalid value for #{attribute.name}: #{value.inspect}" unless attribute.valid?(value)
|
114
|
+
|
115
|
+
instance_variable_set(:"@#{attribute.name}", value)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns a hash of all attribute names and their values.
|
120
|
+
#
|
121
|
+
# @return [Hash{Symbol => Object}] A hash of attribute values
|
122
|
+
# @rbs () -> Hash[Symbol, untyped]
|
123
|
+
def to_hash
|
124
|
+
self.class.send(:attributes).each_with_object({}) do |attribute, hash|
|
125
|
+
hash[attribute.name] = public_send(attribute.name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
alias to_h to_hash
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/context/behavior'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Command
|
7
|
+
module Context
|
8
|
+
# A context class for managing command input arguments. This class provides a structured way to define,
|
9
|
+
# validate, and access input parameters for commands.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class MyInputContext < Domainic::Command::Context::InputContext
|
13
|
+
# argument :name, String, "The name to process"
|
14
|
+
# argument :count, Integer, default: 1
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
18
|
+
# @since 0.1.0
|
19
|
+
class InputContext
|
20
|
+
# @rbs! extend Behavior::ClassMethods
|
21
|
+
|
22
|
+
include Behavior
|
23
|
+
|
24
|
+
# Defines an input argument for the command
|
25
|
+
#
|
26
|
+
# @overload argument(name, *type_validator_and_description, **options)
|
27
|
+
# @param name [String, Symbol] The name of the argument
|
28
|
+
# @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
|
29
|
+
# description arguments
|
30
|
+
# @param options [Hash] Configuration options for the argument
|
31
|
+
# @option options [Object] :default A static default value
|
32
|
+
# @option options [Proc] :default_generator A proc that generates the default value
|
33
|
+
# @option options [Object] :default_value Alias for :default
|
34
|
+
# @option options [String, nil] :desc Short description of the argument
|
35
|
+
# @option options [String, nil] :description Full description of the argument
|
36
|
+
# @option options [Boolean] :required Whether the argument is required
|
37
|
+
# @option options [Class, Module, Object, Proc] :type A type validator
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
# @rbs (
|
41
|
+
# String | Symbol name,
|
42
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
43
|
+
# ?default: untyped,
|
44
|
+
# ?default_generator: untyped,
|
45
|
+
# ?default_value: untyped,
|
46
|
+
# ?desc: String?,
|
47
|
+
# ?description: String?,
|
48
|
+
# ?required: bool,
|
49
|
+
# ?type: Class | Module | Object | Proc
|
50
|
+
# ) -> void
|
51
|
+
def self.argument(...) = attribute(...)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/context/behavior'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Command
|
7
|
+
module Context
|
8
|
+
# A context class for managing command output values. This class provides a structured way to define,
|
9
|
+
# validate, and access the return values from commands.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class MyOutputContext < Domainic::Command::Context::OutputContext
|
13
|
+
# field :processed_name, String, "The processed name"
|
14
|
+
# field :status, Symbol, default: :success
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
18
|
+
# @since 0.1.0
|
19
|
+
class OutputContext
|
20
|
+
# @rbs! extend Behavior::ClassMethods
|
21
|
+
|
22
|
+
include Behavior
|
23
|
+
|
24
|
+
# Defines a return value for the command
|
25
|
+
#
|
26
|
+
# @overload field(name, *type_validator_and_description, **options)
|
27
|
+
# @param name [String, Symbol] The name of the return value
|
28
|
+
# @param type_validator_and_description [Array<Class, Module, Object, Proc, String, nil>] Type validator or
|
29
|
+
# description arguments
|
30
|
+
# @param options [Hash] Configuration options for the return value
|
31
|
+
# @option options [Object] :default A static default value
|
32
|
+
# @option options [Proc] :default_generator A proc that generates the default value
|
33
|
+
# @option options [Object] :default_value Alias for :default
|
34
|
+
# @option options [String, nil] :desc Short description of the return value
|
35
|
+
# @option options [String, nil] :description Full description of the return value
|
36
|
+
# @option options [Boolean] :required Whether the return value is required
|
37
|
+
# @option options [Class, Module, Object, Proc] :type A type validator
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
# @rbs (
|
41
|
+
# String | Symbol name,
|
42
|
+
# *(Class | Module | Object | Proc | String)? type_validator_and_description,
|
43
|
+
# ?default: untyped,
|
44
|
+
# ?default_generator: untyped,
|
45
|
+
# ?default_value: untyped,
|
46
|
+
# ?desc: String?,
|
47
|
+
# ?description: String?,
|
48
|
+
# ?required: bool,
|
49
|
+
# ?type: Class | Module | Object | Proc
|
50
|
+
# ) -> void
|
51
|
+
def self.field(...) = attribute(...)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domainic
|
4
|
+
module Command
|
5
|
+
module Context
|
6
|
+
# A flexible context class for managing command state during execution. This class provides a dynamic
|
7
|
+
# storage mechanism for command data, allowing both hash-style and method-style access to values.
|
8
|
+
#
|
9
|
+
# The RuntimeContext serves as a mutable workspace during command execution, bridging the gap between
|
10
|
+
# input parameters and output values. It automatically handles type coercion of keys to symbols and
|
11
|
+
# provides safe value duplication when converting to a hash.
|
12
|
+
#
|
13
|
+
# @example Hash-style access
|
14
|
+
# context = RuntimeContext.new(count: 1)
|
15
|
+
# context[:count] #=> 1
|
16
|
+
# context[:count] = 2
|
17
|
+
# context[:count] #=> 2
|
18
|
+
#
|
19
|
+
# @example Method-style access
|
20
|
+
# context = RuntimeContext.new(name: "test")
|
21
|
+
# context.name #=> "test"
|
22
|
+
# context.name = "new test"
|
23
|
+
# context.name #=> "new test"
|
24
|
+
#
|
25
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
26
|
+
# @since 0.1.0
|
27
|
+
class RuntimeContext
|
28
|
+
# @rbs @data: Hash[Symbol, untyped]
|
29
|
+
|
30
|
+
# Creates a new RuntimeContext with the given options
|
31
|
+
#
|
32
|
+
# @param options [Hash] Initial values for the context
|
33
|
+
#
|
34
|
+
# @return [RuntimeContext]
|
35
|
+
# @rbs (**untyped options) -> void
|
36
|
+
def initialize(**options)
|
37
|
+
@data = options.transform_keys(&:to_sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves a value by its attribute name
|
41
|
+
#
|
42
|
+
# @param attribute_name [String, Symbol] The name of the attribute to retrieve
|
43
|
+
#
|
44
|
+
# @return [Object, nil] The value associated with the attribute name
|
45
|
+
# @rbs (String | Symbol attribute_name) -> untyped
|
46
|
+
def [](attribute_name)
|
47
|
+
read_from_attribute(attribute_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets a value for the given attribute name
|
51
|
+
#
|
52
|
+
# @param attribute_name [String, Symbol] The name of the attribute to set
|
53
|
+
# @param value [Object] The value to store
|
54
|
+
#
|
55
|
+
# @return [Object] The stored value
|
56
|
+
# @rbs (String | Symbol attribute_name, untyped value) -> untyped
|
57
|
+
def []=(attribute_name, value)
|
58
|
+
write_to_attribute(attribute_name, value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts the context to a hash, duplicating values where appropriate
|
62
|
+
#
|
63
|
+
# @note Class and Module values are not duplicated to prevent potential issues
|
64
|
+
#
|
65
|
+
# @return [Hash{Symbol => Object}] A hash containing all stored values
|
66
|
+
# @rbs () -> Hash[Symbol, untyped]
|
67
|
+
def to_hash
|
68
|
+
@data.transform_values do |value|
|
69
|
+
value.is_a?(Class) || value.is_a?(Module) ? value : value.dup
|
70
|
+
end
|
71
|
+
end
|
72
|
+
alias to_h to_hash
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Handles dynamic method calls for reading and writing attributes
|
77
|
+
#
|
78
|
+
# @return [Object, nil]
|
79
|
+
# @rbs override
|
80
|
+
def method_missing(method_name, ...)
|
81
|
+
return super unless respond_to_missing?(method_name)
|
82
|
+
|
83
|
+
if method_name.to_s.end_with?('=')
|
84
|
+
write_to_attribute(method_name.to_s.delete_suffix('=').to_sym, ...)
|
85
|
+
else
|
86
|
+
@data[method_name]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reads a value from the internal storage
|
91
|
+
#
|
92
|
+
# @param attribute_name [String, Symbol] The name of the attribute to read
|
93
|
+
#
|
94
|
+
# @return [Object, nil] The stored value
|
95
|
+
# @rbs (String | Symbol attribute_name) -> untyped
|
96
|
+
def read_from_attribute(attribute_name)
|
97
|
+
@data[attribute_name.to_sym]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Determines if a method name can be handled dynamically
|
101
|
+
#
|
102
|
+
# @param method_name [Symbol] The name of the method to check
|
103
|
+
# @param _include_private [Boolean] Whether to include private methods
|
104
|
+
#
|
105
|
+
# @return [Boolean] Whether the method can be handled
|
106
|
+
# @rbs (String | Symbol method_name, ?bool _include_private) -> bool
|
107
|
+
def respond_to_missing?(method_name, _include_private = false)
|
108
|
+
return true if method_name.to_s.end_with?('=')
|
109
|
+
return true if @data.key?(method_name.to_sym)
|
110
|
+
|
111
|
+
super
|
112
|
+
end
|
113
|
+
|
114
|
+
# Writes a value to the internal storage
|
115
|
+
#
|
116
|
+
# @param attribute_name [String, Symbol] The name of the attribute to write
|
117
|
+
# @param value [Object] The value to store
|
118
|
+
# @return [Object] The stored value
|
119
|
+
# @rbs (String | Symbol attribute_name, untyped value) -> untyped
|
120
|
+
def write_to_attribute(attribute_name, value)
|
121
|
+
@data[attribute_name.to_sym] = value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Domainic
|
4
|
+
module Command
|
5
|
+
# Base error class for command-related errors. This class serves as the root of the command error
|
6
|
+
# hierarchy, allowing for specific error handling of command-related issues.
|
7
|
+
#
|
8
|
+
# @note This is an abstract class and should not be instantiated directly. Instead, use one of its
|
9
|
+
# subclasses for specific error cases.
|
10
|
+
#
|
11
|
+
# @example Rescuing command errors
|
12
|
+
# begin
|
13
|
+
# # Command execution code
|
14
|
+
# rescue Domainic::Command::Error => e
|
15
|
+
# # Handle any command-related error
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
19
|
+
# @since 0.1.0
|
20
|
+
class Error < StandardError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/errors/error'
|
4
|
+
|
5
|
+
module Domainic
|
6
|
+
module Command
|
7
|
+
# Error class raised when a command encounters an execution failure. This class provides access to
|
8
|
+
# both the error message and the {Result} object containing detailed information about the failure.
|
9
|
+
#
|
10
|
+
# @example Handling execution errors
|
11
|
+
# begin
|
12
|
+
# command.call!
|
13
|
+
# rescue Domainic::Command::ExecutionError => e
|
14
|
+
# puts e.message # Access the error message
|
15
|
+
# puts e.result.errors # Access the detailed errors
|
16
|
+
# puts e.result.status_code # Access the status code
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
20
|
+
# @since 0.1.0
|
21
|
+
class ExecutionError < Error
|
22
|
+
# The {Result} object containing detailed information about the execution failure
|
23
|
+
#
|
24
|
+
# @return [Result] The result object associated with the failure
|
25
|
+
attr_reader :result #: Result
|
26
|
+
|
27
|
+
# Creates a new execution error with the given message and result
|
28
|
+
#
|
29
|
+
# @param message [String] The error message describing what went wrong
|
30
|
+
# @param result [Result] The result object containing detailed failure information
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
# @rbs (String message, Result result) -> void
|
34
|
+
def initialize(message, result)
|
35
|
+
@result = result
|
36
|
+
super(message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'domainic/command/errors/execution_error'
|
4
|
+
require 'domainic/command/result'
|
5
|
+
|
6
|
+
module Domainic
|
7
|
+
module Command
|
8
|
+
# Instance methods that are included in any class that includes {Command}. These methods provide
|
9
|
+
# the core execution logic and error handling for commands.
|
10
|
+
#
|
11
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
12
|
+
# @since 0.1.0
|
13
|
+
module InstanceMethods
|
14
|
+
# Executes the command with the given context, handling any errors
|
15
|
+
#
|
16
|
+
# @param context [Hash] The input context for the command
|
17
|
+
#
|
18
|
+
# @return [Result] The result of the command execution
|
19
|
+
# @rbs (**untyped context) -> Result
|
20
|
+
def call(**context)
|
21
|
+
call!(**context)
|
22
|
+
rescue ExecutionError => e
|
23
|
+
e.result
|
24
|
+
end
|
25
|
+
|
26
|
+
# Executes the command with the given context, raising any errors
|
27
|
+
#
|
28
|
+
# @param input [Hash] The input context for the command
|
29
|
+
#
|
30
|
+
# @raise [ExecutionError] If the command execution fails
|
31
|
+
# @return [Result] The result of the command execution
|
32
|
+
# @rbs (**untyped context) -> Result
|
33
|
+
def call!(**input)
|
34
|
+
__execute_command!(input)
|
35
|
+
rescue StandardError => e
|
36
|
+
raise e if e.is_a?(ExecutionError)
|
37
|
+
|
38
|
+
raise ExecutionError.new("#{self.class} failed", Result.failure(e, context.to_h))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Executes the command's business logic
|
42
|
+
#
|
43
|
+
# @abstract Subclass and override {#execute} to implement command behavior
|
44
|
+
#
|
45
|
+
# @raise [NotImplementedError] If the subclass does not implement {#execute}
|
46
|
+
# @return [void]
|
47
|
+
# @rbs () -> void
|
48
|
+
def execute
|
49
|
+
raise NotImplementedError, "#{self.class} does not implement #execute"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# The runtime context for the command execution
|
55
|
+
#
|
56
|
+
# @return [Context::RuntimeContext] The runtime context
|
57
|
+
attr_reader :context #: Context::RuntimeContext
|
58
|
+
|
59
|
+
# Execute the command with the given input context
|
60
|
+
#
|
61
|
+
# @param input [Hash] The input context for the command
|
62
|
+
#
|
63
|
+
# @return [Result] The result of the command execution
|
64
|
+
# @rbs (Hash[String | Symbol, untyped] input) -> Result
|
65
|
+
def __execute_command!(input)
|
66
|
+
input_context = __validate_context!(:input, input)
|
67
|
+
@context = self.class.send(:runtime_context_class).new(**input_context.to_h)
|
68
|
+
execute
|
69
|
+
output_context = __validate_context!(:output, context.to_h)
|
70
|
+
Result.success(output_context.to_h)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Validates an input or output context
|
74
|
+
#
|
75
|
+
# @param context_type [Symbol] The type of context to validate
|
76
|
+
# @param context [Hash] The context data to validate
|
77
|
+
#
|
78
|
+
# @raise [ExecutionError] If the context is invalid
|
79
|
+
# @return [Context::InputContext, Context::OutputContext] The validated context
|
80
|
+
# @rbs (
|
81
|
+
# :input | :output context_type,
|
82
|
+
# Hash[String | Symbol, untyped] context
|
83
|
+
# ) -> (Context::InputContext | Context::OutputContext)
|
84
|
+
def __validate_context!(context_type, context)
|
85
|
+
self.class.send(:"#{context_type}_context_class").new(**context)
|
86
|
+
rescue StandardError => e
|
87
|
+
result = context_type == :input ? Result.failure_at_input(e) : Result.failure_at_output(e)
|
88
|
+
raise ExecutionError.new("#{self.class} has invalid #{context_type}", result)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|