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,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/contracts'
|
4
|
+
|
5
|
+
module Stannum::Contracts
|
6
|
+
# A TupleContract defines constraints for an ordered, indexed object.
|
7
|
+
#
|
8
|
+
# In order to match a TupleContract, the object must respond to the :[],
|
9
|
+
# :each, and :size methods, and the items in the object at each index must
|
10
|
+
# match the item constraint defined for that index. Finally, unless the
|
11
|
+
# :allow_extra_items option is set to true, the object must not have any extra
|
12
|
+
# items.
|
13
|
+
#
|
14
|
+
# @example Creating A Contract With Item Constraints
|
15
|
+
# third_base_constraint = Stannum::Constraint.new do |actual|
|
16
|
+
# actual == "I Don't Know"
|
17
|
+
# end
|
18
|
+
# tuple_contract = Stannum::Contracts::TupleContract.new do
|
19
|
+
# item { |actual| actual == 'Who' }
|
20
|
+
# item { |actual| actual == 'What' }
|
21
|
+
# item third_base_constraint
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example With A Non-Tuple Object
|
25
|
+
# tuple_contract.matches?(nil) #=> false
|
26
|
+
# errors = tuple_contract.errors_for(nil)
|
27
|
+
# errors.to_a
|
28
|
+
# #=> [
|
29
|
+
# {
|
30
|
+
# type: 'stannum.constraints.does_not_have_methods',
|
31
|
+
# data: { methods: [:[], :each, :size], missing: [:[], :each, :size] },
|
32
|
+
# message: nil,
|
33
|
+
# path: []
|
34
|
+
# }
|
35
|
+
# ]
|
36
|
+
#
|
37
|
+
# @example With An Object With Missing Items
|
38
|
+
# tuple_contract.matches?(['Who']) #=> false
|
39
|
+
# errors = tuple_contract.errors_for(['Who'])
|
40
|
+
# errors.to_a
|
41
|
+
# #=> [
|
42
|
+
# { type: 'stannum.constraints.invalid', data: {}, path: [1], message: nil },
|
43
|
+
# { type: 'stannum.constraints.invalid', data: {}, path: [2], message: nil }
|
44
|
+
# ]
|
45
|
+
#
|
46
|
+
# @example With An Object With Incorrect Items
|
47
|
+
# tuple_contract.matches?(['What', 'What', "I Don't Know"]) #=> false
|
48
|
+
# errors = tuple_contract.errors_for(['What', 'What', "I Don't Know"])
|
49
|
+
# errors.to_a
|
50
|
+
# #=> [
|
51
|
+
# { type: 'stannum.constraints.invalid', data: {}, path: [0], message: nil }
|
52
|
+
# ]
|
53
|
+
#
|
54
|
+
# @example With An Object With Valid Items
|
55
|
+
# tuple_contract.matches?(['Who', 'What', "I Don't Know"]) #=> true
|
56
|
+
# errors = tuple_contract.errors_for(['What', 'What', "I Don't Know"])
|
57
|
+
# errors.to_a #=> []
|
58
|
+
#
|
59
|
+
# @example With An Object With Extra Items
|
60
|
+
# tuple_contract.matches?(['Who', 'What', "I Don't Know", 'Tomorrow', 'Today']) #=> false
|
61
|
+
# errors = tuple_contract.errors_for(['Who', 'What', "I Don't Know", 'Tomorrow', 'Today'])
|
62
|
+
# errors.to_a
|
63
|
+
# #=> [
|
64
|
+
# { type: 'stannum.constraints.tuples.extra_items', data: {}, path: [3], message: nil },
|
65
|
+
# { type: 'stannum.constraints.tuples.extra_items', data: {}, path: [4], message: nil }
|
66
|
+
# ]
|
67
|
+
class TupleContract < Stannum::Contract
|
68
|
+
# Builder class for defining item constraints for a TupleContract.
|
69
|
+
#
|
70
|
+
# This class should not be invoked directly. Instead, pass a block to the
|
71
|
+
# constructor for TupleContract.
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
class Builder < Stannum::Contract::Builder
|
75
|
+
# @param contract [Stannum::Contract] The contract to which constraints
|
76
|
+
# are added.
|
77
|
+
def initialize(contract)
|
78
|
+
super
|
79
|
+
|
80
|
+
@current_index = -1
|
81
|
+
end
|
82
|
+
|
83
|
+
# Defines an item constraint on the contract.
|
84
|
+
#
|
85
|
+
# Each time an item constraint is defined, the constraint is tied to an
|
86
|
+
# incrementing index, i.e. the first constraint is matched against the
|
87
|
+
# item at index 0, the second at index 1, and so on. This can be overriden
|
88
|
+
# by setting the :property option.
|
89
|
+
#
|
90
|
+
# @overload item(constraint, **options)
|
91
|
+
# Adds the given constraint to the contract for the next index.
|
92
|
+
#
|
93
|
+
# @param constraint [Stannum::Constraint::Base] The constraint to add.
|
94
|
+
# @param options [Hash<Symbol, Object>] Options for the constraint.
|
95
|
+
#
|
96
|
+
# @overload item(**options) { |value| }
|
97
|
+
# Creates a new Stannum::Constraint object with the given block, and
|
98
|
+
# adds that constraint to the contract for the next index.
|
99
|
+
#
|
100
|
+
# @param options [Hash<Symbol, Object>] Options for the constraint.
|
101
|
+
# @yieldparam value [Object] The value of the property when called.
|
102
|
+
def item(constraint = nil, **options, &block)
|
103
|
+
index = (@current_index += 1)
|
104
|
+
|
105
|
+
self.constraint(
|
106
|
+
constraint,
|
107
|
+
property: index,
|
108
|
+
property_type: :index,
|
109
|
+
**options,
|
110
|
+
&block
|
111
|
+
)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# @param allow_extra_items [true, false] If false, then a tuple with extra
|
116
|
+
# items after the last expected item will not match the contract.
|
117
|
+
# @param options [Hash<Symbol, Object>] Configuration options for the
|
118
|
+
# contract. Defaults to an empty Hash.
|
119
|
+
def initialize(allow_extra_items: false, **options, &block)
|
120
|
+
super(allow_extra_items: allow_extra_items, **options, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds an index constraint to the contract.
|
124
|
+
#
|
125
|
+
# When the contract is called, the contract will find the value of the
|
126
|
+
# object at the given index.
|
127
|
+
#
|
128
|
+
# @param index [Integer] The index of the value to match.
|
129
|
+
# @param constraint [Stannum::Constraints::Base] The constraint to add.
|
130
|
+
# @param sanity [true, false] Marks the constraint as a sanity constraint,
|
131
|
+
# which is always matched first and will always short-circuit on a failed
|
132
|
+
# match.
|
133
|
+
# @param options [Hash<Symbol, Object>] Options for the constraint. These
|
134
|
+
# can be used by subclasses to define the value and error mappings for the
|
135
|
+
# constraint.
|
136
|
+
#
|
137
|
+
# @return [self] the contract.
|
138
|
+
#
|
139
|
+
# @see Stannum::Contract#add_constraint.
|
140
|
+
def add_index_constraint(index, constraint, sanity: false, **options)
|
141
|
+
add_constraint(
|
142
|
+
constraint,
|
143
|
+
property: index,
|
144
|
+
property_type: :index,
|
145
|
+
sanity: sanity,
|
146
|
+
**options
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [true, false] If false, then a tuple with extra items after the
|
151
|
+
# last expected item will not match the contract.
|
152
|
+
def allow_extra_items?
|
153
|
+
!!options[:allow_extra_items]
|
154
|
+
end
|
155
|
+
|
156
|
+
# (see Stannum::Contracts::Base#with_options)
|
157
|
+
def with_options(**options)
|
158
|
+
return super unless options.key?(:allow_extra_items)
|
159
|
+
|
160
|
+
raise ArgumentError, "can't change option :allow_extra_items"
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
def expected_count
|
166
|
+
each_constraint.reduce(0) do |count, definition|
|
167
|
+
next count unless definition.options[:property_type] == :index
|
168
|
+
|
169
|
+
index = 1 + definition.options.fetch(:property, -1)
|
170
|
+
|
171
|
+
index > count ? index : count
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def map_value(actual, **options)
|
176
|
+
return super unless options[:property_type] == :index
|
177
|
+
|
178
|
+
actual[options[:property]]
|
179
|
+
end
|
180
|
+
|
181
|
+
def valid_property?(property: nil, property_type: nil, **options)
|
182
|
+
return super unless property_type == :index
|
183
|
+
|
184
|
+
property.is_a?(Integer)
|
185
|
+
end
|
186
|
+
|
187
|
+
def validate_property?(**options)
|
188
|
+
options[:property_type] == :index || super
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def add_extra_items_constraint
|
194
|
+
return if allow_extra_items?
|
195
|
+
|
196
|
+
count = -> { expected_count }
|
197
|
+
|
198
|
+
add_constraint Stannum::Constraints::Tuples::ExtraItems.new(count)
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_type_constraint
|
202
|
+
add_constraint Stannum::Constraints::Signatures::Tuple.new, sanity: true
|
203
|
+
end
|
204
|
+
|
205
|
+
def define_constraints(&block)
|
206
|
+
add_type_constraint
|
207
|
+
|
208
|
+
add_extra_items_constraint
|
209
|
+
|
210
|
+
super
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum'
|
4
|
+
|
5
|
+
module Stannum
|
6
|
+
# Namespace for pre-defined contracts.
|
7
|
+
module Contracts
|
8
|
+
autoload :ArrayContract, 'stannum/contracts/array_contract'
|
9
|
+
autoload :Base, 'stannum/contracts/base'
|
10
|
+
autoload :Builder, 'stannum/contracts/builder'
|
11
|
+
autoload :Definition, 'stannum/contracts/definition'
|
12
|
+
autoload :HashContract, 'stannum/contracts/hash_contract'
|
13
|
+
autoload :IndifferentHashContract,
|
14
|
+
'stannum/contracts/indifferent_hash_contract'
|
15
|
+
autoload :Parameters, 'stannum/contracts/parameters'
|
16
|
+
autoload :ParametersContract, 'stannum/contracts/parameters_contract'
|
17
|
+
autoload :TupleContract, 'stannum/contracts/tuple_contract'
|
18
|
+
end
|
19
|
+
end
|