contracts 0.11.0 → 0.12.0

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