stannum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +21 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +105 -0
  5. data/LICENSE +22 -0
  6. data/README.md +1327 -0
  7. data/config/locales/en.rb +47 -0
  8. data/lib/stannum/attribute.rb +115 -0
  9. data/lib/stannum/constraint.rb +65 -0
  10. data/lib/stannum/constraints/absence.rb +42 -0
  11. data/lib/stannum/constraints/anything.rb +28 -0
  12. data/lib/stannum/constraints/base.rb +285 -0
  13. data/lib/stannum/constraints/boolean.rb +33 -0
  14. data/lib/stannum/constraints/delegator.rb +71 -0
  15. data/lib/stannum/constraints/enum.rb +64 -0
  16. data/lib/stannum/constraints/equality.rb +47 -0
  17. data/lib/stannum/constraints/hashes/extra_keys.rb +126 -0
  18. data/lib/stannum/constraints/hashes/indifferent_key.rb +74 -0
  19. data/lib/stannum/constraints/hashes.rb +11 -0
  20. data/lib/stannum/constraints/identity.rb +46 -0
  21. data/lib/stannum/constraints/nothing.rb +28 -0
  22. data/lib/stannum/constraints/presence.rb +42 -0
  23. data/lib/stannum/constraints/signature.rb +92 -0
  24. data/lib/stannum/constraints/signatures/map.rb +17 -0
  25. data/lib/stannum/constraints/signatures/tuple.rb +17 -0
  26. data/lib/stannum/constraints/signatures.rb +11 -0
  27. data/lib/stannum/constraints/tuples/extra_items.rb +84 -0
  28. data/lib/stannum/constraints/tuples.rb +10 -0
  29. data/lib/stannum/constraints/type.rb +113 -0
  30. data/lib/stannum/constraints/types/array_type.rb +148 -0
  31. data/lib/stannum/constraints/types/big_decimal_type.rb +16 -0
  32. data/lib/stannum/constraints/types/date_time_type.rb +16 -0
  33. data/lib/stannum/constraints/types/date_type.rb +16 -0
  34. data/lib/stannum/constraints/types/float_type.rb +14 -0
  35. data/lib/stannum/constraints/types/hash_type.rb +205 -0
  36. data/lib/stannum/constraints/types/hash_with_indifferent_keys.rb +21 -0
  37. data/lib/stannum/constraints/types/hash_with_string_keys.rb +21 -0
  38. data/lib/stannum/constraints/types/hash_with_symbol_keys.rb +21 -0
  39. data/lib/stannum/constraints/types/integer_type.rb +14 -0
  40. data/lib/stannum/constraints/types/nil_type.rb +20 -0
  41. data/lib/stannum/constraints/types/proc_type.rb +14 -0
  42. data/lib/stannum/constraints/types/string_type.rb +14 -0
  43. data/lib/stannum/constraints/types/symbol_type.rb +14 -0
  44. data/lib/stannum/constraints/types/time_type.rb +14 -0
  45. data/lib/stannum/constraints/types.rb +25 -0
  46. data/lib/stannum/constraints/union.rb +85 -0
  47. data/lib/stannum/constraints.rb +26 -0
  48. data/lib/stannum/contract.rb +243 -0
  49. data/lib/stannum/contracts/array_contract.rb +108 -0
  50. data/lib/stannum/contracts/base.rb +597 -0
  51. data/lib/stannum/contracts/builder.rb +72 -0
  52. data/lib/stannum/contracts/definition.rb +74 -0
  53. data/lib/stannum/contracts/hash_contract.rb +136 -0
  54. data/lib/stannum/contracts/indifferent_hash_contract.rb +78 -0
  55. data/lib/stannum/contracts/map_contract.rb +199 -0
  56. data/lib/stannum/contracts/parameters/arguments_contract.rb +185 -0
  57. data/lib/stannum/contracts/parameters/keywords_contract.rb +174 -0
  58. data/lib/stannum/contracts/parameters/signature_contract.rb +29 -0
  59. data/lib/stannum/contracts/parameters.rb +15 -0
  60. data/lib/stannum/contracts/parameters_contract.rb +530 -0
  61. data/lib/stannum/contracts/tuple_contract.rb +213 -0
  62. data/lib/stannum/contracts.rb +19 -0
  63. data/lib/stannum/errors.rb +730 -0
  64. data/lib/stannum/messages/default_strategy.rb +124 -0
  65. data/lib/stannum/messages.rb +25 -0
  66. data/lib/stannum/parameter_validation.rb +216 -0
  67. data/lib/stannum/rspec/match_errors.rb +17 -0
  68. data/lib/stannum/rspec/match_errors_matcher.rb +93 -0
  69. data/lib/stannum/rspec/validate_parameter.rb +23 -0
  70. data/lib/stannum/rspec/validate_parameter_matcher.rb +506 -0
  71. data/lib/stannum/rspec.rb +8 -0
  72. data/lib/stannum/schema.rb +131 -0
  73. data/lib/stannum/struct.rb +444 -0
  74. data/lib/stannum/support/coercion.rb +114 -0
  75. data/lib/stannum/support/optional.rb +69 -0
  76. data/lib/stannum/support.rb +8 -0
  77. data/lib/stannum/version.rb +57 -0
  78. data/lib/stannum.rb +27 -0
  79. 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