entitlements 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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