contracts 0.11.0 → 0.12.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 (50) hide show
  1. checksums.yaml +13 -5
  2. data/CHANGELOG.markdown +8 -0
  3. data/Gemfile +3 -0
  4. data/README.md +14 -10
  5. data/TUTORIAL.md +34 -1
  6. data/benchmarks/io.rb +3 -3
  7. data/cucumber.yml +1 -0
  8. data/features/README.md +17 -0
  9. data/features/basics/functype.feature +71 -0
  10. data/features/basics/simple_example.feature +210 -0
  11. data/features/builtin_contracts/README.md +22 -0
  12. data/features/builtin_contracts/and.feature +103 -0
  13. data/features/builtin_contracts/any.feature +44 -0
  14. data/features/builtin_contracts/args.feature +1 -0
  15. data/features/builtin_contracts/array_of.feature +1 -0
  16. data/features/builtin_contracts/bool.feature +64 -0
  17. data/features/builtin_contracts/enum.feature +1 -0
  18. data/features/builtin_contracts/eq.feature +1 -0
  19. data/features/builtin_contracts/exactly.feature +1 -0
  20. data/features/builtin_contracts/func.feature +1 -0
  21. data/features/builtin_contracts/hash_of.feature +1 -0
  22. data/features/builtin_contracts/keyword_args.feature +1 -0
  23. data/features/builtin_contracts/maybe.feature +1 -0
  24. data/features/builtin_contracts/nat.feature +115 -0
  25. data/features/builtin_contracts/neg.feature +115 -0
  26. data/features/builtin_contracts/none.feature +145 -0
  27. data/features/builtin_contracts/not.feature +1 -0
  28. data/features/builtin_contracts/num.feature +64 -0
  29. data/features/builtin_contracts/or.feature +83 -0
  30. data/features/builtin_contracts/pos.feature +116 -0
  31. data/features/builtin_contracts/range_of.feature +1 -0
  32. data/features/builtin_contracts/respond_to.feature +78 -0
  33. data/features/builtin_contracts/send.feature +147 -0
  34. data/features/builtin_contracts/set_of.feature +1 -0
  35. data/features/builtin_contracts/xor.feature +99 -0
  36. data/features/support/env.rb +6 -0
  37. data/lib/contracts.rb +1 -1
  38. data/lib/contracts/builtin_contracts.rb +356 -351
  39. data/lib/contracts/core.rb +11 -2
  40. data/lib/contracts/formatters.rb +2 -2
  41. data/lib/contracts/validators.rb +6 -0
  42. data/lib/contracts/version.rb +1 -1
  43. data/script/cucumber +5 -0
  44. data/script/docs-release +3 -0
  45. data/script/docs-staging +3 -0
  46. data/spec/builtin_contracts_spec.rb +1 -1
  47. data/spec/contracts_spec.rb +29 -0
  48. data/spec/fixtures/fixtures.rb +12 -0
  49. data/spec/validators_spec.rb +25 -3
  50. metadata +42 -9
@@ -0,0 +1,147 @@
1
+ Feature: Send
2
+
3
+ Takes a variable number of method names as symbols. Given an argument, all of
4
+ those methods are called on the argument one by one. If they all return true,
5
+ the contract passes.
6
+
7
+ ```ruby
8
+ Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item]
9
+ ```
10
+
11
+ This contract will pass only if:
12
+ `arg.valid? == true && arg.has_items? == true`,
13
+ where `arg` is the first argument.
14
+
15
+ Background:
16
+ Given a file named "item.rb" with:
17
+ """ruby
18
+ Item = Struct.new(:name, :cost)
19
+ Item::DEFAULT = Item["default", 0]
20
+ """
21
+
22
+ Given a file named "send_usage.rb" with:
23
+ """ruby
24
+ require "contracts"
25
+ C = Contracts
26
+ require "./item"
27
+
28
+ class FetchItemCommand
29
+ include Contracts::Core
30
+
31
+ Contract C::Send[:valid?, :has_items?] => C::ArrayOf[Item]
32
+ def call(subject)
33
+ ([Item::DEFAULT] + subject.items).uniq
34
+ end
35
+ end
36
+ """
37
+
38
+ Scenario: All methods return `true`
39
+ Given a file named "box.rb" with:
40
+ """ruby
41
+ class Box
42
+ def valid?
43
+ true
44
+ end
45
+
46
+ def has_items?
47
+ true
48
+ end
49
+
50
+ def items
51
+ [Item["cat", 599.99]]
52
+ end
53
+ end
54
+
55
+ require "./send_usage"
56
+ p FetchItemCommand.new.call(Box.new)
57
+ """
58
+ When I run `ruby box.rb`
59
+ Then output should contain:
60
+ """
61
+ [#<struct Item name="default", cost=0>, #<struct Item name="cat", cost=599.99>]
62
+ """
63
+
64
+ Scenario: When second method returns `false`
65
+ Given a file named "cat.rb" with:
66
+ """ruby
67
+ class Cat
68
+ def valid?
69
+ true
70
+ end
71
+
72
+ def has_items?
73
+ false
74
+ end
75
+ end
76
+
77
+ require "./send_usage"
78
+ p FetchItemCommand.new.call(Cat.new)
79
+ """
80
+ When I run `ruby cat.rb`
81
+ Then output should contain:
82
+ """
83
+ : Contract violation for argument 1 of 1: (ParamContractError)
84
+ Expected: (a value that returns true for all of [:valid?, :has_items?]),
85
+ """
86
+ And output should contain:
87
+ """
88
+ Actual: #<Cat
89
+ """
90
+
91
+ Scenario: When first method returns `false`
92
+ Given a file named "invalid.rb" with:
93
+ """ruby
94
+ class Invalid
95
+ def valid?
96
+ false
97
+ end
98
+
99
+ def has_items?
100
+ true
101
+ end
102
+
103
+ def items
104
+ []
105
+ end
106
+ end
107
+
108
+ require "./send_usage"
109
+ p FetchItemCommand.new.call(Invalid.new)
110
+ """
111
+ When I run `ruby invalid.rb`
112
+ Then output should contain:
113
+ """
114
+ : Contract violation for argument 1 of 1: (ParamContractError)
115
+ Expected: (a value that returns true for all of [:valid?, :has_items?]),
116
+ """
117
+ And output should contain:
118
+ """
119
+ Actual: #<Invalid
120
+ """
121
+
122
+ Scenario: When all methods return `false`
123
+ Given a file named "nothing.rb" with:
124
+ """ruby
125
+ class Nothing
126
+ def valid?
127
+ false
128
+ end
129
+
130
+ def has_items?
131
+ false
132
+ end
133
+ end
134
+
135
+ require "./send_usage"
136
+ p FetchItemCommand.new.call(Nothing.new)
137
+ """
138
+ When I run `ruby nothing.rb`
139
+ Then output should contain:
140
+ """
141
+ : Contract violation for argument 1 of 1: (ParamContractError)
142
+ Expected: (a value that returns true for all of [:valid?, :has_items?]),
143
+ """
144
+ And output should contain:
145
+ """
146
+ Actual: #<Nothing
147
+ """
@@ -0,0 +1 @@
1
+ Feature: SetOf (TODO)
@@ -0,0 +1,99 @@
1
+ Feature: Xor
2
+
3
+ Takes a variable number of contracts. The contract passes if one and only one
4
+ of the contracts pass.
5
+
6
+ ```ruby
7
+ Contract C::Xor[Float, C::Neg] => String
8
+ ```
9
+
10
+ This example will validate first argument of a method and accept either
11
+ `Float` or natural integer, but not both.
12
+
13
+ Background:
14
+ Given a file named "xor_usage.rb" with:
15
+ """ruby
16
+ require "contracts"
17
+ C = Contracts
18
+
19
+ class Example
20
+ include Contracts::Core
21
+
22
+ Contract C::Xor[Float, C::Neg] => String
23
+ def strange_number(number)
24
+ number.to_i.to_s
25
+ end
26
+ end
27
+ """
28
+
29
+ Scenario: Accepts float
30
+ Given a file named "accepts_float.rb" with:
31
+ """ruby
32
+ require "./xor_usage"
33
+ puts Example.new.strange_number(3.7)
34
+ """
35
+ When I run `ruby accepts_float.rb`
36
+ Then output should contain:
37
+ """
38
+ 3
39
+ """
40
+
41
+ Scenario: Accepts negative integer
42
+ Given a file named "accepts_negative_integer.rb" with:
43
+ """ruby
44
+ require "./xor_usage"
45
+ puts Example.new.strange_number(-7)
46
+ """
47
+ When I run `ruby accepts_negative_integer.rb`
48
+ Then output should contain:
49
+ """
50
+ -7
51
+ """
52
+
53
+ Scenario: Rejects negative float
54
+ Given a file named "rejects_negative_float.rb" with:
55
+ """ruby
56
+ require "./xor_usage"
57
+ puts Example.new.strange_number(-3.5)
58
+ """
59
+ When I run `ruby rejects_negative_float.rb`
60
+ Then output should contain:
61
+ """
62
+ : Contract violation for argument 1 of 1: (ParamContractError)
63
+ Expected: (Float xor Neg),
64
+ Actual: -3.5
65
+ Value guarded in: Example::strange_number
66
+ With Contract: Xor => String
67
+ """
68
+
69
+ Scenario: Rejects positive integer
70
+ Given a file named "rejects_positive_integer.rb" with:
71
+ """ruby
72
+ require "./xor_usage"
73
+ puts Example.new.strange_number(9)
74
+ """
75
+ When I run `ruby rejects_positive_integer.rb`
76
+ Then output should contain:
77
+ """
78
+ : Contract violation for argument 1 of 1: (ParamContractError)
79
+ Expected: (Float xor Neg),
80
+ Actual: 9
81
+ Value guarded in: Example::strange_number
82
+ With Contract: Xor => String
83
+ """
84
+
85
+ Scenario: Rejects other values
86
+ Given a file named "rejects_other.rb" with:
87
+ """ruby
88
+ require "./xor_usage"
89
+ puts Example.new.strange_number(:foo)
90
+ """
91
+ When I run `ruby rejects_other.rb`
92
+ Then output should contain:
93
+ """
94
+ : Contract violation for argument 1 of 1: (ParamContractError)
95
+ Expected: (Float xor Neg),
96
+ Actual: :foo
97
+ Value guarded in: Example::strange_number
98
+ With Contract: Xor => String
99
+ """
@@ -0,0 +1,6 @@
1
+ require "aruba/cucumber"
2
+ require "aruba/jruby" if RUBY_PLATFORM == "java"
3
+
4
+ Before do
5
+ @aruba_timeout_seconds = RUBY_PLATFORM == "java" ? 30 : 5
6
+ end
@@ -108,7 +108,7 @@ class Contract < Contracts::Decorator
108
108
  def to_s
109
109
  args = args_contracts.map { |c| pretty_contract(c) }.join(", ")
110
110
  ret = pretty_contract(ret_contract)
111
- ("#{args} => #{ret}").gsub("Contracts::", "")
111
+ ("#{args} => #{ret}").gsub("Contracts::Builtin::", "")
112
112
  end
113
113
 
114
114
  # Given a hash, prints out a failure message.
@@ -19,460 +19,465 @@ require "set"
19
19
  # The contract is <tt>Contract Num, Num, Num</tt>.
20
20
  # That says that the +add+ function takes two numbers and returns a number.
21
21
  module Contracts
22
- # Check that an argument is +Numeric+.
23
- class Num
24
- def self.valid? val
25
- val.is_a? Numeric
26
- end
27
- end
28
-
29
- # Check that an argument is a positive number.
30
- class Pos
31
- def self.valid? val
32
- val && val.is_a?(Numeric) && val > 0
33
- end
34
- end
35
-
36
- # Check that an argument is a negative number.
37
- class Neg
38
- def self.valid? val
39
- val && val.is_a?(Numeric) && val < 0
22
+ module Builtin
23
+ # Check that an argument is +Numeric+.
24
+ class Num
25
+ def self.valid? val
26
+ val.is_a? Numeric
27
+ end
40
28
  end
41
- end
42
29
 
43
- # Check that an argument is a natural number.
44
- class Nat
45
- def self.valid? val
46
- val && val.is_a?(Integer) && val >= 0
30
+ # Check that an argument is a positive number.
31
+ class Pos
32
+ def self.valid? val
33
+ val && val.is_a?(Numeric) && val > 0
34
+ end
47
35
  end
48
- end
49
36
 
50
- # Passes for any argument.
51
- class Any
52
- def self.valid? val
53
- true
37
+ # Check that an argument is a negative number.
38
+ class Neg
39
+ def self.valid? val
40
+ val && val.is_a?(Numeric) && val < 0
41
+ end
54
42
  end
55
- end
56
43
 
57
- # Fails for any argument.
58
- class None
59
- def self.valid? val
60
- false
44
+ # Check that an argument is a natural number.
45
+ class Nat
46
+ def self.valid? val
47
+ val && val.is_a?(Integer) && val >= 0
48
+ end
61
49
  end
62
- end
63
50
 
64
- # Use this when you are writing your own contract classes.
65
- # Allows your contract to be called with <tt>[]</tt> instead of <tt>.new</tt>:
66
- #
67
- # Old: <tt>Or.new(param1, param2)</tt>
68
- #
69
- # New: <tt>Or[param1, param2]</tt>
70
- #
71
- # Of course, <tt>.new</tt> still works.
72
- class CallableClass
73
- include ::Contracts::Formatters
74
- def self.[](*vals)
75
- new(*vals)
76
- end
77
- end
78
-
79
- # Takes a variable number of contracts.
80
- # The contract passes if any of the contracts pass.
81
- # Example: <tt>Or[Fixnum, Float]</tt>
82
- class Or < CallableClass
83
- def initialize(*vals)
84
- @vals = vals
51
+ # Passes for any argument.
52
+ class Any
53
+ def self.valid? val
54
+ true
55
+ end
85
56
  end
86
57
 
87
- def valid?(val)
88
- @vals.any? do |contract|
89
- res, _ = Contract.valid?(val, contract)
90
- res
58
+ # Fails for any argument.
59
+ class None
60
+ def self.valid? val
61
+ false
91
62
  end
92
63
  end
93
64
 
94
- def to_s
95
- @vals[0, @vals.size-1].map do |x|
96
- InspectWrapper.create(x)
97
- end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
65
+ # Use this when you are writing your own contract classes.
66
+ # Allows your contract to be called with <tt>[]</tt> instead of <tt>.new</tt>:
67
+ #
68
+ # Old: <tt>Or.new(param1, param2)</tt>
69
+ #
70
+ # New: <tt>Or[param1, param2]</tt>
71
+ #
72
+ # Of course, <tt>.new</tt> still works.
73
+ class CallableClass
74
+ include ::Contracts::Formatters
75
+ def self.[](*vals)
76
+ new(*vals)
77
+ end
98
78
  end
99
- end
100
79
 
101
- # Takes a variable number of contracts.
102
- # The contract passes if exactly one of those contracts pass.
103
- # Example: <tt>Xor[Fixnum, Float]</tt>
104
- class Xor < CallableClass
105
- def initialize(*vals)
106
- @vals = vals
107
- end
80
+ # Takes a variable number of contracts.
81
+ # The contract passes if any of the contracts pass.
82
+ # Example: <tt>Or[Fixnum, Float]</tt>
83
+ class Or < CallableClass
84
+ def initialize(*vals)
85
+ @vals = vals
86
+ end
108
87
 
109
- def valid?(val)
110
- results = @vals.map do |contract|
111
- res, _ = Contract.valid?(val, contract)
112
- res
88
+ def valid?(val)
89
+ @vals.any? do |contract|
90
+ res, _ = Contract.valid?(val, contract)
91
+ res
92
+ end
113
93
  end
114
- results.count(true) == 1
115
- end
116
94
 
117
- def to_s
118
- @vals[0, @vals.size-1].map do |x|
119
- InspectWrapper.create(x)
120
- end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
95
+ def to_s
96
+ @vals[0, @vals.size-1].map do |x|
97
+ InspectWrapper.create(x)
98
+ end.join(", ") + " or " + InspectWrapper.create(@vals[-1]).to_s
99
+ end
121
100
  end
122
- end
123
101
 
124
- # Takes a variable number of contracts.
125
- # The contract passes if all contracts pass.
126
- # Example: <tt>And[Fixnum, Float]</tt>
127
- class And < CallableClass
128
- def initialize(*vals)
129
- @vals = vals
130
- end
102
+ # Takes a variable number of contracts.
103
+ # The contract passes if exactly one of those contracts pass.
104
+ # Example: <tt>Xor[Fixnum, Float]</tt>
105
+ class Xor < CallableClass
106
+ def initialize(*vals)
107
+ @vals = vals
108
+ end
131
109
 
132
- def valid?(val)
133
- @vals.all? do |contract|
134
- res, _ = Contract.valid?(val, contract)
135
- res
110
+ def valid?(val)
111
+ results = @vals.map do |contract|
112
+ res, _ = Contract.valid?(val, contract)
113
+ res
114
+ end
115
+ results.count(true) == 1
136
116
  end
137
- end
138
117
 
139
- def to_s
140
- @vals[0, @vals.size-1].map do |x|
141
- InspectWrapper.create(x)
142
- end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
118
+ def to_s
119
+ @vals[0, @vals.size-1].map do |x|
120
+ InspectWrapper.create(x)
121
+ end.join(", ") + " xor " + InspectWrapper.create(@vals[-1]).to_s
122
+ end
143
123
  end
144
- end
145
124
 
146
- # Takes a variable number of method names as symbols.
147
- # The contract passes if the argument responds to all
148
- # of those methods.
149
- # Example: <tt>RespondTo[:password, :credit_card]</tt>
150
- class RespondTo < CallableClass
151
- def initialize(*meths)
152
- @meths = meths
153
- end
125
+ # Takes a variable number of contracts.
126
+ # The contract passes if all contracts pass.
127
+ # Example: <tt>And[Fixnum, Float]</tt>
128
+ class And < CallableClass
129
+ def initialize(*vals)
130
+ @vals = vals
131
+ end
154
132
 
155
- def valid?(val)
156
- @meths.all? do |meth|
157
- val.respond_to? meth
133
+ def valid?(val)
134
+ @vals.all? do |contract|
135
+ res, _ = Contract.valid?(val, contract)
136
+ res
137
+ end
158
138
  end
159
- end
160
139
 
161
- def to_s
162
- "a value that responds to #{@meths.inspect}"
140
+ def to_s
141
+ @vals[0, @vals.size-1].map do |x|
142
+ InspectWrapper.create(x)
143
+ end.join(", ") + " and " + InspectWrapper.create(@vals[-1]).to_s
144
+ end
163
145
  end
164
- end
165
146
 
166
- # Takes a variable number of method names as symbols.
167
- # Given an argument, all of those methods are called
168
- # on the argument one by one. If they all return true,
169
- # the contract passes.
170
- # Example: <tt>Send[:valid?]</tt>
171
- class Send < CallableClass
172
- def initialize(*meths)
173
- @meths = meths
174
- end
147
+ # Takes a variable number of method names as symbols.
148
+ # The contract passes if the argument responds to all
149
+ # of those methods.
150
+ # Example: <tt>RespondTo[:password, :credit_card]</tt>
151
+ class RespondTo < CallableClass
152
+ def initialize(*meths)
153
+ @meths = meths
154
+ end
175
155
 
176
- def valid?(val)
177
- @meths.all? do |meth|
178
- val.send(meth)
156
+ def valid?(val)
157
+ @meths.all? do |meth|
158
+ val.respond_to? meth
159
+ end
179
160
  end
180
- end
181
161
 
182
- def to_s
183
- "a value that returns true for all of #{@meths.inspect}"
162
+ def to_s
163
+ "a value that responds to #{@meths.inspect}"
164
+ end
184
165
  end
185
- end
186
166
 
187
- # Takes a class +A+. If argument is object of type +A+, the contract passes.
188
- # If it is a subclass of A (or not related to A in any way), it fails.
189
- # Example: <tt>Exactly[Numeric]</tt>
190
- class Exactly < CallableClass
191
- def initialize(cls)
192
- @cls = cls
193
- end
167
+ # Takes a variable number of method names as symbols.
168
+ # Given an argument, all of those methods are called
169
+ # on the argument one by one. If they all return true,
170
+ # the contract passes.
171
+ # Example: <tt>Send[:valid?]</tt>
172
+ class Send < CallableClass
173
+ def initialize(*meths)
174
+ @meths = meths
175
+ end
194
176
 
195
- def valid?(val)
196
- val.class == @cls
197
- end
177
+ def valid?(val)
178
+ @meths.all? do |meth|
179
+ val.send(meth)
180
+ end
181
+ end
198
182
 
199
- def to_s
200
- "exactly #{@cls.inspect}"
183
+ def to_s
184
+ "a value that returns true for all of #{@meths.inspect}"
185
+ end
201
186
  end
202
- end
203
187
 
204
- # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
205
- # the list, the contract passes.
206
- #
207
- # Example: <tt>Enum[:a, :b, :c]</tt>?
208
- class Enum < CallableClass
209
- def initialize(*vals)
210
- @vals = vals
211
- end
188
+ # Takes a class +A+. If argument is object of type +A+, the contract passes.
189
+ # If it is a subclass of A (or not related to A in any way), it fails.
190
+ # Example: <tt>Exactly[Numeric]</tt>
191
+ class Exactly < CallableClass
192
+ def initialize(cls)
193
+ @cls = cls
194
+ end
212
195
 
213
- def valid?(val)
214
- @vals.include? val
215
- end
216
- end
196
+ def valid?(val)
197
+ val.class == @cls
198
+ end
217
199
 
218
- # Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
219
- # otherwise the contract fails.
220
- # Example: <tt>Eq[Class]</tt>
221
- class Eq < CallableClass
222
- def initialize(value)
223
- @value = value
200
+ def to_s
201
+ "exactly #{@cls.inspect}"
202
+ end
224
203
  end
225
204
 
226
- def valid?(val)
227
- @value.equal?(val)
228
- end
205
+ # Takes a list of values, e.g. +[:a, :b, :c]+. If argument is included in
206
+ # the list, the contract passes.
207
+ #
208
+ # Example: <tt>Enum[:a, :b, :c]</tt>?
209
+ class Enum < CallableClass
210
+ def initialize(*vals)
211
+ @vals = vals
212
+ end
229
213
 
230
- def to_s
231
- "to be equal to #{@value.inspect}"
214
+ def valid?(val)
215
+ @vals.include? val
216
+ end
232
217
  end
233
- end
234
218
 
235
- # Takes a variable number of contracts. The contract
236
- # passes if all of those contracts fail for the given argument.
237
- # Example: <tt>Not[nil]</tt>
238
- class Not < CallableClass
239
- def initialize(*vals)
240
- @vals = vals
241
- end
219
+ # Takes a value +v+. If the argument is +.equal+ to +v+, the contract passes,
220
+ # otherwise the contract fails.
221
+ # Example: <tt>Eq[Class]</tt>
222
+ class Eq < CallableClass
223
+ def initialize(value)
224
+ @value = value
225
+ end
242
226
 
243
- def valid?(val)
244
- @vals.all? do |contract|
245
- res, _ = Contract.valid?(val, contract)
246
- !res
227
+ def valid?(val)
228
+ @value.equal?(val)
247
229
  end
248
- end
249
230
 
250
- def to_s
251
- "a value that is none of #{@vals.inspect}"
231
+ def to_s
232
+ "to be equal to #{@value.inspect}"
233
+ end
252
234
  end
253
- end
254
235
 
255
- # @private
256
- # Takes a collection(responds to :each) type and a contract.
257
- # The related argument must be of specified collection type.
258
- # Checks the contract against every element of the collection.
259
- # If it passes for all elements, the contract passes.
260
- # Example: <tt>CollectionOf[Array, Num]</tt>
261
- class CollectionOf < CallableClass
262
- def initialize(collection_class, contract)
263
- @collection_class = collection_class
264
- @contract = contract
265
- end
236
+ # Takes a variable number of contracts. The contract
237
+ # passes if all of those contracts fail for the given argument.
238
+ # Example: <tt>Not[nil]</tt>
239
+ class Not < CallableClass
240
+ def initialize(*vals)
241
+ @vals = vals
242
+ end
266
243
 
267
- def valid?(vals)
268
- return false unless vals.is_a?(@collection_class)
269
- vals.all? do |val|
270
- res, _ = Contract.valid?(val, @contract)
271
- res
244
+ def valid?(val)
245
+ @vals.all? do |contract|
246
+ res, _ = Contract.valid?(val, contract)
247
+ !res
248
+ end
272
249
  end
273
- end
274
250
 
275
- def to_s
276
- "a collection #{@collection_class} of #{@contract}"
251
+ def to_s
252
+ "a value that is none of #{@vals.inspect}"
253
+ end
277
254
  end
278
255
 
279
- class Factory
280
- def initialize(collection_class, &before_new)
256
+ # @private
257
+ # Takes a collection(responds to :each) type and a contract.
258
+ # The related argument must be of specified collection type.
259
+ # Checks the contract against every element of the collection.
260
+ # If it passes for all elements, the contract passes.
261
+ # Example: <tt>CollectionOf[Array, Num]</tt>
262
+ class CollectionOf < CallableClass
263
+ def initialize(collection_class, contract)
281
264
  @collection_class = collection_class
282
- @before_new = before_new
265
+ @contract = contract
283
266
  end
284
267
 
285
- def new(contract)
286
- @before_new && @before_new.call
287
- CollectionOf.new(@collection_class, contract)
268
+ def valid?(vals)
269
+ return false unless vals.is_a?(@collection_class)
270
+ vals.all? do |val|
271
+ res, _ = Contract.valid?(val, @contract)
272
+ res
273
+ end
288
274
  end
289
275
 
290
- alias_method :[], :new
291
- end
292
- end
276
+ def to_s
277
+ "a collection #{@collection_class} of #{@contract}"
278
+ end
293
279
 
294
- # Takes a contract. The related argument must be an array.
295
- # Checks the contract against every element of the array.
296
- # If it passes for all elements, the contract passes.
297
- # Example: <tt>ArrayOf[Num]</tt>
298
- ArrayOf = CollectionOf::Factory.new(Array)
299
-
300
- # Takes a contract. The related argument must be a set.
301
- # Checks the contract against every element of the set.
302
- # If it passes for all elements, the contract passes.
303
- # Example: <tt>SetOf[Num]</tt>
304
- SetOf = CollectionOf::Factory.new(Set)
305
-
306
- # Used for <tt>*args</tt> (variadic functions). Takes a contract
307
- # and uses it to validate every element passed in
308
- # through <tt>*args</tt>.
309
- # Example: <tt>Args[Or[String, Num]]</tt>
310
- class Args < CallableClass
311
- attr_reader :contract
312
- def initialize(contract)
313
- @contract = contract
314
- end
280
+ class Factory
281
+ def initialize(collection_class, &before_new)
282
+ @collection_class = collection_class
283
+ @before_new = before_new
284
+ end
315
285
 
316
- def to_s
317
- "Args[#{@contract}]"
318
- end
319
- end
286
+ def new(contract)
287
+ @before_new && @before_new.call
288
+ CollectionOf.new(@collection_class, contract)
289
+ end
320
290
 
321
- class Bool
322
- def self.valid? val
323
- val.is_a?(TrueClass) || val.is_a?(FalseClass)
291
+ alias_method :[], :new
292
+ end
324
293
  end
325
- end
326
294
 
327
- # Use this to specify a Range object of a particular datatype.
328
- # Example: <tt>RangeOf[Nat]</tt>, <tt>RangeOf[Date]</tt>, ...
329
- class RangeOf < CallableClass
330
- def initialize(contract)
331
- @contract = contract
332
- end
295
+ # Takes a contract. The related argument must be an array.
296
+ # Checks the contract against every element of the array.
297
+ # If it passes for all elements, the contract passes.
298
+ # Example: <tt>ArrayOf[Num]</tt>
299
+ ArrayOf = CollectionOf::Factory.new(Array)
300
+
301
+ # Takes a contract. The related argument must be a set.
302
+ # Checks the contract against every element of the set.
303
+ # If it passes for all elements, the contract passes.
304
+ # Example: <tt>SetOf[Num]</tt>
305
+ SetOf = CollectionOf::Factory.new(Set)
306
+
307
+ # Used for <tt>*args</tt> (variadic functions). Takes a contract
308
+ # and uses it to validate every element passed in
309
+ # through <tt>*args</tt>.
310
+ # Example: <tt>Args[Or[String, Num]]</tt>
311
+ class Args < CallableClass
312
+ attr_reader :contract
313
+ def initialize(contract)
314
+ @contract = contract
315
+ end
333
316
 
334
- def valid?(val)
335
- val.is_a?(Range) &&
336
- Contract.valid?(val.first, @contract) &&
337
- Contract.valid?(val.last, @contract)
317
+ def to_s
318
+ "Args[#{@contract}]"
319
+ end
338
320
  end
339
321
 
340
- def to_s
341
- "a range of #{@contract}"
322
+ class Bool
323
+ def self.valid? val
324
+ val.is_a?(TrueClass) || val.is_a?(FalseClass)
325
+ end
342
326
  end
343
- end
344
327
 
345
- # Use this to specify the Hash characteristics. Takes two contracts,
346
- # one for hash keys and one for hash values.
347
- # Example: <tt>HashOf[Symbol, String]</tt>
348
- class HashOf < CallableClass
349
- INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
350
-
351
- def initialize(key, value = nil)
352
- if value
353
- @key = key
354
- @value = value
355
- else
356
- validate_hash(key)
357
- @key = key.keys.first
358
- @value = key[@key]
328
+ # Use this to specify a Range object of a particular datatype.
329
+ # Example: <tt>RangeOf[Nat]</tt>, <tt>RangeOf[Date]</tt>, ...
330
+ class RangeOf < CallableClass
331
+ def initialize(contract)
332
+ @contract = contract
359
333
  end
360
- end
361
334
 
362
- def valid?(hash)
363
- return false unless hash.is_a?(Hash)
364
- keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
365
- vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
335
+ def valid?(val)
336
+ val.is_a?(Range) &&
337
+ Contract.valid?(val.first, @contract) &&
338
+ Contract.valid?(val.last, @contract)
339
+ end
366
340
 
367
- [keys_match, vals_match].all?
341
+ def to_s
342
+ "a range of #{@contract}"
343
+ end
368
344
  end
369
345
 
370
- def to_s
371
- "Hash<#{@key}, #{@value}>"
372
- end
346
+ # Use this to specify the Hash characteristics. Takes two contracts,
347
+ # one for hash keys and one for hash values.
348
+ # Example: <tt>HashOf[Symbol, String]</tt>
349
+ class HashOf < CallableClass
350
+ INVALID_KEY_VALUE_PAIR = "You should provide only one key-value pair to HashOf contract"
351
+
352
+ def initialize(key, value = nil)
353
+ if value
354
+ @key = key
355
+ @value = value
356
+ else
357
+ validate_hash(key)
358
+ @key = key.keys.first
359
+ @value = key[@key]
360
+ end
361
+ end
373
362
 
374
- private
363
+ def valid?(hash)
364
+ return false unless hash.is_a?(Hash)
365
+ keys_match = hash.keys.map { |k| Contract.valid?(k, @key) }.all?
366
+ vals_match = hash.values.map { |v| Contract.valid?(v, @value) }.all?
375
367
 
376
- def validate_hash(hash)
377
- fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
378
- end
379
- end
368
+ [keys_match, vals_match].all?
369
+ end
380
370
 
381
- # Use this for specifying contracts for keyword arguments
382
- # Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
383
- class KeywordArgs < CallableClass
384
- def initialize(options)
385
- @options = options
386
- end
371
+ def to_s
372
+ "Hash<#{@key}, #{@value}>"
373
+ end
374
+
375
+ private
387
376
 
388
- def valid?(hash)
389
- return false unless hash.keys - options.keys == []
390
- options.all? do |key, contract|
391
- Optional._valid?(hash, key, contract)
377
+ def validate_hash(hash)
378
+ fail ArgumentError, INVALID_KEY_VALUE_PAIR unless hash.count == 1
392
379
  end
393
380
  end
394
381
 
395
- def to_s
396
- "KeywordArgs[#{options}]"
397
- end
382
+ # Use this for specifying contracts for keyword arguments
383
+ # Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
384
+ class KeywordArgs < CallableClass
385
+ def initialize(options)
386
+ @options = options
387
+ end
398
388
 
399
- def inspect
400
- to_s
401
- end
389
+ def valid?(hash)
390
+ return false unless hash.keys - options.keys == []
391
+ options.all? do |key, contract|
392
+ Optional._valid?(hash, key, contract)
393
+ end
394
+ end
402
395
 
403
- private
396
+ def to_s
397
+ "KeywordArgs[#{options}]"
398
+ end
404
399
 
405
- attr_reader :options
406
- end
400
+ def inspect
401
+ to_s
402
+ end
407
403
 
408
- # Use this for specifying optional keyword argument
409
- # Example: <tt>Optional[Num]</tt>
410
- class Optional < CallableClass
411
- UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
412
- "Unable to use Optional contract outside of KeywordArgs contract"
404
+ private
413
405
 
414
- def self._valid?(hash, key, contract)
415
- return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
416
- contract.within_opt_hash!
417
- !hash.key?(key) || Contract.valid?(hash[key], contract)
406
+ attr_reader :options
418
407
  end
419
408
 
420
- def initialize(contract)
421
- @contract = contract
422
- @within_opt_hash = false
423
- end
409
+ # Use this for specifying optional keyword argument
410
+ # Example: <tt>Optional[Num]</tt>
411
+ class Optional < CallableClass
412
+ UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH =
413
+ "Unable to use Optional contract outside of KeywordArgs contract"
424
414
 
425
- def within_opt_hash!
426
- @within_opt_hash = true
427
- self
428
- end
415
+ def self._valid?(hash, key, contract)
416
+ return Contract.valid?(hash[key], contract) unless contract.is_a?(Optional)
417
+ contract.within_opt_hash!
418
+ !hash.key?(key) || Contract.valid?(hash[key], contract)
419
+ end
429
420
 
430
- def valid?(value)
431
- ensure_within_opt_hash
432
- Contract.valid?(value, contract)
433
- end
421
+ def initialize(contract)
422
+ @contract = contract
423
+ @within_opt_hash = false
424
+ end
434
425
 
435
- def to_s
436
- "Optional[#{formatted_contract}]"
437
- end
426
+ def within_opt_hash!
427
+ @within_opt_hash = true
428
+ self
429
+ end
438
430
 
439
- def inspect
440
- to_s
441
- end
431
+ def valid?(value)
432
+ ensure_within_opt_hash
433
+ Contract.valid?(value, contract)
434
+ end
442
435
 
443
- private
436
+ def to_s
437
+ "Optional[#{formatted_contract}]"
438
+ end
444
439
 
445
- attr_reader :contract, :within_opt_hash
440
+ def inspect
441
+ to_s
442
+ end
446
443
 
447
- def ensure_within_opt_hash
448
- return if within_opt_hash
449
- fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
450
- end
444
+ private
451
445
 
452
- def formatted_contract
453
- Formatters::InspectWrapper.create(contract)
454
- end
455
- end
446
+ attr_reader :contract, :within_opt_hash
447
+
448
+ def ensure_within_opt_hash
449
+ return if within_opt_hash
450
+ fail ArgumentError, UNABLE_TO_USE_OUTSIDE_OF_OPT_HASH
451
+ end
456
452
 
457
- # Takes a Contract.
458
- # The contract passes if the contract passes or the given value is nil.
459
- # Maybe(foo) is equivalent to Or[foo, nil].
460
- class Maybe < Or
461
- def initialize(*vals)
462
- super(*(vals + [nil]))
453
+ def formatted_contract
454
+ Formatters::InspectWrapper.create(contract)
455
+ end
463
456
  end
464
457
 
465
- def include_proc?
466
- @vals.include? Proc
458
+ # Takes a Contract.
459
+ # The contract passes if the contract passes or the given value is nil.
460
+ # Maybe(foo) is equivalent to Or[foo, nil].
461
+ class Maybe < Or
462
+ def initialize(*vals)
463
+ super(*(vals + [nil]))
464
+ end
465
+
466
+ def include_proc?
467
+ @vals.include? Proc
468
+ end
467
469
  end
468
- end
469
470
 
470
- # Used to define contracts on functions passed in as arguments.
471
- # Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
472
- class Func < CallableClass
473
- attr_reader :contracts
474
- def initialize(*contracts)
475
- @contracts = contracts
471
+ # Used to define contracts on functions passed in as arguments.
472
+ # Example: <tt>Func[Num => Num] # the function should take a number and return a number</tt>
473
+ class Func < CallableClass
474
+ attr_reader :contracts
475
+ def initialize(*contracts)
476
+ @contracts = contracts
477
+ end
476
478
  end
477
479
  end
480
+
481
+ # Users can still include `Contracts::Core` & `Contracts::Builtin`
482
+ include Builtin
478
483
  end