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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e31d0dc3c6a107d0255ab5af8229749e6ce825e6
4
- data.tar.gz: 915617473470b5450ee855605380d7d6552d49a8
3
+ metadata.gz: bb93546d34391727b5f9e866f193cddea67ac952
4
+ data.tar.gz: c214e45aba01fe36d90b3b74f670ccc3254c4b6b
5
5
  SHA512:
6
- metadata.gz: 73c20ffc1467c52de7a44f91cf1711049c2db2f47aaf3b8faf517a0ba05030aa891c3748a0287ed6d5be799f96d3eb9b92eb8ea950041b0ff459e63262388a56
7
- data.tar.gz: 0343d43d8437dcb11255392d53cb57e06119eb0dbd7f20880c0e0646d40b1abf9888e6b1fd5450464eed6ec4635444f7817b87228b1560eca81f0e4997a79eee
6
+ metadata.gz: 855e9377f9637943e20ebbf82b522bb915208523bfa3bc6bd23b939b613fe860354e9fabdc4d8d3ef3d61d0cbde7f2aa716c6274055d4d948ec998f92f8185e4
7
+ data.tar.gz: 20341d6090d35f0abc1bee62d9eccb618530d78a2e68a60bd67c22d85776a0a2dd7afaf9899088c77e03e84aa2b576804d52990ab614546a6a04eb0bf20b67bb
@@ -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
 
@@ -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
 
@@ -9,4 +9,5 @@ Gem::Specification.new do |s|
9
9
  s.email = "bluemangroupie@gmail.com"
10
10
  s.files = `git ls-files`.split("\n")
11
11
  s.homepage = "http://github.com/egonSchiele/contracts.ruby"
12
+ s.license = "BSD-2-Clause"
12
13
  end
@@ -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
@@ -9,8 +9,6 @@ module Contracts
9
9
  end
10
10
 
11
11
  def self.common(base)
12
- return if base.respond_to?(:Contract)
13
-
14
12
  base.extend(MethodDecorators)
15
13
 
16
14
  base.instance_eval do
@@ -1,3 +1,3 @@
1
1
  module Contracts
2
- VERSION = "0.13.0"
2
+ VERSION = "0.14.0"
3
3
  end
@@ -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
@@ -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
@@ -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.13.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-01-25 00:00:00.000000000 Z
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: []