cuprum 1.2.0 → 1.3.0.rc.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/CHANGELOG.md +28 -0
- data/README.md +4 -2
- data/lib/cuprum/command.rb +40 -3
- data/lib/cuprum/command_factory.rb +5 -5
- data/lib/cuprum/currying/curried_command.rb +2 -0
- data/lib/cuprum/currying.rb +3 -3
- data/lib/cuprum/error.rb +1 -1
- data/lib/cuprum/errors/command_not_implemented.rb +1 -1
- data/lib/cuprum/errors/invalid_parameters.rb +44 -0
- data/lib/cuprum/errors/multiple_errors.rb +1 -1
- data/lib/cuprum/errors/operation_not_called.rb +1 -1
- data/lib/cuprum/errors/uncaught_exception.rb +1 -1
- data/lib/cuprum/errors.rb +1 -0
- data/lib/cuprum/exception_handling.rb +11 -2
- data/lib/cuprum/map_command.rb +4 -0
- data/lib/cuprum/matcher.rb +2 -2
- data/lib/cuprum/matcher_list.rb +4 -4
- data/lib/cuprum/matching.rb +7 -7
- data/lib/cuprum/operation.rb +2 -2
- data/lib/cuprum/parameter_validation/validation_rule.rb +51 -0
- data/lib/cuprum/parameter_validation/validator.rb +109 -0
- data/lib/cuprum/parameter_validation.rb +220 -0
- data/lib/cuprum/processing.rb +5 -5
- data/lib/cuprum/result.rb +3 -3
- data/lib/cuprum/result_helpers.rb +3 -3
- data/lib/cuprum/result_list.rb +2 -2
- data/lib/cuprum/rspec/be_a_result_matcher.rb +1 -1
- data/lib/cuprum/rspec/deferred/parameter_validation_examples.rb +81 -0
- data/lib/cuprum/rspec/deferred.rb +8 -0
- data/lib/cuprum/utils/instance_spy.rb +6 -6
- data/lib/cuprum/utils/parameters_mapping.rb +143 -0
- data/lib/cuprum/version.rb +3 -3
- data/lib/cuprum.rb +16 -10
- metadata +13 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 878254bafd0929c73d6762f9fe732bbf647ae7d595c52733a53b1cd4115a308d
|
4
|
+
data.tar.gz: 4612a002c3523ea886274e4463e15d0d7139185cbebb5752e7ab4a0289d2d776
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 537237e0cb5d7013af24907c0f2a7daf4d2967b5ad22bbfe078a76d7fc975df623f0119428ed0a6a962033e7d50ab8b9544b5bc590814ea65cbcc05d1fc5ea0d
|
7
|
+
data.tar.gz: 12fbd72264936ffaba10313fdc5a20ddd0c3003532c83ef47bb4accecf5652864d7dc99975ae96c86caaf00bfe7f8b1a2d206d46ea2626579ff9dcaf661ab019
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.3.0
|
4
|
+
|
5
|
+
The "A Dream Given Form" Update
|
6
|
+
|
7
|
+
As of version 1.3.0, Cuprum will no longer support Ruby 3.0.
|
8
|
+
|
9
|
+
### Commands
|
10
|
+
|
11
|
+
Updated `Cuprum::ExceptionHandling` to re-raise the exception if the `ENV['CUPRUM_RERAISE_EXCEPTIONS']` flag is set.
|
12
|
+
|
13
|
+
Implemented `Command.subclass`, allowing partial application of constructor parameters (including the implementation block).
|
14
|
+
|
15
|
+
Refactored `Command.new(&block)`, which no longer overwrites the `#process` method directly. Commands can now use `super` in the `#process` method to wrap a block implementation (or the parent class implementation if the parent class overrides `#process`).
|
16
|
+
|
17
|
+
**Breaking Change:** The private method `Command#process_block` is now reserved and used to handle commands with block implementations.
|
18
|
+
|
19
|
+
#### Parameter Validation
|
20
|
+
|
21
|
+
Implemented `Cuprum::ParameterValidation`, which provides a DSL for validating a command's parameters prior to evaluating `#process`.
|
22
|
+
|
23
|
+
### Errors
|
24
|
+
|
25
|
+
**Breaking Change:** Corrected the namespace for `Cuprum::Errors::UncaughtException::TYPE` to remove a reference to `cuprum-collections`. If this causes an issue, update your application to reference the constant, rather than a hard-coded value.
|
26
|
+
|
27
|
+
### RSpec
|
28
|
+
|
29
|
+
Implemented `Cuprum::RSpec::Deferred::ParameterValidationExamples`, which provide a shortcut for testing commands that use parameter validation.
|
30
|
+
|
3
31
|
## 1.2.0
|
4
32
|
|
5
33
|
The "Straight On Till Morning" Update
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ On the opposite end of the scale, frameworks such as [Dry::Monads](https://dry-r
|
|
30
30
|
|
31
31
|
## Compatibility
|
32
32
|
|
33
|
-
Cuprum is tested against Ruby (MRI) 3.
|
33
|
+
Cuprum is tested against Ruby (MRI) 3.1 through 3.4.
|
34
34
|
|
35
35
|
## Documentation
|
36
36
|
|
@@ -38,9 +38,11 @@ Code documentation is generated using [YARD](https://yardoc.org/), and can be ge
|
|
38
38
|
|
39
39
|
The full documentation is available via [GitHub Pages](http://sleepingkingstudios.github.io/cuprum), and includes the code documentation as well as a deeper explanation of Cuprum's features and design philosophy. It also includes documentation for prior versions of the gem.
|
40
40
|
|
41
|
+
To generate documentation locally, see the [SleepingKingStudios::Docs](https://github.com/sleepingkingstudios/sleeping_king_studios-docs) gem.
|
42
|
+
|
41
43
|
## License
|
42
44
|
|
43
|
-
Copyright (c) 2017-
|
45
|
+
Copyright (c) 2017-2025 Rob Smith
|
44
46
|
|
45
47
|
Cuprum is released under the [MIT License](https://opensource.org/licenses/MIT).
|
46
48
|
|
data/lib/cuprum/command.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'sleeping_king_studios/tools/toolbox/subclass'
|
4
|
+
|
3
5
|
require 'cuprum/currying'
|
4
6
|
require 'cuprum/processing'
|
5
7
|
require 'cuprum/steps'
|
@@ -69,10 +71,30 @@ module Cuprum
|
|
69
71
|
#
|
70
72
|
# @see Cuprum::Processing
|
71
73
|
class Command
|
74
|
+
extend SleepingKingStudios::Tools::Toolbox::Subclass
|
72
75
|
include Cuprum::Processing
|
73
76
|
include Cuprum::Currying
|
74
77
|
include Cuprum::Steps
|
75
78
|
|
79
|
+
# @!scope class
|
80
|
+
|
81
|
+
# @!method subclass(*class_arguments, **class_keywords, &block)
|
82
|
+
# Creates a subclass with partially applied constructor parameters.
|
83
|
+
#
|
84
|
+
# @param class_arguments [Array] the arguments, if any, to apply to the
|
85
|
+
# constructor. These arguments will be added before any args passed
|
86
|
+
# directly to the constructor.
|
87
|
+
# @param class_keywords [Hash] the keywords, if any, to apply to the
|
88
|
+
# constructor. These keywords will be added before any kwargs passed
|
89
|
+
# directly to the constructor.
|
90
|
+
#
|
91
|
+
# @yield the block, if any, to pass to the constructor. This will be
|
92
|
+
# overriden by a block passed directly to the constructor.
|
93
|
+
#
|
94
|
+
# @return [Class] the generated subclass.
|
95
|
+
|
96
|
+
# @!scope instance
|
97
|
+
|
76
98
|
# Returns a new instance of Cuprum::Command.
|
77
99
|
#
|
78
100
|
# @yield If a block is given, the block is used to define a private #process
|
@@ -88,13 +110,15 @@ module Cuprum
|
|
88
110
|
def initialize(&implementation)
|
89
111
|
return unless implementation
|
90
112
|
|
91
|
-
define_singleton_method :
|
113
|
+
define_singleton_method :process_block, &implementation
|
92
114
|
|
93
|
-
singleton_class.send(:private, :
|
115
|
+
singleton_class.send(:private, :process_block)
|
116
|
+
|
117
|
+
@process_block = true
|
94
118
|
end
|
95
119
|
|
96
120
|
# (see Cuprum::Processing#call)
|
97
|
-
def call(*args, **kwargs, &
|
121
|
+
def call(*args, **kwargs, &)
|
98
122
|
steps { super }
|
99
123
|
end
|
100
124
|
|
@@ -115,5 +139,18 @@ module Cuprum
|
|
115
139
|
end
|
116
140
|
end
|
117
141
|
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# (see Cuprum::Processing#process)
|
146
|
+
#
|
147
|
+
# @!visibility public
|
148
|
+
def process(...)
|
149
|
+
process_block? ? process_block(...) : super
|
150
|
+
end
|
151
|
+
|
152
|
+
def process_block?
|
153
|
+
@process_block
|
154
|
+
end
|
118
155
|
end
|
119
156
|
end
|
@@ -98,9 +98,9 @@ module Cuprum
|
|
98
98
|
guard_abstract_factory!
|
99
99
|
|
100
100
|
if klass
|
101
|
-
define_command_from_class(klass, name
|
101
|
+
define_command_from_class(klass, name:, metadata:)
|
102
102
|
elsif block_given?
|
103
|
-
define_command_from_block(defn, name
|
103
|
+
define_command_from_block(defn, name:, metadata:)
|
104
104
|
else
|
105
105
|
require_definition!
|
106
106
|
end
|
@@ -287,11 +287,11 @@ module Cuprum
|
|
287
287
|
|
288
288
|
private
|
289
289
|
|
290
|
-
def build_command(command_class, *args, **kwargs, &
|
290
|
+
def build_command(command_class, *args, **kwargs, &)
|
291
291
|
if kwargs.empty?
|
292
|
-
command_class.new(*args, &
|
292
|
+
command_class.new(*args, &)
|
293
293
|
else
|
294
|
-
command_class.new(*args, **kwargs, &
|
294
|
+
command_class.new(*args, **kwargs, &)
|
295
295
|
end
|
296
296
|
end
|
297
297
|
|
@@ -106,11 +106,13 @@ module Cuprum::Currying
|
|
106
106
|
args = [*arguments, *args]
|
107
107
|
kwargs = keywords.merge(kwargs)
|
108
108
|
|
109
|
+
# rubocop:disable Style/RedundantParentheses
|
109
110
|
if kwargs.empty?
|
110
111
|
command.call(*args, &(override || block))
|
111
112
|
else
|
112
113
|
command.call(*args, **kwargs, &(override || block))
|
113
114
|
end
|
115
|
+
# rubocop:enable Style/RedundantParentheses
|
114
116
|
end
|
115
117
|
end
|
116
118
|
end
|
data/lib/cuprum/currying.rb
CHANGED
@@ -69,10 +69,10 @@ module Cuprum
|
|
69
69
|
return self if arguments.empty? && keywords.empty? && block.nil?
|
70
70
|
|
71
71
|
Cuprum::Currying::CurriedCommand.new(
|
72
|
-
arguments
|
73
|
-
block
|
72
|
+
arguments:,
|
73
|
+
block:,
|
74
74
|
command: self,
|
75
|
-
keywords:
|
75
|
+
keywords:
|
76
76
|
)
|
77
77
|
end
|
78
78
|
end
|
data/lib/cuprum/error.rb
CHANGED
@@ -65,7 +65,7 @@ module Cuprum
|
|
65
65
|
def initialize(message: nil, type: nil, **properties)
|
66
66
|
@message = message
|
67
67
|
@type = type || self.class::TYPE
|
68
|
-
@comparable_properties = properties.merge(message
|
68
|
+
@comparable_properties = properties.merge(message:, type:)
|
69
69
|
end
|
70
70
|
|
71
71
|
# @return [String] optional message describing the nature of the error.
|
@@ -22,7 +22,7 @@ module Cuprum::Errors
|
|
22
22
|
class_name = command&.class&.name || 'command'
|
23
23
|
message = MESSAGE_FORMAT % class_name
|
24
24
|
|
25
|
-
super(command
|
25
|
+
super(command:, message:)
|
26
26
|
end
|
27
27
|
|
28
28
|
# @return [Cuprum::Command] The command called without a definition.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/error'
|
4
|
+
require 'cuprum/errors'
|
5
|
+
|
6
|
+
module Cuprum::Errors
|
7
|
+
# Error returned when a command's parameters fail validation.
|
8
|
+
class InvalidParameters < Cuprum::Error
|
9
|
+
# Short string used to identify the type of error.
|
10
|
+
TYPE = 'cuprum.errors.invalid_parameters'
|
11
|
+
|
12
|
+
# @param command_class [Class] the class of the failed command.
|
13
|
+
# @param failures [Array<String>] the messages for the failed validations.
|
14
|
+
def initialize(command_class:, failures:)
|
15
|
+
@command_class = command_class
|
16
|
+
@failures = failures
|
17
|
+
|
18
|
+
super(
|
19
|
+
command_class:,
|
20
|
+
failures:,
|
21
|
+
message: generate_message
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Class] the class of the failed command.
|
26
|
+
attr_reader :command_class
|
27
|
+
|
28
|
+
# @return [Array<String>] the messages for the failed validations.
|
29
|
+
attr_reader :failures
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def as_json_data
|
34
|
+
{
|
35
|
+
'command_class' => command_class.name,
|
36
|
+
'failures' => failures.map(&:to_s)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_message
|
41
|
+
"invalid parameters for #{command_class.name} - #{failures.join(', ')}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -19,7 +19,7 @@ module Cuprum::Errors
|
|
19
19
|
class_name = operation&.class&.name || 'operation'
|
20
20
|
message = MESSAGE_FORMAT % class_name
|
21
21
|
|
22
|
-
super(message
|
22
|
+
super(message:, operation:)
|
23
23
|
end
|
24
24
|
|
25
25
|
# @return [Cuprum::Operation] The uncalled operation.
|
@@ -7,7 +7,7 @@ module Cuprum::Errors
|
|
7
7
|
# Error returned when a command encounters an unhandled exception.
|
8
8
|
class UncaughtException < Cuprum::Error
|
9
9
|
# Short string used to identify the type of error.
|
10
|
-
TYPE = 'cuprum.
|
10
|
+
TYPE = 'cuprum.errors.uncaught_exception'
|
11
11
|
|
12
12
|
# @param exception [StandardError] The exception that was raised.
|
13
13
|
# @param message [String] A message to display. Will be annotated with
|
data/lib/cuprum/errors.rb
CHANGED
@@ -6,6 +6,7 @@ module Cuprum
|
|
6
6
|
# Namespace for custom Cuprum error classes.
|
7
7
|
module Errors
|
8
8
|
autoload :CommandNotImplemented, 'cuprum/errors/command_not_implemented'
|
9
|
+
autoload :InvalidParameters, 'cuprum/errors/invalid_parameters'
|
9
10
|
autoload :MultipleErrors, 'cuprum/errors/multiple_errors'
|
10
11
|
autoload :OperationNotCalled, 'cuprum/errors/operation_not_called'
|
11
12
|
autoload :UncaughtException, 'cuprum/errors/uncaught_exception'
|
@@ -5,6 +5,10 @@ require 'cuprum/errors/uncaught_exception'
|
|
5
5
|
module Cuprum
|
6
6
|
# Utility module for handling uncaught exceptions in commands.
|
7
7
|
#
|
8
|
+
# This functionality can be temporarily disabled by setting the
|
9
|
+
# ENV['CUPRUM_RERAISE_EXCEPTIONS'] flag; this can be used to debug issues when
|
10
|
+
# testing commands.
|
11
|
+
#
|
8
12
|
# @example
|
9
13
|
# class UnsafeCommand < Cuprum::Command
|
10
14
|
# private
|
@@ -39,11 +43,16 @@ module Cuprum
|
|
39
43
|
# a failing result if a StandardError is raised.
|
40
44
|
#
|
41
45
|
# @see Cuprum::Processing#call
|
42
|
-
|
46
|
+
#
|
47
|
+
# @raise StandardError if an exception is raised and the
|
48
|
+
# ENV['CUPRUM_RERAISE_EXCEPTIONS'] flag is set.
|
49
|
+
def call(*args, **kwargs, &)
|
43
50
|
super
|
44
51
|
rescue StandardError => exception
|
52
|
+
raise exception if ENV['CUPRUM_RERAISE_EXCEPTIONS']
|
53
|
+
|
45
54
|
error = Cuprum::Errors::UncaughtException.new(
|
46
|
-
exception
|
55
|
+
exception:,
|
47
56
|
message: "uncaught exception in #{self.class.name} - "
|
48
57
|
)
|
49
58
|
failure(error)
|
data/lib/cuprum/map_command.rb
CHANGED
@@ -221,6 +221,10 @@ module Cuprum
|
|
221
221
|
def splat_items?
|
222
222
|
return @splat_items unless @splat_items.nil?
|
223
223
|
|
224
|
+
if respond_to?(:process_block, true)
|
225
|
+
return @splat_items = method(:process_block).arity > 1
|
226
|
+
end
|
227
|
+
|
224
228
|
@splat_items = method(:process).arity > 1
|
225
229
|
end
|
226
230
|
end
|
data/lib/cuprum/matcher.rb
CHANGED
@@ -69,10 +69,10 @@ module Cuprum
|
|
69
69
|
#
|
70
70
|
# @yield Executes the block in the context of the singleton class. This is
|
71
71
|
# used to define match clauses when instantiating a Matcher instance.
|
72
|
-
def initialize(match_context = nil, &
|
72
|
+
def initialize(match_context = nil, &)
|
73
73
|
@match_context = match_context
|
74
74
|
|
75
|
-
singleton_class.instance_exec(&
|
75
|
+
singleton_class.instance_exec(&) if block_given?
|
76
76
|
end
|
77
77
|
|
78
78
|
# Returns a copy of the matcher with the given execution context.
|
data/lib/cuprum/matcher_list.rb
CHANGED
@@ -113,20 +113,20 @@ module Cuprum
|
|
113
113
|
|
114
114
|
def find_exact_match(result)
|
115
115
|
matchers.find do |matcher|
|
116
|
-
exact_match?(matcher
|
116
|
+
exact_match?(matcher:, result:)
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
120
|
def find_generic_match(result)
|
121
121
|
matchers.find do |matcher|
|
122
|
-
generic_match?(matcher
|
122
|
+
generic_match?(matcher:, result:)
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
def find_partial_match(result)
|
127
127
|
matchers.find do |matcher|
|
128
|
-
error_match?(matcher
|
129
|
-
value_match?(matcher
|
128
|
+
error_match?(matcher:, result:) ||
|
129
|
+
value_match?(matcher:, result:)
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
data/lib/cuprum/matching.rb
CHANGED
@@ -40,21 +40,21 @@ module Cuprum
|
|
40
40
|
# @private
|
41
41
|
def match_result(result:)
|
42
42
|
status_clauses(result.status).find do |clause|
|
43
|
-
clause.matches_result?(result:
|
43
|
+
clause.matches_result?(result:)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
# @private
|
48
48
|
def matches_result?(result:)
|
49
49
|
status_clauses(result.status).reverse_each.any? do |clause|
|
50
|
-
clause.matches_result?(result:
|
50
|
+
clause.matches_result?(result:)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
# @private
|
55
55
|
def matches_status?(error:, status:, value:)
|
56
56
|
status_clauses(status).reverse_each.any? do |clause|
|
57
|
-
clause.matches_details?(error
|
57
|
+
clause.matches_details?(error:, value:)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -174,11 +174,11 @@ module Cuprum
|
|
174
174
|
end
|
175
175
|
|
176
176
|
result = result.to_cuprum_result
|
177
|
-
clause = singleton_class.match_result(result:
|
177
|
+
clause = singleton_class.match_result(result:)
|
178
178
|
|
179
179
|
raise NoMatchError, "no match found for #{result.inspect}" if clause.nil?
|
180
180
|
|
181
|
-
call_match(block: clause.block, result:
|
181
|
+
call_match(block: clause.block, result:)
|
182
182
|
end
|
183
183
|
|
184
184
|
# @return [Boolean] true if an execution context is defined for a matching
|
@@ -214,9 +214,9 @@ module Cuprum
|
|
214
214
|
)
|
215
215
|
elsif result_or_status.is_a?(Symbol)
|
216
216
|
return singleton_class.matches_status?(
|
217
|
-
error
|
217
|
+
error:,
|
218
218
|
status: result_or_status,
|
219
|
-
value:
|
219
|
+
value:
|
220
220
|
)
|
221
221
|
end
|
222
222
|
|
data/lib/cuprum/operation.rb
CHANGED
@@ -63,7 +63,7 @@ module Cuprum
|
|
63
63
|
# implementation.
|
64
64
|
#
|
65
65
|
# @see Cuprum::Command#call
|
66
|
-
def call(*args, **kwargs, &
|
66
|
+
def call(*args, **kwargs, &)
|
67
67
|
reset! if called? # Clear reference to most recent result.
|
68
68
|
|
69
69
|
@result = super
|
@@ -123,7 +123,7 @@ module Cuprum
|
|
123
123
|
|
124
124
|
error = Cuprum::Errors::OperationNotCalled.new(operation: self)
|
125
125
|
|
126
|
-
Cuprum::Result.new(error:
|
126
|
+
Cuprum::Result.new(error:)
|
127
127
|
end
|
128
128
|
|
129
129
|
# @return [Object] the value of the most recent result, or nil if the
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/parameter_validation'
|
4
|
+
|
5
|
+
module Cuprum::ParameterValidation
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# Value class representing a single validation for a parameter.
|
9
|
+
class ValidationRule < Struct.new( # rubocop:disable Style/StructInheritance
|
10
|
+
:name,
|
11
|
+
:type,
|
12
|
+
:method_name,
|
13
|
+
:options,
|
14
|
+
:block,
|
15
|
+
keyword_init: true
|
16
|
+
)
|
17
|
+
# Custom validation type for a block validation.
|
18
|
+
BLOCK_VALIDATION_TYPE = :_block_validation
|
19
|
+
|
20
|
+
# Custom validation type for a named method validation.
|
21
|
+
NAMED_VALIDATION_TYPE = :_named_method_validation
|
22
|
+
|
23
|
+
# @param name [Symbol] the name of the parameter to validate.
|
24
|
+
# @param type [Symbol] the type of validation to perform.
|
25
|
+
# @param method_name [Symbol] the name for the validation method.
|
26
|
+
# @param options [Hash] additional options to pass to the validator.
|
27
|
+
# @param block [Proc] a block to pass to the validator, if any.
|
28
|
+
def initialize(name:, type:, method_name: nil, **options, &block)
|
29
|
+
super(
|
30
|
+
block:,
|
31
|
+
method_name: method_name&.to_sym || method_name_for(name:, type:),
|
32
|
+
name: name.to_sym,
|
33
|
+
options:,
|
34
|
+
type: type.to_sym
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def method_name_for(name:, type:)
|
41
|
+
case type
|
42
|
+
when BLOCK_VALIDATION_TYPE
|
43
|
+
:validate
|
44
|
+
when NAMED_VALIDATION_TYPE
|
45
|
+
:"validate_#{name}"
|
46
|
+
else
|
47
|
+
:"validate_#{type}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sleeping_king_studios/tools/assertions'
|
4
|
+
|
5
|
+
require 'cuprum/errors/invalid_parameters'
|
6
|
+
require 'cuprum/parameter_validation'
|
7
|
+
|
8
|
+
module Cuprum::ParameterValidation
|
9
|
+
# Utility class for validating mapped parameters.
|
10
|
+
class Validator
|
11
|
+
# Exception raised when performing an unknown validation type.
|
12
|
+
class UnknownValidationError < StandardError; end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@failures = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Validates the given parameters.
|
19
|
+
#
|
20
|
+
# @param command [Object] the command whose parameters are validated.
|
21
|
+
# @param parameters [Hash{Symbol=>Object}] the parameters to validate.
|
22
|
+
# @param rules [Array<ValidationRule>] the rules used to validate the
|
23
|
+
# parameters.
|
24
|
+
#
|
25
|
+
# @return [Cuprum::Result] a result with the validation errors, if any.
|
26
|
+
def call(command:, parameters:, rules:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
27
|
+
validator.clear
|
28
|
+
|
29
|
+
rules.each do |rule|
|
30
|
+
value = parameters[rule.name]
|
31
|
+
|
32
|
+
if rule.type == ValidationRule::BLOCK_VALIDATION_TYPE
|
33
|
+
evaluate_block_validation(rule:, value:)
|
34
|
+
elsif command.respond_to?(rule.method_name, true)
|
35
|
+
evaluate_command_validation(command:, rule:, value:)
|
36
|
+
elsif validator.respond_to?(rule.method_name)
|
37
|
+
evaluate_validation(rule:, value:)
|
38
|
+
else
|
39
|
+
raise UnknownValidationError, error_message_for(command:, rule:)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
handle_failures_for(command)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def error_message_for(command:, rule:)
|
49
|
+
unless rule.type == ValidationRule::NAMED_VALIDATION_TYPE
|
50
|
+
return "unknown validation type #{rule.type.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
"undefined method '#{rule.method_name}' for an instance of " \
|
54
|
+
"#{command.class.name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def evaluate_block_validation(rule:, value:)
|
58
|
+
return if rule.block.call(value)
|
59
|
+
|
60
|
+
message = rule.options.fetch(
|
61
|
+
:message,
|
62
|
+
"#{rule.options.fetch(:as, rule.name)} is invalid"
|
63
|
+
)
|
64
|
+
validator << message
|
65
|
+
end
|
66
|
+
|
67
|
+
def evaluate_command_validation(command:, rule:, value:) # rubocop:disable Metrics/MethodLength
|
68
|
+
message = command.send(
|
69
|
+
rule.method_name,
|
70
|
+
value,
|
71
|
+
as: rule.name,
|
72
|
+
**rule.options
|
73
|
+
)
|
74
|
+
|
75
|
+
if message.is_a?(Array)
|
76
|
+
message.each { |item| validator << item }
|
77
|
+
elsif message
|
78
|
+
validator << message
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def evaluate_validation(rule:, value:)
|
83
|
+
validator.send(
|
84
|
+
rule.method_name,
|
85
|
+
value,
|
86
|
+
as: rule.name,
|
87
|
+
**rule.options
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_failures_for(command)
|
92
|
+
return Cuprum::Result.new if validator.empty?
|
93
|
+
|
94
|
+
error = Cuprum::Errors::InvalidParameters.new(
|
95
|
+
command_class: command.class,
|
96
|
+
failures: validator.each.to_a
|
97
|
+
)
|
98
|
+
Cuprum::Result.new(error:)
|
99
|
+
end
|
100
|
+
|
101
|
+
def tools
|
102
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
103
|
+
end
|
104
|
+
|
105
|
+
def validator
|
106
|
+
@validator ||= tools.assertions.aggregator_class.new
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|