contracts 0.13.0 → 0.14.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.
- 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: []
|