contracts 0.17 → 0.17.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/code_style_checks.yaml +4 -2
- data/.github/workflows/tests.yaml +8 -6
- data/.rubocop.yml +1 -1
- data/CHANGELOG.markdown +15 -1
- data/README.md +10 -2
- data/TUTORIAL.md +14 -14
- data/contracts.gemspec +0 -4
- data/features/advanced/pattern_matching.feature +133 -0
- data/features/builtin_contracts/keyword_args_with_optional_positional_args.feature +76 -0
- data/features/builtin_contracts/none.feature +15 -9
- data/lib/contracts/builtin_contracts.rb +3 -3
- data/lib/contracts/call_with.rb +14 -3
- data/lib/contracts/formatters.rb +3 -0
- data/lib/contracts/method_handler.rb +3 -1
- data/lib/contracts/version.rb +1 -1
- data/lib/contracts.rb +7 -24
- data/spec/builtin_contracts_spec.rb +1 -1
- data/spec/fixtures/fixtures.rb +9 -9
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d5a8e7593b70e99ae8c0f56e6bf4484b884ecfbb740a418f27a78321ca856c9
|
4
|
+
data.tar.gz: c62fb8724ebfe3aea0dc2ff4ae8f9af119192d84d57244076ae83d60a5d26c03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea0815ee43b56200ab8465c112f5144989a0ac12c1a9e463ec7bc61ab14b1eeae5ef226ffed7961770b59d57fcfac3527445620cafaa55675337d289beedac4b
|
7
|
+
data.tar.gz: 1895e5c8786d024696e05be452c749f1f830e538b348d30fc9deb165f92a3f87697659b5fef784a65e4eda5f4306ed3c38a7d8622ef72fd76bd5cd38e41f3b1f
|
@@ -6,11 +6,13 @@ on:
|
|
6
6
|
- master
|
7
7
|
paths-ignore:
|
8
8
|
- 'README.md'
|
9
|
+
- 'CHANGELOG.markdown'
|
9
10
|
push:
|
10
11
|
branches:
|
11
12
|
- master
|
12
13
|
paths-ignore:
|
13
14
|
- 'README.md'
|
15
|
+
- 'CHANGELOG.markdown'
|
14
16
|
|
15
17
|
jobs:
|
16
18
|
rubocop:
|
@@ -22,11 +24,11 @@ jobs:
|
|
22
24
|
os:
|
23
25
|
- ubuntu
|
24
26
|
ruby:
|
25
|
-
- "3.
|
27
|
+
- "3.3"
|
26
28
|
runs-on: ${{ matrix.os }}-latest
|
27
29
|
steps:
|
28
30
|
- name: Checkout
|
29
|
-
uses: actions/checkout@
|
31
|
+
uses: actions/checkout@v4
|
30
32
|
- name: Setup Ruby
|
31
33
|
uses: ruby/setup-ruby@v1
|
32
34
|
with:
|
@@ -6,11 +6,13 @@ on:
|
|
6
6
|
- master
|
7
7
|
paths-ignore:
|
8
8
|
- 'README.md'
|
9
|
+
- 'CHANGELOG.markdown'
|
9
10
|
push:
|
10
11
|
branches:
|
11
12
|
- master
|
12
13
|
paths-ignore:
|
13
14
|
- 'README.md'
|
15
|
+
- 'CHANGELOG.markdown'
|
14
16
|
|
15
17
|
jobs:
|
16
18
|
unit_tests:
|
@@ -22,16 +24,16 @@ jobs:
|
|
22
24
|
os:
|
23
25
|
- ubuntu
|
24
26
|
ruby:
|
27
|
+
- "3.3"
|
28
|
+
- "3.2"
|
29
|
+
- "3.1"
|
25
30
|
- "3.0"
|
26
|
-
test_command:
|
27
|
-
|
28
|
-
- os: ubuntu
|
29
|
-
ruby: "3.0"
|
30
|
-
test_command: "bundle exec rspec"
|
31
|
+
test_command:
|
32
|
+
- "bundle exec rspec && bundle exec cucumber"
|
31
33
|
runs-on: ${{ matrix.os }}-latest
|
32
34
|
steps:
|
33
35
|
- name: Checkout
|
34
|
-
uses: actions/checkout@
|
36
|
+
uses: actions/checkout@v4
|
35
37
|
- name: Setup Ruby
|
36
38
|
uses: ruby/setup-ruby@v1
|
37
39
|
with:
|
data/.rubocop.yml
CHANGED
@@ -64,7 +64,7 @@ Style/Documentation:
|
|
64
64
|
Layout/LineLength:
|
65
65
|
Enabled: false
|
66
66
|
|
67
|
-
# triggered by Contract ({ :name => String, :age =>
|
67
|
+
# triggered by Contract ({ :name => String, :age => Integer }) => nil
|
68
68
|
Lint/ParenthesesAsGroupedExpression:
|
69
69
|
Enabled: false
|
70
70
|
|
data/CHANGELOG.markdown
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
|
2
|
+
## [v0.17.1] - 2024-10-06
|
3
|
+
|
4
|
+
[v0.17.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.17...v0.17.1
|
5
|
+
|
6
|
+
- Bugfix: Fix keyword arguments contract when used with optional positional arguments - [PikachuEXE](https://github.com/PikachuEXE) [#305](https://github.com/egonSchiele/contracts.ruby/pull/305)
|
7
|
+
- Enhancement: Always load version.rb, suppress legacy deprecation warning - [Vlad Pisanov](https://github.com/vlad-pisanov) [#301](https://github.com/egonSchiele/contracts.ruby/pull/306)
|
8
|
+
- Enhancement: Update doc & spec about deprecated `Fixnum` to `Integer` - [PikachuEXE](https://github.com/PikachuEXE) [#301](https://github.com/egonSchiele/contracts.ruby/pull/301)
|
9
|
+
|
10
|
+
## [v0.17] - 2021-09-28
|
11
|
+
|
12
|
+
[v0.17]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.1...v0.17
|
13
|
+
|
14
|
+
- Update implementation & spec to be 3.0 compatible **Support for Ruby 2 has been discontinued** - [PikachuEXE](https://github.com/PikachuEXE) [#295](https://github.com/egonSchiele/contracts.ruby/pull/295)
|
15
|
+
|
2
16
|
## [v0.16.1] - 2021-04-17
|
3
17
|
|
4
18
|
[v0.16.1]: https://github.com/egonSchiele/contracts.ruby/compare/v0.16.0...v0.16.1
|
@@ -20,7 +34,7 @@
|
|
20
34
|
[v0.15.0]: https://github.com/egonSchiele/contracts.ruby/compare/v0.14.0...v0.15.0
|
21
35
|
|
22
36
|
- Bugfix: Func contract's return value isn't enforced with blocks - [Piotr Szmielew](https://github.com/esse) [#251](https://github.com/egonSchiele/contracts.ruby/pull/251)
|
23
|
-
-
|
37
|
+
- Bugfix: Fix contracts used in AR-models - [Gert Goet](https://github.com/eval) [#237](https://github.com/egonSchiele/contracts.ruby/pull/237)
|
24
38
|
|
25
39
|
## [v0.14.0] - 2016-04-26
|
26
40
|
|
data/README.md
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
This project is looking for a new maintainer! [More details here](https://github.com/egonSchiele/contracts.ruby/issues/249)
|
2
2
|
|
3
|
-
|
3
|
+
|
4
|
+
|
5
|
+
# contracts.ruby [![GitHub Build Status](https://img.shields.io/github/actions/workflow/status/egonSchiele/contracts.ruby/tests.yaml?branch=master&style=flat-square)](https://github.com/egonSchiele/contracts.ruby/actions/workflows/tests.yaml) [![Join the chat at https://gitter.im/egonSchiele/contracts.ruby](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg)](https://gitter.im/egonSchiele/contracts.ruby)
|
4
6
|
|
5
7
|
Contracts let you clearly – even beautifully – express how your code behaves, and free you from writing tons of boilerplate, defensive code.
|
6
8
|
|
7
9
|
You can think of contracts as `assert` on steroids.
|
8
10
|
|
11
|
+
## 0.17.x = Ruby 3.x only
|
12
|
+
|
13
|
+
0.17.x only supports Ruby 3.x
|
14
|
+
Looking for Ruby 2.x support?
|
15
|
+
Use 0.16.x
|
16
|
+
|
9
17
|
## Installation
|
10
18
|
|
11
19
|
gem install contracts
|
@@ -83,7 +91,7 @@ Using contracts.ruby results in very little slowdown. Check out [this blog post]
|
|
83
91
|
|
84
92
|
**Q.** What Rubies can I use this with?
|
85
93
|
|
86
|
-
**A.** It's been tested with `
|
94
|
+
**A.** It's been tested with `3.0` and `3.1`. (In case this list becomes outdated see [`.github/workflows/tests.yaml`](/.github/workflows/tests.yaml))
|
87
95
|
|
88
96
|
If you're using the library, please [let me know](https://github.com/egonSchiele) what project you're using it on :)
|
89
97
|
|
data/TUTORIAL.md
CHANGED
@@ -80,8 +80,8 @@ contracts.ruby comes with a lot of built-in contracts, including the following:
|
|
80
80
|
|
81
81
|
* Logical combinations
|
82
82
|
* [`Maybe`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Maybe) – specifies that a value _may be_ nil, e.g. `Maybe[String]` (equivalent to `Or[String,nil]`)
|
83
|
-
* [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[
|
84
|
-
* [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[
|
83
|
+
* [`Or`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Or) – passes if any of the given contracts pass, e.g. `Or[Integer, Float]`
|
84
|
+
* [`Xor`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Xor) – passes if exactly one of the given contracts pass, e.g. `Xor[Integer, Float]`
|
85
85
|
* [`And`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/And) – passes if all contracts pass, e.g. `And[Nat, -> (n) { n.even? }]`
|
86
86
|
* [`Not`](http://www.rubydoc.info/gems/contracts/Contracts/Builtin/Not) – passes if all contracts fail for the given argument, e.g. `Not[nil]`
|
87
87
|
|
@@ -89,7 +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 =>
|
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 => Integer }]`
|
93
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]`
|
94
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]`
|
95
95
|
|
@@ -152,7 +152,7 @@ end
|
|
152
152
|
|
153
153
|
You always need to specify a contract for the return value. In this example, `hello` doesn't return anything, so the contract is `nil`. Now you know that you can use a constant like `nil` as the end of a contract. Valid values for a contract are:
|
154
154
|
|
155
|
-
- the name of a class (like `String` or `
|
155
|
+
- the name of a class (like `String` or `Integer`)
|
156
156
|
- a constant (like `nil` or `1`)
|
157
157
|
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
158
158
|
- a class that responds to the `valid?` class method (more on this later)
|
@@ -161,32 +161,32 @@ You always need to specify a contract for the return value. In this example, `he
|
|
161
161
|
### A Double Function
|
162
162
|
|
163
163
|
```ruby
|
164
|
-
Contract C::Or[
|
164
|
+
Contract C::Or[Integer, Float] => C::Or[Integer, Float]
|
165
165
|
def double(x)
|
166
166
|
2 * x
|
167
167
|
end
|
168
168
|
```
|
169
169
|
|
170
170
|
Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes.
|
171
|
-
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[
|
171
|
+
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Integer, Float]` is. The longer way to write it would have been:
|
172
172
|
|
173
173
|
```ruby
|
174
|
-
Contract C::Or.new(
|
174
|
+
Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float)
|
175
175
|
```
|
176
176
|
|
177
177
|
All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
|
178
178
|
|
179
179
|
```ruby
|
180
|
-
Contract C::Or[
|
180
|
+
Contract C::Or[Integer, Float] => C::Or[Integer, Float]
|
181
181
|
```
|
182
182
|
|
183
183
|
or
|
184
184
|
|
185
185
|
```ruby
|
186
|
-
Contract C::Or.new(
|
186
|
+
Contract C::Or.new(Integer, Float) => C::Or.new(Integer, Float)
|
187
187
|
```
|
188
188
|
|
189
|
-
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `
|
189
|
+
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Integer` and `Float`. Use that instance to validate the argument.
|
190
190
|
|
191
191
|
### A Product Function
|
192
192
|
|
@@ -455,7 +455,7 @@ Now you can use `Person` wherever you would have used `Or[Hash, nil]`. Your code
|
|
455
455
|
|
456
456
|
Contracts are very easy to define. To re-iterate, there are 5 kinds of contracts:
|
457
457
|
|
458
|
-
- the name of a class (like `String` or `
|
458
|
+
- the name of a class (like `String` or `Integer`)
|
459
459
|
- a constant (like `nil` or `1`)
|
460
460
|
- a `Proc` that takes a value and returns true or false to indicate whether the contract passed or not
|
461
461
|
- a class that responds to the `valid?` class method (more on this later)
|
@@ -511,7 +511,7 @@ The `Or` contract takes a sequence of contracts, and passes if any of them pass.
|
|
511
511
|
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
|
512
512
|
|
513
513
|
```ruby
|
514
|
-
Contract C::Or[
|
514
|
+
Contract C::Or[Integer, Float] => C::Num
|
515
515
|
def double(x)
|
516
516
|
2 * x
|
517
517
|
end
|
@@ -520,7 +520,7 @@ end
|
|
520
520
|
Without `CallableClass`, we would have to use `.new` instead:
|
521
521
|
|
522
522
|
```ruby
|
523
|
-
Contract C::Or.new(
|
523
|
+
Contract C::Or.new(Integer, Float) => C::Num
|
524
524
|
def double(x)
|
525
525
|
# etc
|
526
526
|
```
|
@@ -723,7 +723,7 @@ class MyBirthday < Struct.new(:day, :month)
|
|
723
723
|
invariant(:day) { 1 <= day && day <= 31 }
|
724
724
|
invariant(:month) { 1 <= month && month <= 12 }
|
725
725
|
|
726
|
-
Contract C::None =>
|
726
|
+
Contract C::None => Integer
|
727
727
|
def silly_next_day!
|
728
728
|
self.day += 1
|
729
729
|
end
|
data/contracts.gemspec
CHANGED
@@ -13,8 +13,4 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.homepage = "https://github.com/egonSchiele/contracts.ruby"
|
14
14
|
s.license = "BSD-2-Clause"
|
15
15
|
s.required_ruby_version = [">= 3.0", "< 4"]
|
16
|
-
s.post_install_message = "
|
17
|
-
0.16.x will be the supporting Ruby 2.x and be feature frozen (only fixes will be released)
|
18
|
-
For Ruby 3.x use 0.17.x or later (might not be released yet)
|
19
|
-
"
|
20
16
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
Feature: Method Overloading
|
2
|
+
|
3
|
+
You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Contract 1 => 1
|
7
|
+
def fact x
|
8
|
+
x
|
9
|
+
end
|
10
|
+
|
11
|
+
Contract C::Num => C::Num
|
12
|
+
def fact x
|
13
|
+
x * fact(x - 1)
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
Background:
|
18
|
+
Given a file named "method_overloading_with_positional_args_usage.rb" with:
|
19
|
+
"""ruby
|
20
|
+
require "contracts"
|
21
|
+
C = Contracts
|
22
|
+
|
23
|
+
class Example
|
24
|
+
include Contracts::Core
|
25
|
+
|
26
|
+
Contract 1 => 1
|
27
|
+
def fact(x)
|
28
|
+
x
|
29
|
+
end
|
30
|
+
|
31
|
+
Contract C::Num => C::Num
|
32
|
+
def fact(x)
|
33
|
+
x * fact(x - 1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
"""
|
37
|
+
|
38
|
+
Given a file named "method_overloading_with_keyword_args_usage.rb" with:
|
39
|
+
"""ruby
|
40
|
+
require "contracts"
|
41
|
+
C = Contracts
|
42
|
+
|
43
|
+
class Example
|
44
|
+
include Contracts::Core
|
45
|
+
|
46
|
+
Contract C::KeywordArgs[age: Integer, size: Symbol] => String
|
47
|
+
def speak(age:, size:)
|
48
|
+
"age: #{age} size: #{size}"
|
49
|
+
end
|
50
|
+
|
51
|
+
Contract C::KeywordArgs[sound: String] => String
|
52
|
+
def speak(sound:)
|
53
|
+
"sound: #{sound}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
"""
|
57
|
+
|
58
|
+
Scenario: Positional Args Method 1
|
59
|
+
Given a file named "positional_args_method_1.rb" with:
|
60
|
+
"""ruby
|
61
|
+
require "./method_overloading_with_positional_args_usage"
|
62
|
+
puts Example.new.fact(1)
|
63
|
+
"""
|
64
|
+
When I run `ruby positional_args_method_1.rb`
|
65
|
+
Then the output should contain:
|
66
|
+
"""
|
67
|
+
1
|
68
|
+
"""
|
69
|
+
|
70
|
+
Scenario: Positional Args Method 2
|
71
|
+
Given a file named "positional_args_method_2.rb" with:
|
72
|
+
"""ruby
|
73
|
+
require "./method_overloading_with_positional_args_usage"
|
74
|
+
puts Example.new.fact(4)
|
75
|
+
"""
|
76
|
+
When I run `ruby positional_args_method_2.rb`
|
77
|
+
Then the output should contain:
|
78
|
+
"""
|
79
|
+
24
|
80
|
+
"""
|
81
|
+
|
82
|
+
Scenario: Keyword Args Method 1
|
83
|
+
Given a file named "keyword_args_method_1.rb" with:
|
84
|
+
"""ruby
|
85
|
+
require "./method_overloading_with_keyword_args_usage"
|
86
|
+
puts Example.new.speak(age: 5, size: :large)
|
87
|
+
"""
|
88
|
+
When I run `ruby keyword_args_method_1.rb`
|
89
|
+
Then the output should contain:
|
90
|
+
"""
|
91
|
+
age: 5 size: large
|
92
|
+
"""
|
93
|
+
|
94
|
+
Scenario: Keyword Args Method 2
|
95
|
+
Given a file named "keyword_args_method_2.rb" with:
|
96
|
+
"""ruby
|
97
|
+
require "./method_overloading_with_keyword_args_usage"
|
98
|
+
puts Example.new.speak(sound: "woof")
|
99
|
+
"""
|
100
|
+
When I run `ruby keyword_args_method_2.rb`
|
101
|
+
Then the output should contain:
|
102
|
+
"""
|
103
|
+
sound: woof
|
104
|
+
"""
|
105
|
+
|
106
|
+
Scenario: Incorrect Positional Args Method
|
107
|
+
Given a file named "incorrect_positional_args_method.rb" with:
|
108
|
+
"""ruby
|
109
|
+
require "contracts"
|
110
|
+
C = Contracts
|
111
|
+
|
112
|
+
class Example
|
113
|
+
include Contracts::Core
|
114
|
+
|
115
|
+
# Notice that this method's contract is wider than the one below
|
116
|
+
# This would cause this method to be called every time but never the one below
|
117
|
+
Contract C::Num => C::Num
|
118
|
+
def fact(x)
|
119
|
+
x * fact(x - 1)
|
120
|
+
end
|
121
|
+
|
122
|
+
Contract 1 => 1
|
123
|
+
def fact(x)
|
124
|
+
x
|
125
|
+
end
|
126
|
+
end
|
127
|
+
puts Example.new.fact(4)
|
128
|
+
"""
|
129
|
+
When I run `ruby incorrect_positional_args_method.rb`
|
130
|
+
Then the output should contain:
|
131
|
+
"""
|
132
|
+
stack level too deep (SystemStackError)
|
133
|
+
"""
|
@@ -0,0 +1,76 @@
|
|
1
|
+
Feature: KeywordArgs when used with optional positional arguments
|
2
|
+
|
3
|
+
Checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Contract Any, KeywordArgs[:number => Num, :description => Optional[String]] => Any
|
7
|
+
```
|
8
|
+
|
9
|
+
Background:
|
10
|
+
Given a file named "keyword_args_with_optional_positional_args_usage.rb" with:
|
11
|
+
"""ruby
|
12
|
+
require "contracts"
|
13
|
+
C = Contracts
|
14
|
+
|
15
|
+
class Example
|
16
|
+
include Contracts::Core
|
17
|
+
|
18
|
+
Contract C::Any, String, C::KeywordArgs[b: C::Optional[String]] => Symbol
|
19
|
+
def foo(output, a = 'a', b: 'b')
|
20
|
+
p [a, b]
|
21
|
+
output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
"""
|
25
|
+
|
26
|
+
Scenario: Accepts arguments when only require arguments filled and valid
|
27
|
+
Given a file named "accepts_all_filled_valid_args.rb" with:
|
28
|
+
"""ruby
|
29
|
+
require "./keyword_args_with_optional_positional_args_usage"
|
30
|
+
puts Example.new.foo(:output)
|
31
|
+
"""
|
32
|
+
When I run `ruby accepts_all_filled_valid_args.rb`
|
33
|
+
Then output should contain:
|
34
|
+
"""
|
35
|
+
["a", "b"]
|
36
|
+
output
|
37
|
+
"""
|
38
|
+
|
39
|
+
Scenario: Accepts arguments when all filled and valid
|
40
|
+
Given a file named "accepts_all_filled_valid_args.rb" with:
|
41
|
+
"""ruby
|
42
|
+
require "./keyword_args_with_optional_positional_args_usage"
|
43
|
+
puts Example.new.foo(:output, 'c', b: 'd')
|
44
|
+
"""
|
45
|
+
When I run `ruby accepts_all_filled_valid_args.rb`
|
46
|
+
Then output should contain:
|
47
|
+
"""
|
48
|
+
["c", "d"]
|
49
|
+
output
|
50
|
+
"""
|
51
|
+
|
52
|
+
Scenario: Accepts arguments when only require arguments & optional keyword arguments filled and valid
|
53
|
+
Given a file named "accepts_all_filled_valid_args.rb" with:
|
54
|
+
"""ruby
|
55
|
+
require "./keyword_args_with_optional_positional_args_usage"
|
56
|
+
puts Example.new.foo(:output, b: 'd')
|
57
|
+
"""
|
58
|
+
When I run `ruby accepts_all_filled_valid_args.rb`
|
59
|
+
Then output should contain:
|
60
|
+
"""
|
61
|
+
["a", "d"]
|
62
|
+
output
|
63
|
+
"""
|
64
|
+
|
65
|
+
Scenario: Accepts arguments when only require arguments & optional positional arguments filled and valid
|
66
|
+
Given a file named "accepts_all_filled_valid_args.rb" with:
|
67
|
+
"""ruby
|
68
|
+
require "./keyword_args_with_optional_positional_args_usage"
|
69
|
+
puts Example.new.foo(:output, 'c')
|
70
|
+
"""
|
71
|
+
When I run `ruby accepts_all_filled_valid_args.rb`
|
72
|
+
Then output should contain:
|
73
|
+
"""
|
74
|
+
["c", "b"]
|
75
|
+
output
|
76
|
+
"""
|
@@ -26,7 +26,8 @@ Feature: None
|
|
26
26
|
def autorescue
|
27
27
|
yield
|
28
28
|
rescue => e
|
29
|
-
|
29
|
+
# Since ruby 3.2 the `#inspect` output becomes a bit different
|
30
|
+
puts e.inspect.gsub(/^#</, "").gsub(/Error:\"/, "Error: ")
|
30
31
|
end
|
31
32
|
"""
|
32
33
|
Given a file named "none_usage.rb" with:
|
@@ -42,6 +43,11 @@ Feature: None
|
|
42
43
|
def self.a_symbol(*args)
|
43
44
|
:a_symbol
|
44
45
|
end
|
46
|
+
|
47
|
+
Contract C::None => Symbol
|
48
|
+
def a_symbol(*args)
|
49
|
+
:a_symbol
|
50
|
+
end
|
45
51
|
end
|
46
52
|
"""
|
47
53
|
|
@@ -61,14 +67,14 @@ Feature: None
|
|
61
67
|
Given a file named "anything.rb" with:
|
62
68
|
"""ruby
|
63
69
|
require "./none_usage"
|
64
|
-
autorescue { Example.a_symbol(nil) }
|
65
|
-
autorescue { Example.a_symbol(12) }
|
66
|
-
autorescue { Example.a_symbol(37.5) }
|
67
|
-
autorescue { Example.a_symbol("foo") }
|
68
|
-
autorescue { Example.a_symbol(:foo) }
|
69
|
-
autorescue { Example.a_symbol({}) }
|
70
|
-
autorescue { Example.a_symbol([]) }
|
71
|
-
autorescue { Example.a_symbol(Object) }
|
70
|
+
autorescue { Example.new.a_symbol(nil) }
|
71
|
+
autorescue { Example.new.a_symbol(12) }
|
72
|
+
autorescue { Example.new.a_symbol(37.5) }
|
73
|
+
autorescue { Example.new.a_symbol("foo") }
|
74
|
+
autorescue { Example.new.a_symbol(:foo) }
|
75
|
+
autorescue { Example.new.a_symbol({}) }
|
76
|
+
autorescue { Example.new.a_symbol([]) }
|
77
|
+
autorescue { Example.new.a_symbol(Object) }
|
72
78
|
"""
|
73
79
|
When I run `ruby anything.rb`
|
74
80
|
|
@@ -95,7 +95,7 @@ module Contracts
|
|
95
95
|
|
96
96
|
# Takes a variable number of contracts.
|
97
97
|
# The contract passes if any of the contracts pass.
|
98
|
-
# Example: <tt>Or[
|
98
|
+
# Example: <tt>Or[Integer, Float]</tt>
|
99
99
|
class Or < CallableClass
|
100
100
|
def initialize(*vals)
|
101
101
|
super()
|
@@ -120,7 +120,7 @@ module Contracts
|
|
120
120
|
|
121
121
|
# Takes a variable number of contracts.
|
122
122
|
# The contract passes if exactly one of those contracts pass.
|
123
|
-
# Example: <tt>Xor[
|
123
|
+
# Example: <tt>Xor[Integer, Float]</tt>
|
124
124
|
class Xor < CallableClass
|
125
125
|
def initialize(*vals)
|
126
126
|
super()
|
@@ -146,7 +146,7 @@ module Contracts
|
|
146
146
|
|
147
147
|
# Takes a variable number of contracts.
|
148
148
|
# The contract passes if all contracts pass.
|
149
|
-
# Example: <tt>And[
|
149
|
+
# Example: <tt>And[Integer, Float]</tt>
|
150
150
|
class And < CallableClass
|
151
151
|
def initialize(*vals)
|
152
152
|
super()
|
data/lib/contracts/call_with.rb
CHANGED
@@ -12,8 +12,20 @@ module Contracts
|
|
12
12
|
# Explicitly append blk=nil if nil != Proc contract violation anticipated
|
13
13
|
nil_block_appended = maybe_append_block!(args, blk)
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
if @kargs_validator && !@kargs_validator[kargs]
|
16
|
+
data = {
|
17
|
+
arg: kargs,
|
18
|
+
contract: kargs_contract,
|
19
|
+
class: klass,
|
20
|
+
method: method,
|
21
|
+
contracts: self,
|
22
|
+
arg_pos: :keyword,
|
23
|
+
total_args: args.size,
|
24
|
+
return_value: false,
|
25
|
+
}
|
26
|
+
return ParamContractError.new("as return value", data) if returns
|
27
|
+
return unless Contract.failure_callback(data)
|
28
|
+
end
|
17
29
|
|
18
30
|
# Loop forward validating the arguments up to the splat (if there is one)
|
19
31
|
(@args_contract_index || args.size).times do |i|
|
@@ -84,7 +96,6 @@ module Contracts
|
|
84
96
|
# If we put the block into args for validating, restore the args
|
85
97
|
# OR if we added a fake nil at the end because a block wasn't passed in.
|
86
98
|
args.slice!(-1) if blk || nil_block_appended
|
87
|
-
args.slice!(-1) if kargs_appended
|
88
99
|
result = if method.respond_to?(:call)
|
89
100
|
# proc, block, lambda, etc
|
90
101
|
method.call(*args, **kargs, &blk)
|
data/lib/contracts/formatters.rb
CHANGED
@@ -174,8 +174,10 @@ https://github.com/egonSchiele/contracts.ruby/issues
|
|
174
174
|
|
175
175
|
def validate_pattern_matching!
|
176
176
|
new_args_contract = decorator.args_contracts
|
177
|
+
new_kargs_contract = decorator.kargs_contract
|
177
178
|
matched = decorated_methods.select do |contract|
|
178
|
-
contract.args_contracts == new_args_contract
|
179
|
+
contract.args_contracts == new_args_contract &&
|
180
|
+
contract.kargs_contract == new_kargs_contract
|
179
181
|
end
|
180
182
|
|
181
183
|
return if matched.empty?
|
data/lib/contracts/version.rb
CHANGED
data/lib/contracts.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "contracts/version"
|
3
4
|
require "contracts/attrs"
|
4
5
|
require "contracts/builtin_contracts"
|
5
6
|
require "contracts/decorators"
|
@@ -52,7 +53,7 @@ class Contract < Contracts::Decorator
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
attr_reader :args_contracts, :ret_contract, :klass, :method
|
56
|
+
attr_reader :args_contracts, :kargs_contract, :ret_contract, :klass, :method
|
56
57
|
|
57
58
|
def initialize(klass, method, *contracts)
|
58
59
|
super(klass, method)
|
@@ -69,6 +70,9 @@ class Contract < Contracts::Decorator
|
|
69
70
|
|
70
71
|
# internally we just convert that return value syntax back to an array
|
71
72
|
@args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
|
73
|
+
# Extract contract for keyword arguments
|
74
|
+
@kargs_contract = args_contracts.find { |c| c.is_a?(Contracts::Builtin::KeywordArgs) }
|
75
|
+
args_contracts.delete(kargs_contract) if kargs_contract
|
72
76
|
|
73
77
|
@ret_contract = contracts[-1].values[0]
|
74
78
|
|
@@ -76,6 +80,8 @@ class Contract < Contracts::Decorator
|
|
76
80
|
Contract.make_validator(contract)
|
77
81
|
end
|
78
82
|
|
83
|
+
@kargs_validator = kargs_contract ? Contract.make_validator(kargs_contract) : nil
|
84
|
+
|
79
85
|
@args_contract_index = args_contracts.index do |contract|
|
80
86
|
contract.is_a? Contracts::Args
|
81
87
|
end
|
@@ -93,16 +99,6 @@ class Contract < Contracts::Decorator
|
|
93
99
|
|
94
100
|
# ====
|
95
101
|
|
96
|
-
# == @has_options_contract
|
97
|
-
last_contract = args_contracts.last
|
98
|
-
penultimate_contract = args_contracts[-2]
|
99
|
-
@has_options_contract = if @has_proc_contract
|
100
|
-
penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
101
|
-
else
|
102
|
-
last_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
103
|
-
end
|
104
|
-
# ===
|
105
|
-
|
106
102
|
@klass, @method = klass, method
|
107
103
|
end
|
108
104
|
|
@@ -255,19 +251,6 @@ class Contract < Contracts::Decorator
|
|
255
251
|
true
|
256
252
|
end
|
257
253
|
|
258
|
-
# Same thing for when we have named params but didn't pass any in.
|
259
|
-
# returns true if it appended nil
|
260
|
-
def maybe_append_options! args, kargs, blk
|
261
|
-
return false unless @has_options_contract
|
262
|
-
|
263
|
-
if @has_proc_contract && args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)
|
264
|
-
args.insert(-2, kargs)
|
265
|
-
elsif args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)
|
266
|
-
args << kargs
|
267
|
-
end
|
268
|
-
true
|
269
|
-
end
|
270
|
-
|
271
254
|
# Used to determine type of failure exception this contract should raise in case of failure
|
272
255
|
def failure_exception
|
273
256
|
if pattern_match?
|
data/spec/fixtures/fixtures.rb
CHANGED
@@ -100,11 +100,11 @@ class GenericExample
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
Contract ({ :name => String, :age =>
|
103
|
+
Contract ({ :name => String, :age => Integer }) => nil
|
104
104
|
def person(data)
|
105
105
|
end
|
106
106
|
|
107
|
-
Contract C::StrictHash[{ :name => String, :age =>
|
107
|
+
Contract C::StrictHash[{ :name => String, :age => Integer }] => nil
|
108
108
|
def strict_person(data)
|
109
109
|
end
|
110
110
|
|
@@ -119,7 +119,7 @@ class GenericExample
|
|
119
119
|
def nested_hash_complex_contracts(data)
|
120
120
|
end
|
121
121
|
|
122
|
-
Contract C::KeywordArgs[:name => String, :age =>
|
122
|
+
Contract C::KeywordArgs[:name => String, :age => Integer] => nil
|
123
123
|
def person_keywordargs(name: "name", age: 10)
|
124
124
|
end
|
125
125
|
|
@@ -529,30 +529,30 @@ class MyBirthday
|
|
529
529
|
@month = month
|
530
530
|
end
|
531
531
|
|
532
|
-
Contract C::None =>
|
532
|
+
Contract C::None => Integer
|
533
533
|
def silly_next_day!
|
534
534
|
self.day += 1
|
535
535
|
end
|
536
536
|
|
537
|
-
Contract C::None =>
|
537
|
+
Contract C::None => Integer
|
538
538
|
def silly_next_month!
|
539
539
|
self.month += 1
|
540
540
|
end
|
541
541
|
|
542
|
-
Contract C::None =>
|
542
|
+
Contract C::None => Integer
|
543
543
|
def clever_next_day!
|
544
544
|
return clever_next_month! if day == 31
|
545
545
|
self.day += 1
|
546
546
|
end
|
547
547
|
|
548
|
-
Contract C::None =>
|
548
|
+
Contract C::None => Integer
|
549
549
|
def clever_next_month!
|
550
550
|
return next_year! if month == 12
|
551
551
|
self.month += 1
|
552
552
|
self.day = 1
|
553
553
|
end
|
554
554
|
|
555
|
-
Contract C::None =>
|
555
|
+
Contract C::None => Integer
|
556
556
|
def next_year!
|
557
557
|
self.month = 1
|
558
558
|
self.day = 1
|
@@ -610,7 +610,7 @@ with_enabled_no_contracts do
|
|
610
610
|
body + "!"
|
611
611
|
end
|
612
612
|
|
613
|
-
Contract
|
613
|
+
Contract Integer, String => String
|
614
614
|
def on_response(status, body)
|
615
615
|
"error #{status}: #{body}"
|
616
616
|
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:
|
4
|
+
version: 0.17.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aditya Bhargava
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-15 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
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- cucumber.yml
|
41
41
|
- dependabot.yml
|
42
42
|
- features/README.md
|
43
|
+
- features/advanced/pattern_matching.feature
|
43
44
|
- features/basics/functype.feature
|
44
45
|
- features/basics/pretty-print.feature
|
45
46
|
- features/basics/simple_example.feature
|
@@ -56,6 +57,7 @@ files:
|
|
56
57
|
- features/builtin_contracts/hash_of.feature
|
57
58
|
- features/builtin_contracts/int.feature
|
58
59
|
- features/builtin_contracts/keyword_args.feature
|
60
|
+
- features/builtin_contracts/keyword_args_with_optional_positional_args.feature
|
59
61
|
- features/builtin_contracts/maybe.feature
|
60
62
|
- features/builtin_contracts/nat.feature
|
61
63
|
- features/builtin_contracts/nat_pos.feature
|
@@ -111,9 +113,7 @@ homepage: https://github.com/egonSchiele/contracts.ruby
|
|
111
113
|
licenses:
|
112
114
|
- BSD-2-Clause
|
113
115
|
metadata: {}
|
114
|
-
post_install_message:
|
115
|
-
frozen (only fixes will be released)\n For Ruby 3.x use 0.17.x or later (might
|
116
|
-
not be released yet)\n "
|
116
|
+
post_install_message:
|
117
117
|
rdoc_options: []
|
118
118
|
require_paths:
|
119
119
|
- lib
|
@@ -131,8 +131,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
131
|
- !ruby/object:Gem::Version
|
132
132
|
version: '0'
|
133
133
|
requirements: []
|
134
|
-
rubygems_version: 3.
|
135
|
-
signing_key:
|
134
|
+
rubygems_version: 3.4.10
|
135
|
+
signing_key:
|
136
136
|
specification_version: 4
|
137
137
|
summary: Contracts for Ruby.
|
138
138
|
test_files: []
|