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.
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