stannum 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 +7 -0
- data/CHANGELOG.md +21 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +105 -0
- data/LICENSE +22 -0
- data/README.md +1327 -0
- data/config/locales/en.rb +47 -0
- data/lib/stannum/attribute.rb +115 -0
- data/lib/stannum/constraint.rb +65 -0
- data/lib/stannum/constraints/absence.rb +42 -0
- data/lib/stannum/constraints/anything.rb +28 -0
- data/lib/stannum/constraints/base.rb +285 -0
- data/lib/stannum/constraints/boolean.rb +33 -0
- data/lib/stannum/constraints/delegator.rb +71 -0
- data/lib/stannum/constraints/enum.rb +64 -0
- data/lib/stannum/constraints/equality.rb +47 -0
- data/lib/stannum/constraints/hashes/extra_keys.rb +126 -0
- data/lib/stannum/constraints/hashes/indifferent_key.rb +74 -0
- data/lib/stannum/constraints/hashes.rb +11 -0
- data/lib/stannum/constraints/identity.rb +46 -0
- data/lib/stannum/constraints/nothing.rb +28 -0
- data/lib/stannum/constraints/presence.rb +42 -0
- data/lib/stannum/constraints/signature.rb +92 -0
- data/lib/stannum/constraints/signatures/map.rb +17 -0
- data/lib/stannum/constraints/signatures/tuple.rb +17 -0
- data/lib/stannum/constraints/signatures.rb +11 -0
- data/lib/stannum/constraints/tuples/extra_items.rb +84 -0
- data/lib/stannum/constraints/tuples.rb +10 -0
- data/lib/stannum/constraints/type.rb +113 -0
- data/lib/stannum/constraints/types/array_type.rb +148 -0
- data/lib/stannum/constraints/types/big_decimal_type.rb +16 -0
- data/lib/stannum/constraints/types/date_time_type.rb +16 -0
- data/lib/stannum/constraints/types/date_type.rb +16 -0
- data/lib/stannum/constraints/types/float_type.rb +14 -0
- data/lib/stannum/constraints/types/hash_type.rb +205 -0
- data/lib/stannum/constraints/types/hash_with_indifferent_keys.rb +21 -0
- data/lib/stannum/constraints/types/hash_with_string_keys.rb +21 -0
- data/lib/stannum/constraints/types/hash_with_symbol_keys.rb +21 -0
- data/lib/stannum/constraints/types/integer_type.rb +14 -0
- data/lib/stannum/constraints/types/nil_type.rb +20 -0
- data/lib/stannum/constraints/types/proc_type.rb +14 -0
- data/lib/stannum/constraints/types/string_type.rb +14 -0
- data/lib/stannum/constraints/types/symbol_type.rb +14 -0
- data/lib/stannum/constraints/types/time_type.rb +14 -0
- data/lib/stannum/constraints/types.rb +25 -0
- data/lib/stannum/constraints/union.rb +85 -0
- data/lib/stannum/constraints.rb +26 -0
- data/lib/stannum/contract.rb +243 -0
- data/lib/stannum/contracts/array_contract.rb +108 -0
- data/lib/stannum/contracts/base.rb +597 -0
- data/lib/stannum/contracts/builder.rb +72 -0
- data/lib/stannum/contracts/definition.rb +74 -0
- data/lib/stannum/contracts/hash_contract.rb +136 -0
- data/lib/stannum/contracts/indifferent_hash_contract.rb +78 -0
- data/lib/stannum/contracts/map_contract.rb +199 -0
- data/lib/stannum/contracts/parameters/arguments_contract.rb +185 -0
- data/lib/stannum/contracts/parameters/keywords_contract.rb +174 -0
- data/lib/stannum/contracts/parameters/signature_contract.rb +29 -0
- data/lib/stannum/contracts/parameters.rb +15 -0
- data/lib/stannum/contracts/parameters_contract.rb +530 -0
- data/lib/stannum/contracts/tuple_contract.rb +213 -0
- data/lib/stannum/contracts.rb +19 -0
- data/lib/stannum/errors.rb +730 -0
- data/lib/stannum/messages/default_strategy.rb +124 -0
- data/lib/stannum/messages.rb +25 -0
- data/lib/stannum/parameter_validation.rb +216 -0
- data/lib/stannum/rspec/match_errors.rb +17 -0
- data/lib/stannum/rspec/match_errors_matcher.rb +93 -0
- data/lib/stannum/rspec/validate_parameter.rb +23 -0
- data/lib/stannum/rspec/validate_parameter_matcher.rb +506 -0
- data/lib/stannum/rspec.rb +8 -0
- data/lib/stannum/schema.rb +131 -0
- data/lib/stannum/struct.rb +444 -0
- data/lib/stannum/support/coercion.rb +114 -0
- data/lib/stannum/support/optional.rb +69 -0
- data/lib/stannum/support.rb +8 -0
- data/lib/stannum/version.rb +57 -0
- data/lib/stannum.rb +27 -0
- metadata +216 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/base'
|
4
|
+
require 'stannum/support/optional'
|
5
|
+
|
6
|
+
module Stannum::Constraints
|
7
|
+
# A type constraint asserts that the object is of the expected type.
|
8
|
+
#
|
9
|
+
# @example Using a Type constraint with a Class
|
10
|
+
# constraint = Stannum::Constraints::Type.new(StandardError)
|
11
|
+
#
|
12
|
+
# constraint.matches?(nil) # => false
|
13
|
+
# constraint.matches?(Object.new) # => false
|
14
|
+
# constraint.matches?(StandardError.new) # => true
|
15
|
+
# constraint.matches?(RuntimeError.new) # => true
|
16
|
+
#
|
17
|
+
# @example Using a Type constraint with a Module
|
18
|
+
# constraint = Stannum::Constraints::Type.new(Enumerable)
|
19
|
+
#
|
20
|
+
# constraint.matches?(nil) #=> false
|
21
|
+
# constraint.matches?(Object.new) #=> false
|
22
|
+
# constraint.matches?(Array) #=> true
|
23
|
+
# constraint.matches?(Object.new.extend(Enumerable)) #=> true
|
24
|
+
#
|
25
|
+
# @example Using a Type constraint with a Class name
|
26
|
+
# constraint = Stannum::Constraints::Type.new('StandardError')
|
27
|
+
#
|
28
|
+
# constraint.matches?(nil) # => false
|
29
|
+
# constraint.matches?(Object.new) # => false
|
30
|
+
# constraint.matches?(StandardError.new) # => true
|
31
|
+
# constraint.matches?(RuntimeError.new) # => true
|
32
|
+
class Type < Stannum::Constraints::Base
|
33
|
+
include Stannum::Support::Optional
|
34
|
+
|
35
|
+
# The :type of the error generated for a matching object.
|
36
|
+
NEGATED_TYPE = 'stannum.constraints.is_type'
|
37
|
+
|
38
|
+
# The :type of the error generated for a non-matching object.
|
39
|
+
TYPE = 'stannum.constraints.is_not_type'
|
40
|
+
|
41
|
+
# @param expected_type [Class, Module, String] The type the object is
|
42
|
+
# expected to belong to. Can be a Class or a Module, or the name of a
|
43
|
+
# class or module.
|
44
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
45
|
+
# constraint. Defaults to an empty Hash.
|
46
|
+
def initialize(expected_type, optional: nil, required: nil, **options)
|
47
|
+
expected_type = resolve_expected_type(expected_type)
|
48
|
+
|
49
|
+
super(
|
50
|
+
expected_type: expected_type,
|
51
|
+
**resolve_required_option(
|
52
|
+
optional: optional,
|
53
|
+
required: required,
|
54
|
+
**options
|
55
|
+
)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# (see Stannum::Constraints::Base#errors_for)
|
60
|
+
def errors_for(actual, errors: nil) # rubocop:disable Lint/UnusedMethodArgument
|
61
|
+
(errors || Stannum::Errors.new).add(type, **error_properties)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Class, Module, String] the type the object is expected to belong
|
65
|
+
# to.
|
66
|
+
def expected_type
|
67
|
+
options[:expected_type]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks that the object is an instance of the expected type.
|
71
|
+
#
|
72
|
+
# @return [true, false] true if the object is an instance of the expected
|
73
|
+
# type, otherwise false.
|
74
|
+
#
|
75
|
+
# @see Stannum::Constraint#matches?
|
76
|
+
def matches?(actual)
|
77
|
+
matches_type?(actual)
|
78
|
+
end
|
79
|
+
alias match? matches?
|
80
|
+
|
81
|
+
# (see Stannum::Constraints::Base#negated_errors_for)
|
82
|
+
def negated_errors_for(actual, errors: nil) # rubocop:disable Lint/UnusedMethodArgument
|
83
|
+
(errors || Stannum::Errors.new).add(negated_type, **error_properties)
|
84
|
+
end
|
85
|
+
|
86
|
+
# (see Stannum::Constraints::Base#with_options)
|
87
|
+
def with_options(**options)
|
88
|
+
options = options.merge(required_by_default: required?)
|
89
|
+
|
90
|
+
super(**resolve_required_option(**options))
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def error_properties
|
96
|
+
{ required: required?, type: expected_type }
|
97
|
+
end
|
98
|
+
|
99
|
+
def matches_type?(actual)
|
100
|
+
actual.is_a?(expected_type) || (optional? && actual.nil?)
|
101
|
+
end
|
102
|
+
|
103
|
+
def resolve_expected_type(type_or_name)
|
104
|
+
return type_or_name if type_or_name.is_a?(Module)
|
105
|
+
|
106
|
+
return Object.const_get(type_or_name) if type_or_name.is_a?(String)
|
107
|
+
|
108
|
+
raise ArgumentError,
|
109
|
+
'expected type must be a Class or Module',
|
110
|
+
caller[1..-1]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
require 'stannum/support/coercion'
|
5
|
+
|
6
|
+
module Stannum::Constraints::Types
|
7
|
+
# An Array type constraint asserts that the object is an Array.
|
8
|
+
#
|
9
|
+
# @example Using an Array type constraint
|
10
|
+
# constraint = Stannum::Constraints::Types::ArrayType.new
|
11
|
+
#
|
12
|
+
# constraint.matches?(nil) # => false
|
13
|
+
# constraint.matches?(Object.new) # => false
|
14
|
+
# constraint.matches?([]) # => true
|
15
|
+
# constraint.matches?([1, 2, 3]) # => true
|
16
|
+
#
|
17
|
+
# @example Using an Array type constraint with an item constraint
|
18
|
+
# constraint = Stannum::Constraints::Types::ArrayType.new(item_type: String)
|
19
|
+
#
|
20
|
+
# constraint.matches?(nil) # => false
|
21
|
+
# constraint.matches?(Object.new) # => false
|
22
|
+
# constraint.matches?([]) # => true
|
23
|
+
# constraint.matches?([1, 2, 3]) # => false
|
24
|
+
# constraint.matches?(%w[one two three]) # => true
|
25
|
+
#
|
26
|
+
# @example Using an Array type constraint with a presence constraint
|
27
|
+
# constraint = Stannum::Constraints::Types::ArrayType.new(allow_empty: false)
|
28
|
+
#
|
29
|
+
# constraint.matches?(nil) # => false
|
30
|
+
# constraint.matches?(Object.new) # => false
|
31
|
+
# constraint.matches?([]) # => false
|
32
|
+
# constraint.matches?([1, 2, 3]) # => true
|
33
|
+
# constraint.matches?(%w[one two three]) # => true
|
34
|
+
class ArrayType < Stannum::Constraints::Type
|
35
|
+
# @param allow_empty [true, false] If false, then the constraint will not
|
36
|
+
# match against an Array with no items.
|
37
|
+
# @param item_type [Stannum::Constraints::Base, Class, nil] If set, then
|
38
|
+
# the constraint will check the types of each item in the Array against
|
39
|
+
# the expected type and will fail if any items do not match.
|
40
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
41
|
+
# constraint. Defaults to an empty Hash.
|
42
|
+
def initialize(allow_empty: true, item_type: nil, **options)
|
43
|
+
super(
|
44
|
+
::Array,
|
45
|
+
allow_empty: !!allow_empty,
|
46
|
+
item_type: coerce_item_type(item_type),
|
47
|
+
**options
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [true, false] if false, then the constraint will not
|
52
|
+
# match against an Array with no items.
|
53
|
+
def allow_empty?
|
54
|
+
options[:allow_empty]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks that the object is not an Array instance.
|
58
|
+
#
|
59
|
+
# @return [true, false] true if the object is not an Array instance,
|
60
|
+
# otherwise false.
|
61
|
+
#
|
62
|
+
# @see Stannum::Constraints::Types::ArrayType#matches?
|
63
|
+
def does_not_match?(actual)
|
64
|
+
!matches_type?(actual)
|
65
|
+
end
|
66
|
+
|
67
|
+
# (see Stannum::Constraints::Base#errors_for)
|
68
|
+
def errors_for(actual, errors: nil)
|
69
|
+
return super unless actual.is_a?(expected_type)
|
70
|
+
|
71
|
+
errors ||= Stannum::Errors.new
|
72
|
+
|
73
|
+
return add_presence_error(errors) unless presence_matches?(actual)
|
74
|
+
|
75
|
+
unless item_type_matches?(actual)
|
76
|
+
non_matching_items(actual).each do |item, index|
|
77
|
+
item_type.errors_for(item, errors: errors[index])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
errors
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Stannum::Constraints::Base, nil] the expected type for the items
|
85
|
+
# in the array.
|
86
|
+
def item_type
|
87
|
+
options[:item_type]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Checks that the object is an Array instance and that the items match.
|
91
|
+
#
|
92
|
+
# If the constraint was configured with an item_type, each item in the Array
|
93
|
+
# will be compared to the expected type. If any items do not match the
|
94
|
+
# expectation, then #matches? will return false.
|
95
|
+
#
|
96
|
+
# @return [true, false] true if the object is an Array instance with
|
97
|
+
# matching items, otherwise false.
|
98
|
+
#
|
99
|
+
# @see Stannum::Constraints::Types::ArrayType#does_not_match?
|
100
|
+
def matches?(actual)
|
101
|
+
return false unless super
|
102
|
+
|
103
|
+
return false unless presence_matches?(actual)
|
104
|
+
|
105
|
+
return false unless item_type_matches?(actual)
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
alias match? matches?
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def add_presence_error(errors)
|
114
|
+
errors.add(
|
115
|
+
Stannum::Constraints::Presence::TYPE,
|
116
|
+
**error_properties
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def coerce_item_type(item_type)
|
121
|
+
Stannum::Support::Coercion.type_constraint(
|
122
|
+
item_type,
|
123
|
+
allow_nil: true,
|
124
|
+
as: 'item type'
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def error_properties
|
129
|
+
super().merge(allow_empty: allow_empty?)
|
130
|
+
end
|
131
|
+
|
132
|
+
def item_type_matches?(actual)
|
133
|
+
return true unless item_type
|
134
|
+
|
135
|
+
return true if actual.nil?
|
136
|
+
|
137
|
+
actual.all? { |item| item_type.matches?(item) }
|
138
|
+
end
|
139
|
+
|
140
|
+
def non_matching_items(actual)
|
141
|
+
actual.each.with_index.reject { |item, _| item_type.matches?(item) }
|
142
|
+
end
|
143
|
+
|
144
|
+
def presence_matches?(actual)
|
145
|
+
allow_empty? || !actual.empty?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
require 'stannum/constraints/types'
|
6
|
+
|
7
|
+
module Stannum::Constraints::Types
|
8
|
+
# A BigDecimal type constraint asserts that the object is a BigDecimal.
|
9
|
+
class BigDecimalType < Stannum::Constraints::Type
|
10
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
11
|
+
# constraint. Defaults to an empty Hash.
|
12
|
+
def initialize(**options)
|
13
|
+
super(::BigDecimal, **options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require 'stannum/constraints/types'
|
6
|
+
|
7
|
+
module Stannum::Constraints::Types
|
8
|
+
# A DateTimeType constraint asserts that the object is a DateTime.
|
9
|
+
class DateTimeType < Stannum::Constraints::Type
|
10
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
11
|
+
# constraint. Defaults to an empty Hash.
|
12
|
+
def initialize(**options)
|
13
|
+
super(::DateTime, **options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
require 'stannum/constraints/types'
|
6
|
+
|
7
|
+
module Stannum::Constraints::Types
|
8
|
+
# A DateType constraint asserts that the object is a Date.
|
9
|
+
class DateType < Stannum::Constraints::Type
|
10
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
11
|
+
# constraint. Defaults to an empty Hash.
|
12
|
+
def initialize(**options)
|
13
|
+
super(::Date, **options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# A FloatType constraint asserts that the object is a Float.
|
7
|
+
class FloatType < Stannum::Constraints::Type
|
8
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
9
|
+
# constraint. Defaults to an empty Hash.
|
10
|
+
def initialize(**options)
|
11
|
+
super(::Float, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
require 'stannum/support/coercion'
|
5
|
+
|
6
|
+
module Stannum::Constraints::Types
|
7
|
+
# A Hash type constraint asserts that the object is a Hash.
|
8
|
+
#
|
9
|
+
# @example Using a Hash type constraint
|
10
|
+
# constraint = Stannum::Constraints::Types::HashType.new
|
11
|
+
#
|
12
|
+
# constraint.matches?(nil) # => false
|
13
|
+
# constraint.matches?(Object.new) # => false
|
14
|
+
# constraint.matches?({}) # => true
|
15
|
+
# constraint.matches?({ key: 'value' }) # => true
|
16
|
+
#
|
17
|
+
# @example Using a Hash type constraint with a key constraint
|
18
|
+
# constraint = Stannum::Constraints::Types::HashType.new(key_type: String)
|
19
|
+
#
|
20
|
+
# constraint.matches?(nil) # => false
|
21
|
+
# constraint.matches?(Object.new) # => false
|
22
|
+
# constraint.matches?({}) # => true
|
23
|
+
# constraint.matches?({ key: 'value' }) # => false
|
24
|
+
# constraint.matches?({ 'key' => 'value' }) # => true
|
25
|
+
#
|
26
|
+
# @example Using a Hash type constraint with a value constraint
|
27
|
+
# constraint = Stannum::Constraints::Types::HashType.new(value_type: String)
|
28
|
+
#
|
29
|
+
# constraint.matches?(nil) # => false
|
30
|
+
# constraint.matches?(Object.new) # => false
|
31
|
+
# constraint.matches?({}) # => true
|
32
|
+
# constraint.matches?({ key: :value }) # => false
|
33
|
+
# constraint.matches?({ key: 'value' }) # => true
|
34
|
+
#
|
35
|
+
# @example Using a Hash type constraint with a presence constraint
|
36
|
+
# constraint = Stannum::Constraints::Types::HashType.new(allow_empty: false)
|
37
|
+
#
|
38
|
+
# constraint.matches?(nil) # => false
|
39
|
+
# constraint.matches?(Object.new) # => false
|
40
|
+
# constraint.matches?({}) # => false
|
41
|
+
# constraint.matches?({ key: :value }) # => true
|
42
|
+
# constraint.matches?({ key: 'value' }) # => true
|
43
|
+
class HashType < Stannum::Constraints::Type
|
44
|
+
# @param allow_empty [true, false] If false, then the constraint will not
|
45
|
+
# match against a Hash with no keys.
|
46
|
+
# @param key_type [Stannum::Constraints::Base, Class, nil] If set, then the
|
47
|
+
# constraint will check the types of each key in the Hash against the
|
48
|
+
# expected type and will fail if any keys do not match.
|
49
|
+
# @param value_type [Stannum::Constraints::Base, Class, nil] If set, then
|
50
|
+
# the constraint will check the types of each value in the Hash against
|
51
|
+
# the expected type and will fail if any values do not match.
|
52
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
53
|
+
# constraint. Defaults to an empty Hash.
|
54
|
+
def initialize(allow_empty: true, key_type: nil, value_type: nil, **options)
|
55
|
+
super(
|
56
|
+
::Hash,
|
57
|
+
allow_empty: !!allow_empty,
|
58
|
+
key_type: coerce_key_type(key_type),
|
59
|
+
value_type: coerce_value_type(value_type),
|
60
|
+
**options
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [true, false] if false, then the constraint will not
|
65
|
+
# match against a Hash with no keys.
|
66
|
+
def allow_empty?
|
67
|
+
options[:allow_empty]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks that the object is not a Hash instance.
|
71
|
+
#
|
72
|
+
# @return [true, false] true if the object is not a Hash instance, otherwise
|
73
|
+
# false.
|
74
|
+
#
|
75
|
+
# @see Stannum::Constraints::Types::HashType#matches?
|
76
|
+
def does_not_match?(actual)
|
77
|
+
!matches_type?(actual)
|
78
|
+
end
|
79
|
+
|
80
|
+
# (see Stannum::Constraints::Base#errors_for)
|
81
|
+
def errors_for(actual, errors: nil)
|
82
|
+
return super unless actual.is_a?(expected_type)
|
83
|
+
|
84
|
+
errors ||= Stannum::Errors.new
|
85
|
+
|
86
|
+
return add_presence_error(errors) unless presence_matches?(actual)
|
87
|
+
|
88
|
+
update_key_errors_for(actual: actual, errors: errors)
|
89
|
+
|
90
|
+
update_value_errors_for(actual: actual, errors: errors)
|
91
|
+
|
92
|
+
errors
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Stannum::Constraints::Base, nil] the expected type for the keys
|
96
|
+
# in the hash.
|
97
|
+
def key_type
|
98
|
+
options[:key_type]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Checks that the object is a Hash instance and that the keys/values match.
|
102
|
+
#
|
103
|
+
# If the constraint was configured with a key_type, each key in the hash
|
104
|
+
# will be compared to the expected type. Likewise, if the constraint was
|
105
|
+
# configured with a value_type, each value in the hash will be compared. If
|
106
|
+
# any keys and/or values do not match the expectation, then #matches? will
|
107
|
+
# return false.
|
108
|
+
#
|
109
|
+
# @return [true, false] true if the object is a Hash instance with matching
|
110
|
+
# keys and values, otherwise false.
|
111
|
+
#
|
112
|
+
# @see Stannum::Constraints::Types::HashType#does_not_match?
|
113
|
+
def matches?(actual)
|
114
|
+
return false unless super
|
115
|
+
|
116
|
+
return false unless presence_matches?(actual)
|
117
|
+
|
118
|
+
return false unless key_type_matches?(actual)
|
119
|
+
|
120
|
+
return false unless value_type_matches?(actual)
|
121
|
+
|
122
|
+
true
|
123
|
+
end
|
124
|
+
alias match? matches?
|
125
|
+
|
126
|
+
# @return [Stannum::Constraints::Base, nil] the expected type for the values
|
127
|
+
# in the hash.
|
128
|
+
def value_type
|
129
|
+
options[:value_type]
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def add_presence_error(errors)
|
135
|
+
errors.add(
|
136
|
+
Stannum::Constraints::Presence::TYPE,
|
137
|
+
**error_properties
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def coerce_key_type(key_type)
|
142
|
+
Stannum::Support::Coercion.type_constraint(
|
143
|
+
key_type,
|
144
|
+
allow_nil: true,
|
145
|
+
as: 'key type'
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
def coerce_value_type(value_type)
|
150
|
+
Stannum::Support::Coercion.type_constraint(
|
151
|
+
value_type,
|
152
|
+
allow_nil: true,
|
153
|
+
as: 'value type'
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def error_properties
|
158
|
+
super().merge(allow_empty: allow_empty?)
|
159
|
+
end
|
160
|
+
|
161
|
+
def key_type_matches?(actual)
|
162
|
+
return true unless key_type
|
163
|
+
|
164
|
+
return true if actual.nil?
|
165
|
+
|
166
|
+
actual.each_key.all? { |key| key_type.matches?(key) }
|
167
|
+
end
|
168
|
+
|
169
|
+
def non_matching_keys(actual)
|
170
|
+
return [] unless key_type && actual.is_a?(Hash)
|
171
|
+
|
172
|
+
actual.each_key.reject { |key| key_type.matches?(key) }
|
173
|
+
end
|
174
|
+
|
175
|
+
def non_matching_values(actual)
|
176
|
+
return [] unless value_type && actual.is_a?(Hash)
|
177
|
+
|
178
|
+
actual.each.reject { |_, value| value_type.matches?(value) }
|
179
|
+
end
|
180
|
+
|
181
|
+
def presence_matches?(actual)
|
182
|
+
allow_empty? || !actual.empty?
|
183
|
+
end
|
184
|
+
|
185
|
+
def update_key_errors_for(actual:, errors:)
|
186
|
+
non_matching_keys(actual).each do |key|
|
187
|
+
key_type.errors_for(key, errors: errors[:keys][key])
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def update_value_errors_for(actual:, errors:)
|
192
|
+
non_matching_values(actual).each do |key, value|
|
193
|
+
value_type.errors_for(value, errors: errors[key])
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def value_type_matches?(actual)
|
198
|
+
return true unless value_type
|
199
|
+
|
200
|
+
return true if actual.nil?
|
201
|
+
|
202
|
+
actual.each_value.all? { |value| value_type.matches?(value) }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# Asserts that the object is a Hash with String or Symbol keys.
|
7
|
+
class HashWithIndifferentKeys < Stannum::Constraints::Types::HashType
|
8
|
+
# @param value_type [Stannum::Constraints::Base, Class, nil] If set, then
|
9
|
+
# the constraint will check the types of each value in the Hash against
|
10
|
+
# the expected type and will fail if any values do not match.
|
11
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
12
|
+
# constraint. Defaults to an empty Hash.
|
13
|
+
def initialize(value_type: nil, **options)
|
14
|
+
super(
|
15
|
+
key_type: Stannum::Constraints::Hashes::IndifferentKey.new,
|
16
|
+
value_type: coerce_value_type(value_type),
|
17
|
+
**options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# Asserts that the object is a Hash with String keys.
|
7
|
+
class HashWithStringKeys < Stannum::Constraints::Types::HashType
|
8
|
+
# @param value_type [Stannum::Constraints::Base, Class, nil] If set, then
|
9
|
+
# the constraint will check the types of each value in the Hash against
|
10
|
+
# the expected type and will fail if any values do not match.
|
11
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
12
|
+
# constraint. Defaults to an empty Hash.
|
13
|
+
def initialize(value_type: nil, **options)
|
14
|
+
super(
|
15
|
+
key_type: Stannum::Constraints::Types::StringType.new,
|
16
|
+
value_type: coerce_value_type(value_type),
|
17
|
+
**options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# Asserts that the object is a Hash with Symbol keys.
|
7
|
+
class HashWithSymbolKeys < Stannum::Constraints::Types::HashType
|
8
|
+
# @param value_type [Stannum::Constraints::Base, Class, nil] If set, then
|
9
|
+
# the constraint will check the types of each value in the Hash against
|
10
|
+
# the expected type and will fail if any values do not match.
|
11
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
12
|
+
# constraint. Defaults to an empty Hash.
|
13
|
+
def initialize(value_type: nil, **options)
|
14
|
+
super(
|
15
|
+
key_type: Stannum::Constraints::Types::SymbolType.new,
|
16
|
+
value_type: coerce_value_type(value_type),
|
17
|
+
**options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# An IntegerType constraint asserts that the object is an Integer.
|
7
|
+
class IntegerType < Stannum::Constraints::Type
|
8
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
9
|
+
# constraint. Defaults to an empty Hash.
|
10
|
+
def initialize(**options)
|
11
|
+
super(::Integer, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# A Nil type constraint asserts that the object is nil.
|
7
|
+
class NilType < Stannum::Constraints::Type
|
8
|
+
# The :type of the error generated for a matching object.
|
9
|
+
NEGATED_TYPE = 'stannum.constraints.types.is_nil'
|
10
|
+
|
11
|
+
# The :type of the error generated for a non-matching object.
|
12
|
+
TYPE = 'stannum.constraints.types.is_not_nil'
|
13
|
+
|
14
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
15
|
+
# constraint. Defaults to an empty Hash.
|
16
|
+
def initialize(**options)
|
17
|
+
super(::NilClass, **options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# A Proc type constraint asserts that the object is a Proc.
|
7
|
+
class ProcType < Stannum::Constraints::Type
|
8
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
9
|
+
# constraint. Defaults to an empty Hash.
|
10
|
+
def initialize(**options)
|
11
|
+
super(::Proc, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# A String type constraint asserts that the object is a String.
|
7
|
+
class StringType < Stannum::Constraints::Type
|
8
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
9
|
+
# constraint. Defaults to an empty Hash.
|
10
|
+
def initialize(**options)
|
11
|
+
super(::String, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types'
|
4
|
+
|
5
|
+
module Stannum::Constraints::Types
|
6
|
+
# A Symbol type constraint asserts that the object is a Symbol.
|
7
|
+
class SymbolType < Stannum::Constraints::Type
|
8
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
9
|
+
# constraint. Defaults to an empty Hash.
|
10
|
+
def initialize(**options)
|
11
|
+
super(::Symbol, **options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|