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 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: []