domainic-attributer 0.1.0 → 0.2.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 +32 -1
- data/README.md +42 -355
- data/docs/USAGE.md +723 -0
- data/lib/domainic/attributer/attribute/callback.rb +21 -9
- data/lib/domainic/attributer/attribute/coercer.rb +28 -13
- data/lib/domainic/attributer/attribute/mixin/belongs_to_attribute.rb +16 -13
- data/lib/domainic/attributer/attribute/signature.rb +43 -32
- data/lib/domainic/attributer/attribute/validator.rb +46 -16
- data/lib/domainic/attributer/attribute.rb +28 -18
- data/lib/domainic/attributer/attribute_set.rb +21 -19
- data/lib/domainic/attributer/class_methods.rb +136 -83
- data/lib/domainic/attributer/dsl/attribute_builder/option_parser.rb +64 -22
- data/lib/domainic/attributer/dsl/attribute_builder.rb +515 -26
- data/lib/domainic/attributer/dsl/initializer.rb +23 -18
- data/lib/domainic/attributer/dsl/method_injector.rb +16 -14
- data/lib/domainic/attributer/errors/aggregate_error.rb +36 -0
- data/lib/domainic/attributer/errors/callback_execution_error.rb +30 -0
- data/lib/domainic/attributer/errors/coercion_execution_error.rb +37 -0
- data/lib/domainic/attributer/errors/error.rb +19 -0
- data/lib/domainic/attributer/errors/validation_execution_error.rb +30 -0
- data/lib/domainic/attributer/instance_methods.rb +11 -8
- data/lib/domainic/attributer/undefined.rb +9 -7
- data/lib/domainic/attributer.rb +88 -27
- data/sig/domainic/attributer/attribute/callback.rbs +10 -7
- data/sig/domainic/attributer/attribute/coercer.rbs +14 -11
- data/sig/domainic/attributer/attribute/mixin/belongs_to_attribute.rbs +14 -12
- data/sig/domainic/attributer/attribute/signature.rbs +43 -32
- data/sig/domainic/attributer/attribute/validator.rbs +28 -13
- data/sig/domainic/attributer/attribute.rbs +27 -17
- data/sig/domainic/attributer/attribute_set.rbs +21 -19
- data/sig/domainic/attributer/class_methods.rbs +133 -80
- data/sig/domainic/attributer/dsl/attribute_builder/option_parser.rbs +62 -22
- data/sig/domainic/attributer/dsl/attribute_builder.rbs +515 -26
- data/sig/domainic/attributer/dsl/initializer.rbs +21 -19
- data/sig/domainic/attributer/dsl/method_injector.rbs +16 -14
- data/sig/domainic/attributer/errors/aggregate_error.rbs +28 -0
- data/sig/domainic/attributer/errors/callback_execution_error.rbs +23 -0
- data/sig/domainic/attributer/errors/coercion_execution_error.rbs +29 -0
- data/sig/domainic/attributer/errors/error.rbs +17 -0
- data/sig/domainic/attributer/errors/validation_execution_error.rbs +23 -0
- data/sig/domainic/attributer/instance_methods.rbs +11 -8
- data/sig/domainic/attributer/undefined.rbs +5 -3
- data/sig/domainic/attributer.rbs +88 -27
- metadata +19 -6
@@ -1,16 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'domainic/attributer/attribute/mixin/belongs_to_attribute'
|
4
|
+
require 'domainic/attributer/errors/callback_execution_error'
|
4
5
|
|
5
6
|
module Domainic
|
6
7
|
module Attributer
|
7
8
|
class Attribute
|
8
|
-
# A class responsible for managing change callbacks for an attribute
|
9
|
+
# A class responsible for managing change callbacks for an attribute
|
9
10
|
#
|
10
11
|
# This class handles the execution of callbacks that are triggered when an
|
11
12
|
# attribute's value changes. Each callback must be a Proc that accepts two
|
12
|
-
# arguments: the old value and the new value
|
13
|
+
# arguments: the old value, and the new value
|
13
14
|
#
|
15
|
+
# @api private
|
16
|
+
# @!visibility private
|
14
17
|
# @author {https://aaronmallen.me Aaron Allen}
|
15
18
|
# @since 0.1.0
|
16
19
|
class Callback
|
@@ -21,12 +24,12 @@ module Domainic
|
|
21
24
|
|
22
25
|
include BelongsToAttribute
|
23
26
|
|
24
|
-
# Initialize a new Callback instance
|
27
|
+
# Initialize a new Callback instance
|
25
28
|
#
|
26
|
-
# @param attribute [Attribute] the
|
29
|
+
# @param attribute [Attribute] the {Attribute} this instance belongs to
|
27
30
|
# @param handlers [Array<Proc>] the handlers to use for processing
|
28
31
|
#
|
29
|
-
# @return [Callback] the new instance
|
32
|
+
# @return [Callback] the new Callback instance
|
30
33
|
# @rbs (Attribute attribute, Array[handler] | handler handlers) -> void
|
31
34
|
def initialize(attribute, handlers = [])
|
32
35
|
super
|
@@ -36,21 +39,30 @@ module Domainic
|
|
36
39
|
end.uniq
|
37
40
|
end
|
38
41
|
|
39
|
-
# Execute all callbacks for a value change
|
42
|
+
# Execute all callbacks for a value change
|
40
43
|
#
|
41
44
|
# @param instance [Object] the instance on which to execute callbacks
|
42
45
|
# @param old_value [Object] the previous value
|
43
46
|
# @param new_value [Object] the new value
|
44
47
|
#
|
48
|
+
# @raise [CallbackExecutionError] if any callback handlers raises an error
|
45
49
|
# @return [void]
|
46
50
|
# @rbs (untyped instance, untyped old_value, untyped new_value) -> void
|
47
51
|
def call(instance, old_value, new_value)
|
48
|
-
|
52
|
+
errors = []
|
53
|
+
|
54
|
+
@handlers.each do |handler|
|
55
|
+
instance.instance_exec(old_value, new_value, &handler)
|
56
|
+
rescue StandardError => e
|
57
|
+
errors << e
|
58
|
+
end
|
59
|
+
|
60
|
+
raise CallbackExecutionError, errors unless errors.empty?
|
49
61
|
end
|
50
62
|
|
51
63
|
private
|
52
64
|
|
53
|
-
# Validate that a callback handler is a valid Proc
|
65
|
+
# Validate that a callback handler is a valid Proc
|
54
66
|
#
|
55
67
|
# @param handler [Object] the handler to validate
|
56
68
|
#
|
@@ -60,7 +72,7 @@ module Domainic
|
|
60
72
|
def validate_handler!(handler)
|
61
73
|
return if handler.is_a?(Proc)
|
62
74
|
|
63
|
-
raise TypeError, "`#{attribute_method_name}`: invalid handler: #{handler.inspect}. Must be a Proc
|
75
|
+
raise TypeError, "`#{attribute_method_name}`: invalid handler: #{handler.inspect}. Must be a Proc"
|
64
76
|
end
|
65
77
|
end
|
66
78
|
end
|
@@ -1,16 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'domainic/attributer/attribute/mixin/belongs_to_attribute'
|
4
|
+
require 'domainic/attributer/errors/coercion_execution_error'
|
5
|
+
require 'domainic/attributer/undefined'
|
4
6
|
|
5
7
|
module Domainic
|
6
8
|
module Attributer
|
7
9
|
class Attribute
|
8
|
-
# A class responsible for coercing attribute values
|
10
|
+
# A class responsible for coercing attribute values
|
9
11
|
#
|
10
12
|
# This class manages the coercion of values assigned to an attribute. Coercion can be
|
11
|
-
# handled by either a Proc that accepts a single value argument, or by referencing an
|
12
|
-
# instance method via Symbol
|
13
|
+
# handled by either a {Proc} that accepts a single value argument, or by referencing an
|
14
|
+
# instance method via {Symbol}
|
13
15
|
#
|
16
|
+
# @api private
|
17
|
+
# @!visibility private
|
14
18
|
# @author {https://aaronmallen.me Aaron Allen}
|
15
19
|
# @since 0.1.0
|
16
20
|
class Coercer
|
@@ -23,12 +27,12 @@ module Domainic
|
|
23
27
|
|
24
28
|
# @rbs @handlers: Array[handler]
|
25
29
|
|
26
|
-
# Initialize a new Coercer instance
|
30
|
+
# Initialize a new {Coercer} instance
|
27
31
|
#
|
28
|
-
# @param attribute [Attribute] the
|
32
|
+
# @param attribute [Attribute] the {Attribute} this instance belongs to
|
29
33
|
# @param handlers [Array<Proc, Symbol>] the handlers to use for processing
|
30
34
|
#
|
31
|
-
# @return [Coercer] the new instance
|
35
|
+
# @return [Coercer] the new Coercer instance
|
32
36
|
# @rbs (Attribute attribute, Array[handler] | handler handlers) -> void
|
33
37
|
def initialize(attribute, handlers = [])
|
34
38
|
super
|
@@ -38,20 +42,31 @@ module Domainic
|
|
38
42
|
end.uniq
|
39
43
|
end
|
40
44
|
|
41
|
-
# Process a value through all coercion handlers
|
45
|
+
# Process a value through all coercion handlers
|
42
46
|
#
|
43
47
|
# @param instance [Object] the instance on which to perform coercion
|
44
48
|
# @param value [Object] the value to coerce
|
45
49
|
#
|
46
|
-
# @
|
47
|
-
# @
|
50
|
+
# @raise [CoercionExecutionError] if a coercion handler raises an error
|
51
|
+
# @return [Object, nil] the coerced value
|
52
|
+
# @rbs (untyped instance, untyped? value) -> untyped?
|
48
53
|
def call(instance, value)
|
49
|
-
|
54
|
+
return value if value == Undefined
|
55
|
+
return value if value.nil? && !@attribute.signature.nilable?
|
56
|
+
|
57
|
+
@handlers.reduce(value) do |accumulator, handler|
|
58
|
+
coerce_value(instance, handler, accumulator)
|
59
|
+
rescue StandardError => e
|
60
|
+
raise CoercionExecutionError.new(
|
61
|
+
"Failed to coerce #{accumulator.inspect} with #{handler.inspect}: #{e.message}",
|
62
|
+
handler
|
63
|
+
)
|
64
|
+
end
|
50
65
|
end
|
51
66
|
|
52
67
|
private
|
53
68
|
|
54
|
-
# Process a value through a single coercion handler
|
69
|
+
# Process a value through a single coercion handler
|
55
70
|
#
|
56
71
|
# @param instance [Object] the instance on which to perform coercion
|
57
72
|
# @param handler [Proc, Symbol] the coercion handler
|
@@ -67,12 +82,12 @@ module Domainic
|
|
67
82
|
when Symbol
|
68
83
|
instance.send(handler, value)
|
69
84
|
else
|
70
|
-
# We should never get here because we validate the handlers in the initializer
|
85
|
+
# We should never get here because we validate the handlers in the initializer
|
71
86
|
raise TypeError, "`#{attribute_method_name}`: invalid coercer: #{handler}. "
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
75
|
-
# Validate that a coercion handler is valid
|
90
|
+
# Validate that a coercion handler is valid
|
76
91
|
#
|
77
92
|
# @param handler [Object] the handler to validate
|
78
93
|
#
|
@@ -1,35 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'domainic/attributer/attribute'
|
4
|
+
|
3
5
|
module Domainic
|
4
6
|
module Attributer
|
5
7
|
class Attribute
|
6
|
-
# A mixin providing common functionality for classes that belong to an Attribute
|
8
|
+
# A mixin providing common functionality for classes that belong to an Attribute
|
7
9
|
#
|
8
10
|
# This module provides initialization and duplication behavior for classes that are owned
|
9
11
|
# by and work in conjunction with an Attribute instance. These classes typically handle
|
10
|
-
# specific aspects of attribute processing such as coercion, validation, or callbacks
|
12
|
+
# specific aspects of attribute processing such as coercion, validation, or callbacks
|
11
13
|
#
|
14
|
+
# @api private
|
15
|
+
# @!visibility private
|
12
16
|
# @author {https://aaronmallen.me Aaron Allen}
|
13
17
|
# @since 0.1.0
|
14
18
|
module BelongsToAttribute
|
15
19
|
# @rbs @attribute: Attribute
|
16
20
|
|
17
|
-
# Initialize a new instance that belongs to an
|
21
|
+
# Initialize a new instance that belongs to an {Attribute}
|
18
22
|
#
|
19
|
-
# @param attribute [Attribute] the
|
23
|
+
# @param attribute [Attribute] the {Attribute} this instance belongs to
|
20
24
|
#
|
21
|
-
# @return [
|
25
|
+
# @return [BelongsToAttribute] the new BelongsToAttribute instance
|
22
26
|
# @rbs (Attribute attribute, *untyped, **untyped) -> void
|
23
27
|
def initialize(attribute, ...)
|
24
28
|
validate_attribute!(attribute)
|
25
29
|
@attribute = attribute
|
26
30
|
end
|
27
31
|
|
28
|
-
# Create a duplicate instance associated with a new
|
32
|
+
# Create a duplicate instance associated with a new {Attribute}
|
29
33
|
#
|
30
34
|
# @param new_attribute [Attribute] the new attribute to associate with
|
31
35
|
#
|
32
|
-
# @return [BelongsToAttribute]
|
36
|
+
# @return [BelongsToAttribute] duplicate instance with new {Attribute}
|
33
37
|
# @rbs (Attribute attribute) -> BelongsToAttribute
|
34
38
|
def dup_with_attribute(new_attribute)
|
35
39
|
validate_attribute!(new_attribute)
|
@@ -39,24 +43,23 @@ module Domainic
|
|
39
43
|
|
40
44
|
private
|
41
45
|
|
42
|
-
# Generate a method name for error messages
|
46
|
+
# Generate a method name for error messages
|
43
47
|
#
|
44
|
-
# @return [String]
|
48
|
+
# @return [String] formatted method name
|
45
49
|
# @rbs () -> String
|
46
50
|
def attribute_method_name
|
47
51
|
"#{@attribute.base}##{@attribute.name}"
|
48
52
|
end
|
49
53
|
|
50
|
-
# Ensure that an
|
54
|
+
# Ensure that an {Attribute} is a valid {Attribute} instance
|
51
55
|
#
|
52
|
-
# @param attribute [Attribute] the
|
56
|
+
# @param attribute [Attribute] the {Attribute} to validate
|
53
57
|
#
|
54
|
-
# @raise [TypeError] if the attribute is not a valid
|
58
|
+
# @raise [TypeError] if the attribute is not a valid Attribute instance
|
55
59
|
# @return [void]
|
56
60
|
# @rbs (Attribute attribute) -> void
|
57
61
|
def validate_attribute!(attribute)
|
58
62
|
return if attribute.is_a?(Attribute)
|
59
|
-
return if defined?(RSpec::Mocks::TestDouble) && attribute.is_a?(RSpec::Mocks::TestDouble)
|
60
63
|
|
61
64
|
raise TypeError,
|
62
65
|
"invalid attribute: #{attribute.inspect}. Must be an Domainic::Attributer::Attribute instance"
|
@@ -5,12 +5,14 @@ require 'domainic/attributer/attribute/mixin/belongs_to_attribute'
|
|
5
5
|
module Domainic
|
6
6
|
module Attributer
|
7
7
|
class Attribute
|
8
|
-
# A class responsible for managing attribute signature information
|
8
|
+
# A class responsible for managing attribute signature information
|
9
9
|
#
|
10
|
-
# This class encapsulates the type and visibility configuration for an
|
10
|
+
# This class encapsulates the type and visibility configuration for an {Attribute}
|
11
11
|
# It validates and manages whether an attribute is an argument or option, as well
|
12
|
-
# as controlling read and write visibility (public, protected, or private)
|
12
|
+
# as controlling read and write visibility (public, protected, or private)
|
13
13
|
#
|
14
|
+
# @api private
|
15
|
+
# @!visibility private
|
14
16
|
# @author {https://aaronmallen.me Aaron Allen}
|
15
17
|
# @since 0.1.0
|
16
18
|
class Signature
|
@@ -37,10 +39,10 @@ module Domainic
|
|
37
39
|
|
38
40
|
include BelongsToAttribute
|
39
41
|
|
40
|
-
# @return [Hash{Symbol => Object}] Default options for a new Signature instance
|
42
|
+
# @return [Hash{Symbol => Object}] Default options for a new Signature instance
|
41
43
|
DEFAULT_OPTIONS = { nilable: true, read: :public, required: false, write: :public }.freeze #: default_options
|
42
44
|
|
43
|
-
# Constants defining valid attribute types
|
45
|
+
# Constants defining valid attribute types
|
44
46
|
#
|
45
47
|
# @author {https://aaronmallen.me Aaron Allen}
|
46
48
|
# @since 0.1.0
|
@@ -55,7 +57,7 @@ module Domainic
|
|
55
57
|
ALL = [ARGUMENT, OPTION].freeze #: Array[type_symbol]
|
56
58
|
end
|
57
59
|
|
58
|
-
# Constants defining valid visibility levels
|
60
|
+
# Constants defining valid visibility levels
|
59
61
|
#
|
60
62
|
# @author {https://aaronmallen.me Aaron Allen}
|
61
63
|
# @since 0.1.0
|
@@ -80,30 +82,39 @@ module Domainic
|
|
80
82
|
# @rbs @type: type_symbol
|
81
83
|
# @rbs @write_visibility: visibility_symbol
|
82
84
|
|
85
|
+
# Get the position of the attribute
|
86
|
+
#
|
83
87
|
# @return [Integer, nil] the position of the attribute
|
84
88
|
attr_reader :position #: Integer?
|
85
89
|
|
90
|
+
# Get the visibility level for reading the attribute
|
91
|
+
#
|
86
92
|
# @return [Symbol] the visibility level for reading the attribute
|
87
93
|
attr_reader :read_visibility #: visibility_symbol
|
88
94
|
|
95
|
+
# Get the attribute type
|
96
|
+
#
|
89
97
|
# @return [Symbol] the type of the attribute
|
90
98
|
attr_reader :type #: type_symbol
|
91
99
|
|
100
|
+
# Get the visibility level for writing the attribute
|
101
|
+
#
|
92
102
|
# @return [Symbol] the visibility level for writing the attribute
|
93
103
|
attr_reader :write_visibility #: visibility_symbol
|
94
104
|
|
95
|
-
# Initialize a new Signature instance
|
105
|
+
# Initialize a new {Signature} instance
|
96
106
|
#
|
97
|
-
# @param attribute [Attribute] the
|
107
|
+
# @param attribute [Attribute] the {Attribute} this instance belongs to
|
98
108
|
# @param options [Hash{Symbol => Object}] the signature options
|
99
|
-
# @option options [Boolean] nilable (true) whether the attribute is allowed to be nil
|
109
|
+
# @option options [Boolean] nilable (true) whether the attribute is allowed to be nil
|
100
110
|
# @option options [Integer, nil] position (nil) optional position for ordered attributes
|
101
111
|
# @option options [Symbol] read (:public) the read visibility
|
102
112
|
# @option options [Boolean] required (false) whether the attribute is required
|
103
113
|
# @option options [Symbol] type the type of attribute
|
104
114
|
# @option options [Symbol] write (:public) the write visibility
|
105
115
|
#
|
106
|
-
# @
|
116
|
+
# @raise [ArgumentError] if the configuration is invalid
|
117
|
+
# @return [Signature] the new Signature instance
|
107
118
|
# @rbs (
|
108
119
|
# Attribute attribute,
|
109
120
|
# ?nilable: bool,
|
@@ -127,7 +138,7 @@ module Domainic
|
|
127
138
|
@write_visibility = options.fetch(:write).to_sym
|
128
139
|
end
|
129
140
|
|
130
|
-
# Check if this signature is for an argument attribute
|
141
|
+
# Check if this signature is for an argument attribute
|
131
142
|
#
|
132
143
|
# @return [Boolean] true if this is an argument attribute
|
133
144
|
# @rbs () -> bool
|
@@ -135,15 +146,15 @@ module Domainic
|
|
135
146
|
@type == TYPE::ARGUMENT
|
136
147
|
end
|
137
148
|
|
138
|
-
# Check if the attribute
|
149
|
+
# Check if the attribute allows nil values
|
139
150
|
#
|
140
|
-
# @return [Boolean] true if the attribute
|
151
|
+
# @return [Boolean] true if the attribute allows nil values
|
141
152
|
# @rbs () -> bool
|
142
153
|
def nilable?
|
143
154
|
@nilable
|
144
155
|
end
|
145
156
|
|
146
|
-
# Check if this signature is for an option attribute
|
157
|
+
# Check if this signature is for an option attribute
|
147
158
|
#
|
148
159
|
# @return [Boolean] true if this is an option attribute
|
149
160
|
# @rbs () -> bool
|
@@ -151,7 +162,7 @@ module Domainic
|
|
151
162
|
@type == TYPE::OPTION
|
152
163
|
end
|
153
164
|
|
154
|
-
# Check if this signature is for an optional attribute
|
165
|
+
# Check if this signature is for an optional attribute
|
155
166
|
#
|
156
167
|
# @return [Boolean] true if this is an optional attribute
|
157
168
|
# @rbs () -> bool
|
@@ -159,7 +170,7 @@ module Domainic
|
|
159
170
|
!required?
|
160
171
|
end
|
161
172
|
|
162
|
-
# Check if both read and write operations are private
|
173
|
+
# Check if both read and write operations are private
|
163
174
|
#
|
164
175
|
# @return [Boolean] true if both read and write are private
|
165
176
|
# @rbs () -> bool
|
@@ -167,7 +178,7 @@ module Domainic
|
|
167
178
|
private_read? && private_write?
|
168
179
|
end
|
169
180
|
|
170
|
-
# Check if read operations are private
|
181
|
+
# Check if read operations are private
|
171
182
|
#
|
172
183
|
# @return [Boolean] true if read operations are private
|
173
184
|
# @rbs () -> bool
|
@@ -175,7 +186,7 @@ module Domainic
|
|
175
186
|
[VISIBILITY::PRIVATE, VISIBILITY::PROTECTED].include?(@read_visibility)
|
176
187
|
end
|
177
188
|
|
178
|
-
# Check if write operations are private
|
189
|
+
# Check if write operations are private
|
179
190
|
#
|
180
191
|
# @return [Boolean] true if write operations are private
|
181
192
|
# @rbs () -> bool
|
@@ -183,7 +194,7 @@ module Domainic
|
|
183
194
|
[VISIBILITY::PRIVATE, VISIBILITY::PROTECTED].include?(@write_visibility)
|
184
195
|
end
|
185
196
|
|
186
|
-
# Check if both read and write operations are protected
|
197
|
+
# Check if both read and write operations are protected
|
187
198
|
#
|
188
199
|
# @return [Boolean] true if both read and write are protected
|
189
200
|
# @rbs () -> bool
|
@@ -191,7 +202,7 @@ module Domainic
|
|
191
202
|
protected_read? && protected_write?
|
192
203
|
end
|
193
204
|
|
194
|
-
# Check if read operations are protected
|
205
|
+
# Check if read operations are protected
|
195
206
|
#
|
196
207
|
# @return [Boolean] true if read operations are protected
|
197
208
|
# @rbs () -> bool
|
@@ -199,7 +210,7 @@ module Domainic
|
|
199
210
|
@read_visibility == VISIBILITY::PROTECTED
|
200
211
|
end
|
201
212
|
|
202
|
-
# Check if write operations are protected
|
213
|
+
# Check if write operations are protected
|
203
214
|
#
|
204
215
|
# @return [Boolean] true if write operations are protected
|
205
216
|
# @rbs () -> bool
|
@@ -207,7 +218,7 @@ module Domainic
|
|
207
218
|
@write_visibility == VISIBILITY::PROTECTED
|
208
219
|
end
|
209
220
|
|
210
|
-
# Check if both read and write operations are public
|
221
|
+
# Check if both read and write operations are public
|
211
222
|
#
|
212
223
|
# @return [Boolean] true if both read and write are public
|
213
224
|
# @rbs () -> bool
|
@@ -215,7 +226,7 @@ module Domainic
|
|
215
226
|
public_read? && public_write?
|
216
227
|
end
|
217
228
|
|
218
|
-
# Check if read operations are public
|
229
|
+
# Check if read operations are public
|
219
230
|
#
|
220
231
|
# @return [Boolean] true if read operations are public
|
221
232
|
# @rbs () -> bool
|
@@ -223,7 +234,7 @@ module Domainic
|
|
223
234
|
@read_visibility == VISIBILITY::PUBLIC
|
224
235
|
end
|
225
236
|
|
226
|
-
# Check if write operations are public
|
237
|
+
# Check if write operations are public
|
227
238
|
#
|
228
239
|
# @return [Boolean] true if write operations are public
|
229
240
|
# @rbs () -> bool
|
@@ -231,9 +242,9 @@ module Domainic
|
|
231
242
|
@write_visibility == VISIBILITY::PUBLIC
|
232
243
|
end
|
233
244
|
|
234
|
-
# Check if
|
245
|
+
# Check if this signature requires an attribute value
|
235
246
|
#
|
236
|
-
# @return [Boolean] true if
|
247
|
+
# @return [Boolean] true if this signature requires an attribute value
|
237
248
|
# @rbs () -> bool
|
238
249
|
def required?
|
239
250
|
@required
|
@@ -241,7 +252,7 @@ module Domainic
|
|
241
252
|
|
242
253
|
private
|
243
254
|
|
244
|
-
# Get signature options as a hash
|
255
|
+
# Get signature options as a hash
|
245
256
|
#
|
246
257
|
# @return [Hash] the signature options
|
247
258
|
# @rbs () -> initialize_options
|
@@ -256,7 +267,7 @@ module Domainic
|
|
256
267
|
}
|
257
268
|
end
|
258
269
|
|
259
|
-
# Validate that a value is a
|
270
|
+
# Validate that a value is a boolean
|
260
271
|
#
|
261
272
|
# @param name [String, Symbol] the name of the attribute being validated
|
262
273
|
# @param value [Boolean] the value to validate
|
@@ -270,7 +281,7 @@ module Domainic
|
|
270
281
|
raise ArgumentError, "`#{attribute_method_name}`: invalid #{name}: #{value}. Must be `true` or `false`."
|
271
282
|
end
|
272
283
|
|
273
|
-
# Validate all initialization options
|
284
|
+
# Validate all initialization options
|
274
285
|
#
|
275
286
|
# @param options [Hash{Symbol => Object}] the options to validate
|
276
287
|
# @option options [Boolean] nilable the nilable flag to validate
|
@@ -291,7 +302,7 @@ module Domainic
|
|
291
302
|
validate_type!(options[:type])
|
292
303
|
end
|
293
304
|
|
294
|
-
# Validate that a position value is valid
|
305
|
+
# Validate that a position value is valid
|
295
306
|
#
|
296
307
|
# @param position [Integer, nil] the position to validate
|
297
308
|
#
|
@@ -304,7 +315,7 @@ module Domainic
|
|
304
315
|
raise ArgumentError, "`#{attribute_method_name}`: invalid position: #{position}. Must be Integer or nil."
|
305
316
|
end
|
306
317
|
|
307
|
-
# Validate that a type value is valid
|
318
|
+
# Validate that a type value is valid
|
308
319
|
#
|
309
320
|
# @param type [Symbol] the type to validate
|
310
321
|
#
|
@@ -318,7 +329,7 @@ module Domainic
|
|
318
329
|
"`#{attribute_method_name}`: invalid type: #{type}. Must be one of #{TYPE::ALL.join(', ')}"
|
319
330
|
end
|
320
331
|
|
321
|
-
# Validate that visibility values are valid
|
332
|
+
# Validate that visibility values are valid
|
322
333
|
#
|
323
334
|
# @param type [Symbol] which visibility setting to validate
|
324
335
|
# @param value [Symbol] the visibility value to validate
|
@@ -1,16 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'domainic/attributer/attribute/mixin/belongs_to_attribute'
|
4
|
+
require 'domainic/attributer/errors/error'
|
5
|
+
require 'domainic/attributer/errors/validation_execution_error'
|
6
|
+
require 'domainic/attributer/undefined'
|
4
7
|
|
5
8
|
module Domainic
|
6
9
|
module Attributer
|
7
10
|
class Attribute
|
8
|
-
# A class responsible for validating attribute values
|
11
|
+
# A class responsible for validating attribute values
|
9
12
|
#
|
10
13
|
# This class manages the validation of values assigned to an attribute. Validation
|
11
|
-
# can be performed either by a Proc that accepts a single value argument and returns
|
12
|
-
# a boolean, or by any object that responds to the `===` operator
|
14
|
+
# can be performed either by a {Proc} that accepts a single value argument and returns
|
15
|
+
# a boolean, or by any object that responds to the `===` operator
|
13
16
|
#
|
17
|
+
# @api private
|
18
|
+
# @!visibility private
|
14
19
|
# @author {https://aaronmallen.me Aaron Allen}
|
15
20
|
# @since 0.1.0
|
16
21
|
class Validator
|
@@ -37,12 +42,12 @@ module Domainic
|
|
37
42
|
|
38
43
|
# @rbs @handlers: Array[handler]
|
39
44
|
|
40
|
-
# Initialize a new Validator instance
|
45
|
+
# Initialize a new Validator instance
|
41
46
|
#
|
42
|
-
# @param attribute [Attribute] the
|
47
|
+
# @param attribute [Attribute] the {Attribute} this instance belongs to
|
43
48
|
# @param handlers [Array<Class, Module, Object, Proc>] the handlers to use for processing
|
44
49
|
#
|
45
|
-
# @return [Validator] the new instance
|
50
|
+
# @return [Validator] the new Validator instance
|
46
51
|
# @rbs (Attribute attribute, Array[handler] | handler handlers) -> void
|
47
52
|
def initialize(attribute, handlers = [])
|
48
53
|
super
|
@@ -52,28 +57,28 @@ module Domainic
|
|
52
57
|
end.uniq
|
53
58
|
end
|
54
59
|
|
55
|
-
# Validate a value using all configured validators
|
60
|
+
# Validate a value using all configured validators
|
56
61
|
#
|
57
62
|
# @param instance [Object] the instance on which to perform validation
|
58
63
|
# @param value [Object] the value to validate
|
59
64
|
#
|
60
65
|
# @raise [ArgumentError] if the value fails validation
|
66
|
+
# @raise [ValidationExecutionError] if errors occur during validation execution
|
61
67
|
# @return [void]
|
62
68
|
# @rbs (untyped instance, untyped value) -> void
|
63
69
|
def call(instance, value)
|
64
70
|
return if value == Undefined && handle_undefined!
|
65
71
|
return if value.nil? && handle_nil!
|
66
|
-
return if @handlers.all? { |handler| validate_value!(handler, instance, value) }
|
67
72
|
|
68
|
-
|
73
|
+
run_validations!(instance, value)
|
69
74
|
end
|
70
75
|
|
71
76
|
private
|
72
77
|
|
73
|
-
# Handle a
|
78
|
+
# Handle a nil value
|
74
79
|
#
|
75
80
|
# @raise [ArgumentError] if the attribute is not nilable
|
76
|
-
# @return [
|
81
|
+
# @return [Boolean] true if the attribute is nilable
|
77
82
|
# @rbs () -> bool
|
78
83
|
def handle_nil!
|
79
84
|
return true if @attribute.signature.nilable?
|
@@ -81,10 +86,10 @@ module Domainic
|
|
81
86
|
raise ArgumentError, "`#{attribute_method_name}`: cannot be nil"
|
82
87
|
end
|
83
88
|
|
84
|
-
# Handle an {Undefined} value
|
89
|
+
# Handle an {Undefined} value
|
85
90
|
#
|
86
91
|
# @raise [ArgumentError] if the attribute is required
|
87
|
-
# @return [
|
92
|
+
# @return [Boolean] true if the attribute is optional
|
88
93
|
# @rbs () -> bool
|
89
94
|
def handle_undefined!
|
90
95
|
return true if @attribute.signature.optional?
|
@@ -92,7 +97,30 @@ module Domainic
|
|
92
97
|
raise ArgumentError, "`#{attribute_method_name}`: is required"
|
93
98
|
end
|
94
99
|
|
95
|
-
#
|
100
|
+
# Run all configured validations
|
101
|
+
#
|
102
|
+
# @param instance [Object] the instance on which to perform validation
|
103
|
+
# @param value [Object] the value to validate
|
104
|
+
#
|
105
|
+
# @raise [ArgumentError] if the value fails validation
|
106
|
+
# @raise [ValidationExecutionError] if errors occur during validation execution
|
107
|
+
# @return [void]
|
108
|
+
# @rbs (untyped instance, untyped value) -> void
|
109
|
+
def run_validations!(instance, value)
|
110
|
+
errors = []
|
111
|
+
is_valid = true
|
112
|
+
|
113
|
+
@handlers.each do |handler|
|
114
|
+
is_valid = validate_value!(handler, instance, value)
|
115
|
+
rescue StandardError => e
|
116
|
+
errors << e
|
117
|
+
end
|
118
|
+
|
119
|
+
raise ValidationExecutionError, errors unless errors.empty?
|
120
|
+
raise ArgumentError, "`#{attribute_method_name}`: has invalid value: #{value.inspect}" unless is_valid
|
121
|
+
end
|
122
|
+
|
123
|
+
# Validate that a validation handler is valid
|
96
124
|
#
|
97
125
|
# @param handler [Object] the handler to validate
|
98
126
|
#
|
@@ -106,11 +134,13 @@ module Domainic
|
|
106
134
|
'or an object responding to `#===`.'
|
107
135
|
end
|
108
136
|
|
109
|
-
# Validate a value using a single handler
|
137
|
+
# Validate a value using a single handler
|
110
138
|
#
|
111
139
|
# @param handler [Object] the handler to use for validation
|
112
140
|
# @param instance [Object] the instance on which to perform validation
|
113
141
|
# @param value [Object] the value to validate
|
142
|
+
#
|
143
|
+
# @return [Boolean] true if validation succeeds
|
114
144
|
# @rbs (handler handler, untyped instance, untyped value) -> bool
|
115
145
|
def validate_value!(handler, instance, value)
|
116
146
|
if handler.is_a?(Proc)
|
@@ -118,7 +148,7 @@ module Domainic
|
|
118
148
|
elsif handler.respond_to?(:===)
|
119
149
|
handler === value # rubocop:disable Style/CaseEquality
|
120
150
|
else
|
121
|
-
# We should never get here because we validate the handlers in the initializer
|
151
|
+
# We should never get here because we validate the handlers in the initializer
|
122
152
|
raise TypeError, "`#{attribute_method_name}`: invalid validator: #{handler.inspect}"
|
123
153
|
end
|
124
154
|
end
|