entitlements 0.1.8 → 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.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/deploy-entitlements +10 -1
  4. data/lib/contracts-ruby2/CHANGELOG.markdown +115 -0
  5. data/lib/contracts-ruby2/Gemfile +17 -0
  6. data/lib/contracts-ruby2/LICENSE +23 -0
  7. data/lib/contracts-ruby2/README.md +108 -0
  8. data/lib/contracts-ruby2/Rakefile +8 -0
  9. data/lib/contracts-ruby2/TODO.markdown +6 -0
  10. data/lib/contracts-ruby2/TUTORIAL.md +773 -0
  11. data/lib/contracts-ruby2/benchmarks/bench.rb +67 -0
  12. data/lib/contracts-ruby2/benchmarks/hash.rb +69 -0
  13. data/lib/contracts-ruby2/benchmarks/invariants.rb +91 -0
  14. data/lib/contracts-ruby2/benchmarks/io.rb +62 -0
  15. data/lib/contracts-ruby2/benchmarks/wrap_test.rb +57 -0
  16. data/lib/contracts-ruby2/contracts.gemspec +17 -0
  17. data/lib/contracts-ruby2/cucumber.yml +1 -0
  18. data/lib/contracts-ruby2/dependabot.yml +20 -0
  19. data/lib/contracts-ruby2/features/README.md +17 -0
  20. data/lib/contracts-ruby2/features/basics/functype.feature +71 -0
  21. data/lib/contracts-ruby2/features/basics/pretty-print.feature +241 -0
  22. data/lib/contracts-ruby2/features/basics/simple_example.feature +210 -0
  23. data/lib/contracts-ruby2/features/builtin_contracts/README.md +22 -0
  24. data/lib/contracts-ruby2/features/builtin_contracts/and.feature +103 -0
  25. data/lib/contracts-ruby2/features/builtin_contracts/any.feature +44 -0
  26. data/lib/contracts-ruby2/features/builtin_contracts/args.feature +80 -0
  27. data/lib/contracts-ruby2/features/builtin_contracts/array_of.feature +1 -0
  28. data/lib/contracts-ruby2/features/builtin_contracts/bool.feature +64 -0
  29. data/lib/contracts-ruby2/features/builtin_contracts/enum.feature +1 -0
  30. data/lib/contracts-ruby2/features/builtin_contracts/eq.feature +1 -0
  31. data/lib/contracts-ruby2/features/builtin_contracts/exactly.feature +1 -0
  32. data/lib/contracts-ruby2/features/builtin_contracts/func.feature +1 -0
  33. data/lib/contracts-ruby2/features/builtin_contracts/hash_of.feature +1 -0
  34. data/lib/contracts-ruby2/features/builtin_contracts/int.feature +93 -0
  35. data/lib/contracts-ruby2/features/builtin_contracts/keyword_args.feature +1 -0
  36. data/lib/contracts-ruby2/features/builtin_contracts/maybe.feature +1 -0
  37. data/lib/contracts-ruby2/features/builtin_contracts/nat.feature +115 -0
  38. data/lib/contracts-ruby2/features/builtin_contracts/nat_pos.feature +119 -0
  39. data/lib/contracts-ruby2/features/builtin_contracts/neg.feature +115 -0
  40. data/lib/contracts-ruby2/features/builtin_contracts/none.feature +145 -0
  41. data/lib/contracts-ruby2/features/builtin_contracts/not.feature +1 -0
  42. data/lib/contracts-ruby2/features/builtin_contracts/num.feature +64 -0
  43. data/lib/contracts-ruby2/features/builtin_contracts/or.feature +83 -0
  44. data/lib/contracts-ruby2/features/builtin_contracts/pos.feature +116 -0
  45. data/lib/contracts-ruby2/features/builtin_contracts/range_of.feature +1 -0
  46. data/lib/contracts-ruby2/features/builtin_contracts/respond_to.feature +78 -0
  47. data/lib/contracts-ruby2/features/builtin_contracts/send.feature +147 -0
  48. data/lib/contracts-ruby2/features/builtin_contracts/set_of.feature +1 -0
  49. data/lib/contracts-ruby2/features/builtin_contracts/xor.feature +99 -0
  50. data/lib/contracts-ruby2/features/support/env.rb +6 -0
  51. data/lib/contracts-ruby2/lib/contracts/attrs.rb +24 -0
  52. data/lib/contracts-ruby2/lib/contracts/builtin_contracts.rb +542 -0
  53. data/lib/contracts-ruby2/lib/contracts/call_with.rb +108 -0
  54. data/lib/contracts-ruby2/lib/contracts/core.rb +52 -0
  55. data/lib/contracts-ruby2/lib/contracts/decorators.rb +47 -0
  56. data/lib/contracts-ruby2/lib/contracts/engine/base.rb +136 -0
  57. data/lib/contracts-ruby2/lib/contracts/engine/eigenclass.rb +50 -0
  58. data/lib/contracts-ruby2/lib/contracts/engine/target.rb +70 -0
  59. data/lib/contracts-ruby2/lib/contracts/engine.rb +26 -0
  60. data/lib/contracts-ruby2/lib/contracts/errors.rb +71 -0
  61. data/lib/contracts-ruby2/lib/contracts/formatters.rb +136 -0
  62. data/lib/contracts-ruby2/lib/contracts/invariants.rb +68 -0
  63. data/lib/contracts-ruby2/lib/contracts/method_handler.rb +187 -0
  64. data/lib/contracts-ruby2/lib/contracts/method_reference.rb +100 -0
  65. data/lib/contracts-ruby2/lib/contracts/support.rb +61 -0
  66. data/lib/contracts-ruby2/lib/contracts/validators.rb +139 -0
  67. data/lib/contracts-ruby2/lib/contracts/version.rb +3 -0
  68. data/lib/contracts-ruby2/lib/contracts.rb +281 -0
  69. data/lib/contracts-ruby2/script/docs-release +3 -0
  70. data/lib/contracts-ruby2/script/docs-staging +3 -0
  71. data/lib/contracts-ruby2/script/rubocop.rb +5 -0
  72. data/lib/contracts-ruby2/spec/attrs_spec.rb +119 -0
  73. data/lib/contracts-ruby2/spec/builtin_contracts_spec.rb +461 -0
  74. data/lib/contracts-ruby2/spec/contracts_spec.rb +770 -0
  75. data/lib/contracts-ruby2/spec/fixtures/fixtures.rb +730 -0
  76. data/lib/contracts-ruby2/spec/invariants_spec.rb +17 -0
  77. data/lib/contracts-ruby2/spec/methods_spec.rb +54 -0
  78. data/lib/contracts-ruby2/spec/module_spec.rb +18 -0
  79. data/lib/contracts-ruby2/spec/override_validators_spec.rb +162 -0
  80. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  81. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  82. data/lib/contracts-ruby2/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  83. data/lib/contracts-ruby2/spec/spec_helper.rb +102 -0
  84. data/lib/contracts-ruby2/spec/support.rb +10 -0
  85. data/lib/contracts-ruby2/spec/support_spec.rb +21 -0
  86. data/lib/contracts-ruby2/spec/validators_spec.rb +47 -0
  87. data/lib/contracts-ruby3/CHANGELOG.markdown +117 -0
  88. data/lib/contracts-ruby3/Gemfile +21 -0
  89. data/lib/contracts-ruby3/LICENSE +23 -0
  90. data/lib/contracts-ruby3/README.md +114 -0
  91. data/lib/contracts-ruby3/Rakefile +10 -0
  92. data/lib/contracts-ruby3/TODO.markdown +6 -0
  93. data/lib/contracts-ruby3/TUTORIAL.md +773 -0
  94. data/lib/contracts-ruby3/benchmarks/bench.rb +67 -0
  95. data/lib/contracts-ruby3/benchmarks/hash.rb +69 -0
  96. data/lib/contracts-ruby3/benchmarks/invariants.rb +91 -0
  97. data/lib/contracts-ruby3/benchmarks/io.rb +62 -0
  98. data/lib/contracts-ruby3/benchmarks/wrap_test.rb +57 -0
  99. data/lib/contracts-ruby3/contracts.gemspec +20 -0
  100. data/lib/contracts-ruby3/cucumber.yml +1 -0
  101. data/lib/contracts-ruby3/dependabot.yml +20 -0
  102. data/lib/contracts-ruby3/features/README.md +17 -0
  103. data/lib/contracts-ruby3/features/basics/functype.feature +71 -0
  104. data/lib/contracts-ruby3/features/basics/pretty-print.feature +241 -0
  105. data/lib/contracts-ruby3/features/basics/simple_example.feature +210 -0
  106. data/lib/contracts-ruby3/features/builtin_contracts/README.md +22 -0
  107. data/lib/contracts-ruby3/features/builtin_contracts/and.feature +103 -0
  108. data/lib/contracts-ruby3/features/builtin_contracts/any.feature +44 -0
  109. data/lib/contracts-ruby3/features/builtin_contracts/args.feature +80 -0
  110. data/lib/contracts-ruby3/features/builtin_contracts/array_of.feature +1 -0
  111. data/lib/contracts-ruby3/features/builtin_contracts/bool.feature +64 -0
  112. data/lib/contracts-ruby3/features/builtin_contracts/enum.feature +1 -0
  113. data/lib/contracts-ruby3/features/builtin_contracts/eq.feature +1 -0
  114. data/lib/contracts-ruby3/features/builtin_contracts/exactly.feature +1 -0
  115. data/lib/contracts-ruby3/features/builtin_contracts/func.feature +1 -0
  116. data/lib/contracts-ruby3/features/builtin_contracts/hash_of.feature +1 -0
  117. data/lib/contracts-ruby3/features/builtin_contracts/int.feature +93 -0
  118. data/lib/contracts-ruby3/features/builtin_contracts/keyword_args.feature +1 -0
  119. data/lib/contracts-ruby3/features/builtin_contracts/maybe.feature +1 -0
  120. data/lib/contracts-ruby3/features/builtin_contracts/nat.feature +115 -0
  121. data/lib/contracts-ruby3/features/builtin_contracts/nat_pos.feature +119 -0
  122. data/lib/contracts-ruby3/features/builtin_contracts/neg.feature +115 -0
  123. data/lib/contracts-ruby3/features/builtin_contracts/none.feature +145 -0
  124. data/lib/contracts-ruby3/features/builtin_contracts/not.feature +1 -0
  125. data/lib/contracts-ruby3/features/builtin_contracts/num.feature +64 -0
  126. data/lib/contracts-ruby3/features/builtin_contracts/or.feature +83 -0
  127. data/lib/contracts-ruby3/features/builtin_contracts/pos.feature +116 -0
  128. data/lib/contracts-ruby3/features/builtin_contracts/range_of.feature +1 -0
  129. data/lib/contracts-ruby3/features/builtin_contracts/respond_to.feature +78 -0
  130. data/lib/contracts-ruby3/features/builtin_contracts/send.feature +147 -0
  131. data/lib/contracts-ruby3/features/builtin_contracts/set_of.feature +1 -0
  132. data/lib/contracts-ruby3/features/builtin_contracts/xor.feature +99 -0
  133. data/lib/contracts-ruby3/features/support/env.rb +8 -0
  134. data/lib/contracts-ruby3/lib/contracts/attrs.rb +26 -0
  135. data/lib/contracts-ruby3/lib/contracts/builtin_contracts.rb +575 -0
  136. data/lib/contracts-ruby3/lib/contracts/call_with.rb +119 -0
  137. data/lib/contracts-ruby3/lib/contracts/core.rb +54 -0
  138. data/lib/contracts-ruby3/lib/contracts/decorators.rb +50 -0
  139. data/lib/contracts-ruby3/lib/contracts/engine/base.rb +137 -0
  140. data/lib/contracts-ruby3/lib/contracts/engine/eigenclass.rb +51 -0
  141. data/lib/contracts-ruby3/lib/contracts/engine/target.rb +72 -0
  142. data/lib/contracts-ruby3/lib/contracts/engine.rb +28 -0
  143. data/lib/contracts-ruby3/lib/contracts/errors.rb +74 -0
  144. data/lib/contracts-ruby3/lib/contracts/formatters.rb +140 -0
  145. data/lib/contracts-ruby3/lib/contracts/invariants.rb +72 -0
  146. data/lib/contracts-ruby3/lib/contracts/method_handler.rb +197 -0
  147. data/lib/contracts-ruby3/lib/contracts/method_reference.rb +102 -0
  148. data/lib/contracts-ruby3/lib/contracts/support.rb +63 -0
  149. data/lib/contracts-ruby3/lib/contracts/validators.rb +143 -0
  150. data/lib/contracts-ruby3/lib/contracts/version.rb +5 -0
  151. data/lib/contracts-ruby3/lib/contracts.rb +290 -0
  152. data/lib/contracts-ruby3/script/docs-release +3 -0
  153. data/lib/contracts-ruby3/script/docs-staging +3 -0
  154. data/lib/contracts-ruby3/script/rubocop.rb +5 -0
  155. data/lib/contracts-ruby3/spec/attrs_spec.rb +119 -0
  156. data/lib/contracts-ruby3/spec/builtin_contracts_spec.rb +457 -0
  157. data/lib/contracts-ruby3/spec/contracts_spec.rb +773 -0
  158. data/lib/contracts-ruby3/spec/fixtures/fixtures.rb +725 -0
  159. data/lib/contracts-ruby3/spec/invariants_spec.rb +17 -0
  160. data/lib/contracts-ruby3/spec/methods_spec.rb +54 -0
  161. data/lib/contracts-ruby3/spec/module_spec.rb +18 -0
  162. data/lib/contracts-ruby3/spec/override_validators_spec.rb +162 -0
  163. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
  164. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
  165. data/lib/contracts-ruby3/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
  166. data/lib/contracts-ruby3/spec/spec_helper.rb +102 -0
  167. data/lib/contracts-ruby3/spec/support.rb +10 -0
  168. data/lib/contracts-ruby3/spec/support_spec.rb +21 -0
  169. data/lib/contracts-ruby3/spec/validators_spec.rb +47 -0
  170. data/lib/entitlements/data/groups/calculated/yaml.rb +7 -1
  171. data/lib/entitlements/data/people/yaml.rb +9 -1
  172. data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +5 -1
  173. data/lib/entitlements/extras/orgchart/person_methods.rb +7 -1
  174. data/lib/entitlements.rb +13 -2
  175. data/lib/ruby_version_check.rb +17 -0
  176. metadata +209 -14
@@ -0,0 +1,575 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "contracts/formatters"
4
+ require "set"
5
+
6
+ # rdoc
7
+ # This module contains all the builtin contracts.
8
+ # If you want to use them, first:
9
+ #
10
+ # import Contracts
11
+ #
12
+ # And then use these or write your own!
13
+ #
14
+ # A simple example:
15
+ #
16
+ # Contract Num, Num => Num
17
+ # def add(a, b)
18
+ # a + b
19
+ # end
20
+ #
21
+ # The contract is <tt>Contract Num, Num, Num</tt>.
22
+ # That says that the +add+ function takes two numbers and returns a number.
23
+ module Contracts
24
+ module Builtin
25
+ # Check that an argument is +Numeric+.
26
+ class Num
27
+ def self.valid? val
28
+ val.is_a? Numeric
29
+ end
30
+ end
31
+
32
+ # Check that an argument is a positive number.
33
+ class Pos
34
+ def self.valid? val
35
+ val.is_a?(Numeric) && val.positive?
36
+ end
37
+ end
38
+
39
+ # Check that an argument is a negative number.
40
+ class Neg
41
+ def self.valid? val
42
+ val.is_a?(Numeric) && val.negative?
43
+ end
44
+ end
45
+
46
+ # Check that an argument is an +Integer+.
47
+ class Int
48
+ def self.valid? val
49
+ val.is_a?(Integer)
50
+ end
51
+ end
52
+
53
+ # Check that an argument is a natural number (includes zero).
54
+ class Nat
55
+ def self.valid? val
56
+ val.is_a?(Integer) && val >= 0
57
+ end
58
+ end
59
+
60
+ # Check that an argument is a positive natural number (excludes zero).
61
+ class NatPos
62
+ def self.valid? val
63
+ val.is_a?(Integer) && val.positive?
64
+ end
65
+ end
66
+
67
+ # Passes for any argument.
68
+ class Any
69
+ def self.valid? val
70
+ true
71
+ end
72
+ end
73
+
74
+ # Fails for any argument.
75
+ class None
76
+ def self.valid? val
77
+ false
78
+ end
79
+ end
80
+
81
+ # Use this when you are writing your own contract classes.
82
+ # Allows your contract to be called with <tt>[]</tt> instead of <tt>.new</tt>:
83
+ #
84
+ # Old: <tt>Or.new(param1, param2)</tt>
85
+ #
86
+ # New: <tt>Or[param1, param2]</tt>
87
+ #
88
+ # Of course, <tt>.new</tt> still works.
89
+ class CallableClass
90
+ include ::Contracts::Formatters
91
+ def self.[](*vals)
92
+ new(*vals)
93
+ end
94
+ end
95
+
96
+ # Takes a variable number of contracts.
97
+ # The contract passes if any of the contracts pass.
98
+ # Example: <tt>Or[Fixnum, Float]</tt>
99
+ class Or < CallableClass
100
+ def initialize(*vals)
101
+ super()
102
+ @vals = vals
103
+ end
104
+
105
+ def valid?(val)
106
+ @vals.any? do |contract|
107
+ res, _ = Contract.valid?(val, contract)
108
+ res
109
+ end
110
+ end
111
+
112
+ def to_s
113
+ # rubocop:disable Style/StringConcatenation
114
+ @vals[0, @vals.size-1].map do |x|
115
+ InspectWrapper.create(x)
116
+ end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
117
+ # rubocop:enable Style/StringConcatenation
118
+ end
119
+ end
120
+
121
+ # Takes a variable number of contracts.
122
+ # The contract passes if exactly one of those contracts pass.
123
+ # Example: <tt>Xor[Fixnum, Float]</tt>
124
+ class Xor < CallableClass
125
+ def initialize(*vals)
126
+ super()
127
+ @vals = vals
128
+ end
129
+
130
+ def valid?(val)
131
+ results = @vals.map do |contract|
132
+ res, _ = Contract.valid?(val, contract)
133
+ res
134
+ end
135
+ results.count(true) == 1
136
+ end
137
+
138
+ def to_s
139
+ # rubocop:disable Style/StringConcatenation
140
+ @vals[0, @vals.size-1].map do |x|
141
+ InspectWrapper.create(x)
142
+ end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
143
+ # rubocop:enable Style/StringConcatenation
144
+ end
145
+ end
146
+
147
+ # Takes a variable number of contracts.
148
+ # The contract passes if all contracts pass.
149
+ # Example: <tt>And[Fixnum, Float]</tt>
150
+ class And < CallableClass
151
+ def initialize(*vals)
152
+ super()
153
+ @vals = vals
154
+ end
155
+
156
+ def valid?(val)
157
+ @vals.all? do |contract|
158
+ res, _ = Contract.valid?(val, contract)
159
+ res
160
+ end
161
+ end
162
+
163
+ def to_s
164
+ # rubocop:disable Style/StringConcatenation
165
+ @vals[0, @vals.size-1].map do |x|
166
+ InspectWrapper.create(x)
167
+ end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
168
+ # rubocop:enable Style/StringConcatenation
169
+ end
170
+ end
171
+
172
+ # Takes a variable number of method names as symbols.
173
+ # The contract passes if the argument responds to all
174
+ # of those methods.
175
+ # Example: <tt>RespondTo[:password, :credit_card]</tt>
176
+ class RespondTo < CallableClass
177
+ def initialize(*meths)
178
+ super()
179
+ @meths = meths
180
+ end
181
+
182
+ def valid?(val)
183
+ @meths.all? do |meth|
184
+ val.respond_to? meth
185
+ end
186
+ end
187
+
188
+ def to_s
189
+ "a value that responds to #{@meths.inspect}"
190
+ end
191
+ end
192
+
193
+ # Takes a variable number of method names as symbols.
194
+ # Given an argument, all of those methods are called
195
+ # on the argument one by one. If they all return true,
196
+ # the contract passes.
197
+ # Example: <tt>Send[:valid?]</tt>
198
+ class Send < CallableClass
199
+ def initialize(*meths)
200
+ super()
201
+ @meths = meths
202
+ end
203
+
204
+ def valid?(val)
205
+ @meths.all? do |meth|
206
+ val.send(meth)
207
+ end
208
+ end
209
+
210
+ def to_s
211
+ "a value that returns true for all of #{@meths.inspect}"
212
+ end
213
+ end
214
+
215
+ # Takes a class +A+. If argument is object of type +A+, the contract passes.
216
+ # If it is a subclass of A (or not related to A in any way), it fails.
217
+ # Example: <tt>Exactly[Numeric]</tt>
218
+ class Exactly < CallableClass
219
+ def initialize(cls)
220
+ super()
221
+ @cls = cls
222
+ end
223
+
224
+ def valid?(val)
225
+ val.instance_of?(@cls)
226
+ end
227
+
228
+ def to_s
229
+ "exactly #{@cls.inspect}"
230
+ end
231
+ end
232
+
233
+ # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
234
+ # the list, the contract passes.
235
+ #
236
+ # Example: <tt>Enum[:a, :b, :c]</tt>?
237
+ class Enum < CallableClass
238
+ def initialize(*vals)
239
+ super()
240
+ @vals = vals
241
+ end
242
+
243
+ def valid?(val)
244
+ @vals.include? val
245
+ end
246
+ end
247
+
248
+ # Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
249
+ # otherwise the contract fails.
250
+ # Example: <tt>Eq[Class]</tt>
251
+ class Eq < CallableClass
252
+ def initialize(value)
253
+ super()
254
+ @value = value
255
+ end
256
+
257
+ def valid?(val)
258
+ @value.equal?(val)
259
+ end
260
+
261
+ def to_s
262
+ "to be equal to #{@value.inspect}"
263
+ end
264
+ end
265
+
266
+ # Takes a variable number of contracts. The contract
267
+ # passes if all of those contracts fail for the given argument.
268
+ # Example: <tt>Not[nil]</tt>
269
+ class Not < CallableClass
270
+ def initialize(*vals)
271
+ super()
272
+ @vals = vals
273
+ end
274
+
275
+ def valid?(val)
276
+ @vals.all? do |contract|
277
+ res, _ = Contract.valid?(val, contract)
278
+ !res
279
+ end
280
+ end
281
+
282
+ def to_s
283
+ "a value that is none of #{@vals.inspect}"
284
+ end
285
+ end
286
+
287
+ # @private
288
+ # Takes a collection(responds to :each) type and a contract.
289
+ # The related argument must be of specified collection type.
290
+ # Checks the contract against every element of the collection.
291
+ # If it passes for all elements, the contract passes.
292
+ # Example: <tt>CollectionOf[Array, Num]</tt>
293
+ class CollectionOf < CallableClass
294
+ def initialize(collection_class, contract)
295
+ super()
296
+ @collection_class = collection_class
297
+ @contract = contract
298
+ end
299
+
300
+ def valid?(vals)
301
+ return false unless vals.is_a?(@collection_class)
302
+
303
+ vals.all? do |val|
304
+ res, _ = Contract.valid?(val, @contract)
305
+ res
306
+ end
307
+ end
308
+
309
+ def to_s
310
+ "a collection #{@collection_class} of #{@contract}"
311
+ end
312
+
313
+ class Factory
314
+ def initialize(collection_class, &before_new)
315
+ @collection_class = collection_class
316
+ @before_new = before_new
317
+ end
318
+
319
+ def new(contract)
320
+ @before_new&.call
321
+ CollectionOf.new(@collection_class, contract)
322
+ end
323
+
324
+ alias_method :[], :new
325
+ end
326
+ end
327
+
328
+ # Takes a contract. The related argument must be an array.
329
+ # Checks the contract against every element of the array.
330
+ # If it passes for all elements, the contract passes.
331
+ # Example: <tt>ArrayOf[Num]</tt>
332
+ ArrayOf = CollectionOf::Factory.new(Array)
333
+
334
+ # Takes a contract. The related argument must be a set.
335
+ # Checks the contract against every element of the set.
336
+ # If it passes for all elements, the contract passes.
337
+ # Example: <tt>SetOf[Num]</tt>
338
+ SetOf = CollectionOf::Factory.new(Set)
339
+
340
+ # Used for <tt>*args</tt> (variadic functions). Takes a contract
341
+ # and uses it to validate every element passed in
342
+ # through <tt>*args</tt>.
343
+ # Example: <tt>Args[Or[String, Num]]</tt>
344
+ class Args < CallableClass
345
+ attr_reader :contract
346
+
347
+ def initialize(contract)
348
+ super()
349
+ @contract = contract
350
+ end
351
+
352
+ def to_s
353
+ "Args[#{@contract}]"
354
+ end
355
+ end
356
+
357
+ class Bool
358
+ def self.valid? val
359
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
360
+ end
361
+ end
362
+
363
+ # Use this to specify a Range object of a particular datatype.
364
+ # Example: <tt>RangeOf[Nat]</tt>, <tt>RangeOf[Date]</tt>, ...
365
+ class RangeOf < CallableClass
366
+ def initialize(contract)
367
+ super()
368
+ @contract = contract
369
+ end
370
+
371
+ def valid?(val)
372
+ val.is_a?(Range) &&
373
+ Contract.valid?(val.first, @contract) &&
374
+ Contract.valid?(val.last, @contract)
375
+ end
376
+
377
+ def to_s
378
+ "a range of #{@contract}"
379
+ end
380
+ end
381
+
382
+ # Use this to specify the Hash characteristics. Takes two contracts,
383
+ # one for hash keys and one for hash values.
384
+ # Example: <tt>HashOf[Symbol, String]</tt>
385
+ class HashOf < CallableClass
386
+ INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
387
+
388
+ def initialize(key, value = nil)
389
+ super()
390
+ if value
391
+ @key = key
392
+ @value = value
393
+ else
394
+ validate_hash(key)
395
+ @key = key.keys.first
396
+ @value = key[@key]
397
+ end
398
+ end
399
+
400
+ def valid?(hash)
401
+ return false unless hash.is_a?(Hash)
402
+
403
+ keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
404
+ vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
405
+
406
+ [keys_match, vals_match].all?
407
+ end
408
+
409
+ def to_s
410
+ "Hash<#{@key}, #{@value}>"
411
+ end
412
+
413
+ private
414
+
415
+ def validate_hash(hash)
416
+ fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
417
+ end
418
+ end
419
+
420
+ # Use this to specify the Hash characteristics. This contracts fails
421
+ # if there are any extra keys that don't have contracts on them.
422
+ # Example: <tt>StrictHash[{ a: String, b: Bool }]</tt>
423
+ class StrictHash < CallableClass
424
+ attr_reader :contract_hash
425
+
426
+ def initialize(contract_hash)
427
+ super()
428
+ @contract_hash = contract_hash
429
+ end
430
+
431
+ def valid?(arg)
432
+ return false unless arg.is_a?(Hash)
433
+ return false unless arg.keys.sort.eql?(contract_hash.keys.sort)
434
+
435
+ contract_hash.all? do |key, contract|
436
+ contract_hash.key?(key) && Contract.valid?(arg[key], contract)
437
+ end
438
+ end
439
+ end
440
+
441
+ # Use this for specifying contracts for keyword arguments
442
+ # Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
443
+ class KeywordArgs < CallableClass
444
+ def initialize(options)
445
+ super()
446
+ @options = options
447
+ end
448
+
449
+ def valid?(hash)
450
+ return false unless hash.is_a?(Hash)
451
+ return false unless hash.keys - options.keys == []
452
+
453
+ options.all? do |key, contract|
454
+ Optional._valid?(hash, key, contract)
455
+ end
456
+ end
457
+
458
+ def to_s
459
+ "KeywordArgs[#{options}]"
460
+ end
461
+
462
+ def inspect
463
+ to_s
464
+ end
465
+
466
+ private
467
+
468
+ attr_reader :options
469
+ end
470
+
471
+ # Use this for specifying contracts for class arguments
472
+ # Example: <tt>DescendantOf[ e: Range, f: Optional[Num] ]</tt>
473
+ class DescendantOf < CallableClass
474
+ def initialize(parent_class)
475
+ super()
476
+ @parent_class = parent_class
477
+ end
478
+
479
+ def valid?(given_class)
480
+ given_class.is_a?(Class) && given_class.ancestors.include?(parent_class)
481
+ end
482
+
483
+ def to_s
484
+ "DescendantOf[#{parent_class}]"
485
+ end
486
+
487
+ def inspect
488
+ to_s
489
+ end
490
+
491
+ private
492
+
493
+ attr_reader :parent_class
494
+ end
495
+
496
+ # Use this for specifying optional keyword argument
497
+ # Example: <tt>Optional[Num]</tt>
498
+ class Optional < CallableClass
499
+ UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
500
+ "Unable to use Optional contract outside of KeywordArgs contract"
501
+
502
+ def self._valid?(hash, key, contract)
503
+ return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
504
+
505
+ contract.within_opt_hash!
506
+ !hash.key?(key) || Contract.valid?(hash[key], contract)
507
+ end
508
+
509
+ def initialize(contract)
510
+ super()
511
+ @contract = contract
512
+ @within_opt_hash = false
513
+ end
514
+
515
+ def within_opt_hash!
516
+ @within_opt_hash = true
517
+ self
518
+ end
519
+
520
+ def valid?(value)
521
+ ensure_within_opt_hash
522
+ Contract.valid?(value, contract)
523
+ end
524
+
525
+ def to_s
526
+ "Optional[#{formatted_contract}]"
527
+ end
528
+
529
+ def inspect
530
+ to_s
531
+ end
532
+
533
+ private
534
+
535
+ attr_reader :contract, :within_opt_hash
536
+
537
+ def ensure_within_opt_hash
538
+ return if within_opt_hash
539
+
540
+ fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
541
+ end
542
+
543
+ def formatted_contract
544
+ Formatters::InspectWrapper.create(contract)
545
+ end
546
+ end
547
+
548
+ # Takes a Contract.
549
+ # The contract passes if the contract passes or the given value is nil.
550
+ # Maybe(foo) is equivalent to Or[foo, nil].
551
+ class Maybe < Or
552
+ def initialize(*vals)
553
+ super(*(vals + [nil]))
554
+ end
555
+
556
+ def include_proc?
557
+ @vals.include? Proc
558
+ end
559
+ end
560
+
561
+ # Used to define contracts on functions passed in as arguments.
562
+ # Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
563
+ class Func < CallableClass
564
+ attr_reader :contracts
565
+
566
+ def initialize(*contracts)
567
+ super()
568
+ @contracts = contracts
569
+ end
570
+ end
571
+ end
572
+
573
+ # Users can still include `Contracts::Core` & `Contracts::Builtin`
574
+ include Builtin
575
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contracts
4
+ module CallWith
5
+ def call_with(this, *args, **kargs, &blk)
6
+ call_with_inner(false, this, *args, **kargs, &blk)
7
+ end
8
+
9
+ def call_with_inner(returns, this, *args, **kargs, &blk)
10
+ args << blk if blk
11
+
12
+ # Explicitly append blk=nil if nil != Proc contract violation anticipated
13
+ nil_block_appended = maybe_append_block!(args, blk)
14
+
15
+ # Explicitly append options={} if Hash contract is present
16
+ kargs_appended = maybe_append_options!(args, kargs, blk)
17
+
18
+ # Loop forward validating the arguments up to the splat (if there is one)
19
+ (@args_contract_index || args.size).times do |i|
20
+ contract = args_contracts[i]
21
+ arg = args[i]
22
+ validator = @args_validators[i]
23
+
24
+ unless validator && validator[arg]
25
+ data = {
26
+ arg: arg,
27
+ contract: contract,
28
+ class: klass,
29
+ method: method,
30
+ contracts: self,
31
+ arg_pos: i+1,
32
+ total_args: args.size,
33
+ return_value: false,
34
+ }
35
+ return ParamContractError.new("as return value", data) if returns
36
+ return unless Contract.failure_callback(data)
37
+ end
38
+
39
+ if contract.is_a?(Contracts::Func) && blk && !nil_block_appended
40
+ blk = Contract.new(klass, arg, *contract.contracts)
41
+ elsif contract.is_a?(Contracts::Func)
42
+ args[i] = Contract.new(klass, arg, *contract.contracts)
43
+ end
44
+ end
45
+
46
+ # If there is a splat loop backwards to the lower index of the splat
47
+ # Once we hit the splat in this direction set its upper index
48
+ # Keep validating but use this upper index to get the splat validator.
49
+ if @args_contract_index
50
+ splat_upper_index = @args_contract_index
51
+ (args.size - @args_contract_index).times do |i|
52
+ arg = args[args.size - 1 - i]
53
+
54
+ if args_contracts[args_contracts.size - 1 - i].is_a?(Contracts::Args)
55
+ splat_upper_index = i
56
+ end
57
+
58
+ # Each arg after the spat is found must use the splat validator
59
+ j = i < splat_upper_index ? i : splat_upper_index
60
+ contract = args_contracts[args_contracts.size - 1 - j]
61
+ validator = @args_validators[args_contracts.size - 1 - j]
62
+
63
+ unless validator && validator[arg]
64
+ # rubocop:disable Style/SoleNestedConditional
65
+ return unless Contract.failure_callback({
66
+ :arg => arg,
67
+ :contract => contract,
68
+ :class => klass,
69
+ :method => method,
70
+ :contracts => self,
71
+ :arg_pos => i - 1,
72
+ :total_args => args.size,
73
+ :return_value => false,
74
+ })
75
+ # rubocop:enable Style/SoleNestedConditional
76
+ end
77
+
78
+ if contract.is_a?(Contracts::Func)
79
+ args[args.size - 1 - i] = Contract.new(klass, arg, *contract.contracts)
80
+ end
81
+ end
82
+ end
83
+
84
+ # If we put the block into args for validating, restore the args
85
+ # OR if we added a fake nil at the end because a block wasn't passed in.
86
+ args.slice!(-1) if blk || nil_block_appended
87
+ args.slice!(-1) if kargs_appended
88
+ result = if method.respond_to?(:call)
89
+ # proc, block, lambda, etc
90
+ method.call(*args, **kargs, &blk)
91
+ else
92
+ # original method name reference
93
+ # Don't reassign blk, else Travis CI shows "stack level too deep".
94
+ target_blk = blk
95
+ target_blk = lambda { |*params| blk.call(*params) } if blk.is_a?(Contract)
96
+ method.send_to(this, *args, **kargs, &target_blk)
97
+ end
98
+
99
+ unless @ret_validator[result]
100
+ Contract.failure_callback({
101
+ arg: result,
102
+ contract: ret_contract,
103
+ class: klass,
104
+ method: method,
105
+ contracts: self,
106
+ return_value: true,
107
+ })
108
+ end
109
+
110
+ this.verify_invariants!(method) if this.respond_to?(:verify_invariants!)
111
+
112
+ if ret_contract.is_a?(Contracts::Func)
113
+ result = Contract.new(klass, result, *ret_contract.contracts)
114
+ end
115
+
116
+ result
117
+ end
118
+ end
119
+ end