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.
- 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
|