contracts 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +8 -0
- data/Gemfile +1 -1
- data/LICENSE +23 -0
- data/README.md +2 -0
- data/TUTORIAL.md +2 -1
- data/contracts.gemspec +1 -0
- data/lib/contracts.rb +4 -4
- data/lib/contracts/builtin_contracts.rb +44 -0
- data/lib/contracts/core.rb +0 -2
- data/lib/contracts/version.rb +1 -1
- data/spec/builtin_contracts_spec.rb +62 -0
- data/spec/contracts_spec.rb +17 -0
- data/spec/fixtures/fixtures.rb +28 -0
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +15 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb93546d34391727b5f9e866f193cddea67ac952
|
4
|
+
data.tar.gz: c214e45aba01fe36d90b3b74f670ccc3254c4b6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 855e9377f9637943e20ebbf82b522bb915208523bfa3bc6bd23b939b613fe860354e9fabdc4d8d3ef3d61d0cbde7f2aa716c6274055d4d948ec998f92f8185e4
|
7
|
+
data.tar.gz: 20341d6090d35f0abc1bee62d9eccb618530d78a2e68a60bd67c22d85776a0a2dd7afaf9899088c77e03e84aa2b576804d52990ab614546a6a04eb0bf20b67bb
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## v0.14.0
|
2
|
+
- Enhancement: Add StrictHash contract - [Fyodor](https://github.com/cbrwizard) [#236](https://github.com/egonSchiele/contracts.ruby/pull/236)
|
3
|
+
- Bugfix: dont fail if something other than a hash is passed to a KeywordArgs - [Dan Padilha](https://github.com/dpad) [#234](https://github.com/egonSchiele/contracts.ruby/pull/234)
|
4
|
+
- LICENSE ADDED: Simplified BSD (same as what is specified in the readme) - [Charles Dale](https://github.com/chuckd) [#233](https://github.com/egonSchiele/contracts.ruby/pull/233)
|
5
|
+
- Bugfix: fix constant looking when including a module that includes contracts (requires removing the check to see if contracts is already included) - [Aditya Bhargava](https://github.com/egonSchiele) [#232](https://github.com/egonSchiele/contracts.ruby/pull/232)
|
6
|
+
- Bugfix for err case when KeywordArgs and Proc are used together - [Aditya Bhargava](https://github.com/egonSchiele) [#230](https://github.com/egonSchiele/contracts.ruby/pull/230)
|
7
|
+
- Enhancement: Add DescendantOf contract - [Miguel Palhas](https://github.com/naps62) [#227](https://github.com/egonSchiele/contracts.ruby/pull/227)
|
8
|
+
|
1
9
|
## v0.13.0
|
2
10
|
|
3
11
|
- Enhancement: Add support for Ruby 2.3 - [Oleksii Fedorov](https://github.com/waterlink) [#216](https://github.com/egonSchiele/contracts.ruby/pull/216)
|
data/Gemfile
CHANGED
@@ -6,7 +6,7 @@ group :test do
|
|
6
6
|
gem "rspec"
|
7
7
|
gem "aruba"
|
8
8
|
gem "cucumber", "~> 1.3.20"
|
9
|
-
gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22]
|
9
|
+
gem "rubocop", "~> 0.29.1", :platform => [:ruby_20, :ruby_21, :ruby_22, :ruby_23]
|
10
10
|
end
|
11
11
|
|
12
12
|
group :development do
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012-2016 Aditya Bhargava
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
15
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
16
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
18
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
20
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
21
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
22
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
23
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
CHANGED
@@ -39,6 +39,7 @@ puts Example.new.double("oops")
|
|
39
39
|
|
40
40
|
Save this in a file and run it. Notice we are calling `double` with `"oops"`, which is not a number. The contract fails with a detailed error message:
|
41
41
|
|
42
|
+
```
|
42
43
|
ParamContractError: Contract violation for argument 1 of 1:
|
43
44
|
Expected: Num,
|
44
45
|
Actual: "oops"
|
@@ -46,6 +47,7 @@ ParamContractError: Contract violation for argument 1 of 1:
|
|
46
47
|
With Contract: Num => Num
|
47
48
|
At: main.rb:8
|
48
49
|
...stack trace...
|
50
|
+
```
|
49
51
|
|
50
52
|
Instead of throwing an exception, you could log it, print a clean error message for your user...whatever you want. contracts.ruby is here to help you handle bugs better, not to get in your way.
|
51
53
|
|
data/TUTORIAL.md
CHANGED
@@ -89,6 +89,7 @@ contracts.ruby comes with a lot of built-in contracts, including the following:
|
|
89
89
|
* [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
|
90
90
|
* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
|
91
91
|
* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]`
|
92
|
+
* [`StrictHash`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/StrictHash) – checks that the argument is a hash, and every key passed is present in the given contract, e.g. `StrictHash[{ :description => String, :number => Fixnum }]`
|
92
93
|
* [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
|
93
94
|
* [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]`
|
94
95
|
|
@@ -270,7 +271,7 @@ You don't need to put a contract on every key. So this call would succeed:
|
|
270
271
|
person({:name => "Adit", :age => 42, :foo => "bar"})
|
271
272
|
```
|
272
273
|
|
273
|
-
even though we don't specify a type for `:foo`.
|
274
|
+
even though we don't specify a type for `:foo`. If you need this check though, use `StrictHash` instead.
|
274
275
|
|
275
276
|
Peruse this contract on the keys and values of a Hash.
|
276
277
|
|
data/contracts.gemspec
CHANGED
data/lib/contracts.rb
CHANGED
@@ -92,9 +92,9 @@ class Contract < Contracts::Decorator
|
|
92
92
|
last_contract = args_contracts.last
|
93
93
|
penultimate_contract = args_contracts[-2]
|
94
94
|
@has_options_contract = if @has_proc_contract
|
95
|
-
penultimate_contract.is_a?(Hash)
|
95
|
+
penultimate_contract.is_a?(Hash) || penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
96
96
|
else
|
97
|
-
last_contract.is_a?(Hash)
|
97
|
+
last_contract.is_a?(Hash) || last_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
98
98
|
end
|
99
99
|
# ===
|
100
100
|
|
@@ -215,9 +215,9 @@ class Contract < Contracts::Decorator
|
|
215
215
|
# returns true if it appended nil
|
216
216
|
def maybe_append_options! args, blk
|
217
217
|
return false unless @has_options_contract
|
218
|
-
if @has_proc_contract && args_contracts[-2].is_a?(Hash) && !args[-2].is_a?(Hash)
|
218
|
+
if @has_proc_contract && (args_contracts[-2].is_a?(Hash) || args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-2].is_a?(Hash)
|
219
219
|
args.insert(-2, {})
|
220
|
-
elsif args_contracts[-1].is_a?(Hash) && !args[-1].is_a?(Hash)
|
220
|
+
elsif (args_contracts[-1].is_a?(Hash) || args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-1].is_a?(Hash)
|
221
221
|
args << {}
|
222
222
|
end
|
223
223
|
true
|
@@ -393,6 +393,25 @@ module Contracts
|
|
393
393
|
end
|
394
394
|
end
|
395
395
|
|
396
|
+
# Use this to specify the Hash characteristics. This contracts fails
|
397
|
+
# if there are any extra keys that don't have contracts on them.
|
398
|
+
# Example: <tt>StrictHash[{ a: String, b: Bool }]</tt>
|
399
|
+
class StrictHash < CallableClass
|
400
|
+
attr_reader :contract_hash
|
401
|
+
|
402
|
+
def initialize(contract_hash)
|
403
|
+
@contract_hash = contract_hash
|
404
|
+
end
|
405
|
+
|
406
|
+
def valid?(arg)
|
407
|
+
return false unless arg.is_a?(Hash)
|
408
|
+
|
409
|
+
contract_hash.all? do |key, _v|
|
410
|
+
contract_hash.key?(key) && Contract.valid?(arg[key], contract_hash[key])
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
396
415
|
# Use this for specifying contracts for keyword arguments
|
397
416
|
# Example: <tt>KeywordArgs[ e: Range, f: Optional[Num] ]</tt>
|
398
417
|
class KeywordArgs < CallableClass
|
@@ -401,6 +420,7 @@ module Contracts
|
|
401
420
|
end
|
402
421
|
|
403
422
|
def valid?(hash)
|
423
|
+
return false unless hash.is_a?(Hash)
|
404
424
|
return false unless hash.keys - options.keys == []
|
405
425
|
options.all? do |key, contract|
|
406
426
|
Optional._valid?(hash, key, contract)
|
@@ -420,6 +440,30 @@ module Contracts
|
|
420
440
|
attr_reader :options
|
421
441
|
end
|
422
442
|
|
443
|
+
# Use this for specifying contracts for class arguments
|
444
|
+
# Example: <tt>Descendant[ e: Range, f: Optional[Num] ]</tt>
|
445
|
+
class DescendantOf < CallableClass
|
446
|
+
def initialize(parent_class)
|
447
|
+
@parent_class = parent_class
|
448
|
+
end
|
449
|
+
|
450
|
+
def valid?(given_class)
|
451
|
+
given_class.is_a?(Class) && given_class.ancestors.include?(parent_class)
|
452
|
+
end
|
453
|
+
|
454
|
+
def to_s
|
455
|
+
"DescendantOf[#{parent_class}]"
|
456
|
+
end
|
457
|
+
|
458
|
+
def inspect
|
459
|
+
to_s
|
460
|
+
end
|
461
|
+
|
462
|
+
private
|
463
|
+
|
464
|
+
attr_reader :parent_class
|
465
|
+
end
|
466
|
+
|
423
467
|
# Use this for specifying optional keyword argument
|
424
468
|
# Example: <tt>Optional[Num]</tt>
|
425
469
|
class Optional < CallableClass
|
data/lib/contracts/core.rb
CHANGED
data/lib/contracts/version.rb
CHANGED
@@ -3,6 +3,24 @@ RSpec.describe "Contracts:" do
|
|
3
3
|
@o = GenericExample.new
|
4
4
|
end
|
5
5
|
|
6
|
+
describe "DescendantOf:" do
|
7
|
+
it "should pass for Array" do
|
8
|
+
expect { @o.enumerable_descendant_test(Array) }.to_not raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should pass for a hash" do
|
12
|
+
expect { @o.enumerable_descendant_test(Hash) }.to_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should fail for a number class" do
|
16
|
+
expect { @o.enumerable_descendant_test(Integer) }.to raise_error(ContractError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should fail for a non-class" do
|
20
|
+
expect { @o.enumerable_descendant_test(1) }.to raise_error(ContractError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
6
24
|
describe "Num:" do
|
7
25
|
it "should pass for Fixnums" do
|
8
26
|
expect { @o.double(2) }.to_not raise_error
|
@@ -350,6 +368,10 @@ RSpec.describe "Contracts:" do
|
|
350
368
|
expect { @o.hash_keywordargs(:hash => nil) }.to raise_error(ContractError)
|
351
369
|
expect { @o.hash_keywordargs(:hash => 1) }.to raise_error(ContractError)
|
352
370
|
end
|
371
|
+
|
372
|
+
it "should pass if a method is overloaded with non-KeywordArgs" do
|
373
|
+
expect { @o.person_keywordargs("name", 10) }.to_not raise_error
|
374
|
+
end
|
353
375
|
end
|
354
376
|
|
355
377
|
describe "Optional:" do
|
@@ -396,4 +418,44 @@ RSpec.describe "Contracts:" do
|
|
396
418
|
end
|
397
419
|
end
|
398
420
|
end
|
421
|
+
|
422
|
+
describe "StrictHash:" do
|
423
|
+
context "when given an exact correct input" do
|
424
|
+
it "does not raise an error" do
|
425
|
+
expect do
|
426
|
+
@o.strict_person(:name => "calvin", :age => 10)
|
427
|
+
end.to_not raise_error
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "when given an input with correct keys but wrong types" do
|
432
|
+
it "raises an error" do
|
433
|
+
expect do
|
434
|
+
@o.strict_person(:name => "calvin", :age => "10")
|
435
|
+
end.to raise_error(ContractError)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
context "when given an input with missing keys" do
|
440
|
+
it "raises an error" do
|
441
|
+
expect do
|
442
|
+
@o.strict_person(:name => "calvin")
|
443
|
+
end.to raise_error(ContractError)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
context "when given an input with extra keys" do
|
448
|
+
it "raises an error" do
|
449
|
+
expect do
|
450
|
+
@o.strict_person(:name => "calvin", :age => "10", :soft => true)
|
451
|
+
end.to raise_error(ContractError)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context "when given not a hash" do
|
456
|
+
it "raises an error" do
|
457
|
+
expect { @o.strict_person(1337) }.to raise_error(ContractError)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
399
461
|
end
|
data/spec/contracts_spec.rb
CHANGED
@@ -727,5 +727,22 @@ RSpec.describe "Contracts:" do
|
|
727
727
|
it "works correctly with methods with passing contracts" do
|
728
728
|
expect { klass.new.foo(42) }.to raise_error(ContractError, /Expected: String/)
|
729
729
|
end
|
730
|
+
|
731
|
+
# See the discussion on this issue:
|
732
|
+
# https://github.com/egonSchiele/contracts.ruby/issues/229
|
733
|
+
it "should not fail with 'undefined method 'Contract''" do
|
734
|
+
expect do
|
735
|
+
class ModuleThenContracts
|
736
|
+
include ModuleWithContracts
|
737
|
+
include Contracts::Core
|
738
|
+
|
739
|
+
# fails on this line
|
740
|
+
Contract C::Num => C::Num
|
741
|
+
def double(x)
|
742
|
+
x * 2
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end.to_not raise_error
|
746
|
+
end
|
730
747
|
end
|
731
748
|
end
|
data/spec/fixtures/fixtures.rb
CHANGED
@@ -104,6 +104,10 @@ class GenericExample
|
|
104
104
|
def person(data)
|
105
105
|
end
|
106
106
|
|
107
|
+
Contract C::StrictHash[{ :name => String, :age => Fixnum }] => nil
|
108
|
+
def strict_person(data)
|
109
|
+
end
|
110
|
+
|
107
111
|
Contract ({ :rigged => C::Or[TrueClass, FalseClass] }) => nil
|
108
112
|
def hash_complex_contracts(data)
|
109
113
|
end
|
@@ -119,6 +123,11 @@ class GenericExample
|
|
119
123
|
def person_keywordargs(data)
|
120
124
|
end
|
121
125
|
|
126
|
+
# Testing overloaded method
|
127
|
+
Contract String, Fixnum => nil
|
128
|
+
def person_keywordargs(name, age)
|
129
|
+
end
|
130
|
+
|
122
131
|
Contract C::KeywordArgs[:hash => C::HashOf[Symbol, C::Num]] => nil
|
123
132
|
def hash_keywordargs(data)
|
124
133
|
end
|
@@ -263,6 +272,10 @@ class GenericExample
|
|
263
272
|
r.first
|
264
273
|
end
|
265
274
|
|
275
|
+
Contract C::DescendantOf[Enumerable] => nil
|
276
|
+
def enumerable_descendant_test(enum)
|
277
|
+
end
|
278
|
+
|
266
279
|
Contract C::Bool => nil
|
267
280
|
def bool_test(x)
|
268
281
|
end
|
@@ -676,3 +689,18 @@ module ModuleContractExample
|
|
676
689
|
:world
|
677
690
|
end
|
678
691
|
end
|
692
|
+
|
693
|
+
module ModuleWithContracts
|
694
|
+
def self.included(base)
|
695
|
+
base.extend ClassMethods
|
696
|
+
end
|
697
|
+
|
698
|
+
module ClassMethods
|
699
|
+
include Contracts::Core
|
700
|
+
|
701
|
+
Contract C::None => String
|
702
|
+
def foo
|
703
|
+
"bar"
|
704
|
+
end
|
705
|
+
end
|
706
|
+
end
|
@@ -7,6 +7,11 @@ class GenericExample
|
|
7
7
|
Contract ({foo: C::Nat}) => nil
|
8
8
|
def nat_test_with_kwarg(foo: 10)
|
9
9
|
end
|
10
|
+
|
11
|
+
Contract C::KeywordArgs[name: C::Optional[String]], C::Func[String => String] => String
|
12
|
+
def keyword_args_hello(name: "Adit", &block)
|
13
|
+
"Hey, #{yield name}!"
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
RSpec.describe "Contracts:" do
|
@@ -37,4 +42,14 @@ RSpec.describe "Contracts:" do
|
|
37
42
|
expect { @o.nat_test_with_kwarg }.to raise_error(ContractError)
|
38
43
|
end
|
39
44
|
end
|
45
|
+
|
46
|
+
describe "keyword args with defaults, with a block" do
|
47
|
+
it "should work when both keyword args and a block is given" do
|
48
|
+
expect(@o.keyword_args_hello(name: "maggie", &:upcase)).to eq("Hey, MAGGIE!")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should work even when keyword args aren't given" do
|
52
|
+
expect(@o.keyword_args_hello(&:upcase)).to eq("Hey, ADIT!")
|
53
|
+
end
|
54
|
+
end
|
40
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contracts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aditya Bhargava
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: This library provides contracts for Ruby. Contracts let you clearly express
|
14
14
|
how your code behaves, and free you from writing tons of boilerplate, defensive
|
@@ -24,6 +24,7 @@ files:
|
|
24
24
|
- ".travis.yml"
|
25
25
|
- CHANGELOG.markdown
|
26
26
|
- Gemfile
|
27
|
+
- LICENSE
|
27
28
|
- README.md
|
28
29
|
- Rakefile
|
29
30
|
- TODO.markdown
|
@@ -101,7 +102,8 @@ files:
|
|
101
102
|
- spec/support_spec.rb
|
102
103
|
- spec/validators_spec.rb
|
103
104
|
homepage: http://github.com/egonSchiele/contracts.ruby
|
104
|
-
licenses:
|
105
|
+
licenses:
|
106
|
+
- BSD-2-Clause
|
105
107
|
metadata: {}
|
106
108
|
post_install_message:
|
107
109
|
rdoc_options: []
|