contracts-lite 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.markdown +80 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +102 -0
- data/TODO.markdown +6 -0
- data/TUTORIAL.md +747 -0
- data/benchmarks/bench.rb +67 -0
- data/benchmarks/hash.rb +69 -0
- data/benchmarks/invariants.rb +91 -0
- data/benchmarks/io.rb +62 -0
- data/benchmarks/wrap_test.rb +57 -0
- data/contracts.gemspec +13 -0
- data/lib/contracts.rb +231 -0
- data/lib/contracts/builtin_contracts.rb +541 -0
- data/lib/contracts/call_with.rb +97 -0
- data/lib/contracts/core.rb +52 -0
- data/lib/contracts/decorators.rb +47 -0
- data/lib/contracts/engine.rb +26 -0
- data/lib/contracts/engine/base.rb +136 -0
- data/lib/contracts/engine/eigenclass.rb +50 -0
- data/lib/contracts/engine/target.rb +70 -0
- data/lib/contracts/error_formatter.rb +121 -0
- data/lib/contracts/errors.rb +71 -0
- data/lib/contracts/formatters.rb +134 -0
- data/lib/contracts/invariants.rb +68 -0
- data/lib/contracts/method_handler.rb +195 -0
- data/lib/contracts/method_reference.rb +100 -0
- data/lib/contracts/support.rb +59 -0
- data/lib/contracts/validators.rb +139 -0
- data/lib/contracts/version.rb +3 -0
- data/script/rubocop +7 -0
- data/spec/builtin_contracts_spec.rb +461 -0
- data/spec/contracts_spec.rb +748 -0
- data/spec/error_formatter_spec.rb +68 -0
- data/spec/fixtures/fixtures.rb +710 -0
- data/spec/invariants_spec.rb +17 -0
- data/spec/module_spec.rb +18 -0
- data/spec/override_validators_spec.rb +162 -0
- data/spec/ruby_version_specific/contracts_spec_1.9.rb +24 -0
- data/spec/ruby_version_specific/contracts_spec_2.0.rb +55 -0
- data/spec/ruby_version_specific/contracts_spec_2.1.rb +63 -0
- data/spec/spec_helper.rb +102 -0
- data/spec/support.rb +10 -0
- data/spec/support_spec.rb +21 -0
- data/spec/validators_spec.rb +47 -0
- metadata +94 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
module Contracts
|
2
|
+
class ErrorFormatters
|
3
|
+
def self.failure_msg(data)
|
4
|
+
class_for(data).new(data).message
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.class_for(data)
|
8
|
+
return Contracts::KeywordArgsErrorFormatter if keyword_args?(data)
|
9
|
+
DefaultErrorFormatter
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.keyword_args?(data)
|
13
|
+
data[:contract].is_a?(Contracts::Builtin::KeywordArgs) && data[:arg].is_a?(Hash)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class DefaultErrorFormatter
|
18
|
+
attr_accessor :data
|
19
|
+
def initialize(data)
|
20
|
+
@data = data
|
21
|
+
end
|
22
|
+
|
23
|
+
def message
|
24
|
+
%{#{header}
|
25
|
+
Expected: #{expected},
|
26
|
+
Actual: #{data[:arg].inspect}
|
27
|
+
Value guarded in: #{data[:class]}::#{method_name}
|
28
|
+
With Contract: #{data[:contracts]}
|
29
|
+
At: #{position} }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def header
|
35
|
+
if data[:return_value]
|
36
|
+
"Contract violation for return value:"
|
37
|
+
else
|
38
|
+
"Contract violation for argument #{data[:arg_pos]} of #{data[:total_args]}:"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def expected
|
43
|
+
Contracts::Formatters::Expected.new(data[:contract]).contract
|
44
|
+
end
|
45
|
+
|
46
|
+
def position
|
47
|
+
Contracts::Support.method_position(data[:method])
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_name
|
51
|
+
Contracts::Support.method_name(data[:method])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class KeywordArgsErrorFormatter < DefaultErrorFormatter
|
56
|
+
def message
|
57
|
+
s = []
|
58
|
+
s << "#{header}"
|
59
|
+
s << " Expected: #{expected}"
|
60
|
+
s << " Actual: #{data[:arg].inspect}"
|
61
|
+
s << " Missing Contract: #{missing_contract_info}" unless missing_contract_info.empty?
|
62
|
+
s << " Invalid Args: #{invalid_args_info}" unless invalid_args_info.empty?
|
63
|
+
s << " Missing Args: #{missing_args_info}" unless missing_args_info.empty?
|
64
|
+
s << " Value guarded in: #{data[:class]}::#{method_name}"
|
65
|
+
s << " With Contract: #{data[:contracts]}"
|
66
|
+
s << " At: #{position} "
|
67
|
+
|
68
|
+
s.join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def missing_args_info
|
74
|
+
@missing_args_info ||= begin
|
75
|
+
missing_keys = contract_options.keys - arg.keys
|
76
|
+
contract_options.select do |key, _|
|
77
|
+
missing_keys.include?(key)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def missing_contract_info
|
83
|
+
@missing_contract_info ||= begin
|
84
|
+
contract_keys = contract_options.keys
|
85
|
+
arg.select { |key, _| !contract_keys.include?(key) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def invalid_args_info
|
90
|
+
@invalid_args_info ||= begin
|
91
|
+
invalid_keys = []
|
92
|
+
arg.each do |key, value|
|
93
|
+
contract = contract_options[key]
|
94
|
+
next unless contract
|
95
|
+
invalid_keys.push(key) unless check_contract(contract, value)
|
96
|
+
end
|
97
|
+
invalid_keys.map do |key|
|
98
|
+
{key => arg[key], :contract => contract_options[key] }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_contract(contract, value)
|
104
|
+
if contract.respond_to?(:valid?)
|
105
|
+
contract.valid?(value)
|
106
|
+
else
|
107
|
+
value.is_a?(contract)
|
108
|
+
end
|
109
|
+
rescue
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
def contract_options
|
114
|
+
@contract_options ||= data[:contract].send(:options)
|
115
|
+
end
|
116
|
+
|
117
|
+
def arg
|
118
|
+
data[:arg]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# @private
|
2
|
+
# Base class for Contract errors
|
3
|
+
#
|
4
|
+
# If default failure callback is used it stores failure data
|
5
|
+
class ContractBaseError < ArgumentError
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
def initialize(message, data)
|
9
|
+
super(message)
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used to convert to simple ContractError from other contract errors
|
14
|
+
def to_contract_error
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Default contract error
|
20
|
+
#
|
21
|
+
# If default failure callback is used, users normally see only these contract errors
|
22
|
+
class ContractError < ContractBaseError
|
23
|
+
end
|
24
|
+
|
25
|
+
class ParamContractError < ContractError
|
26
|
+
end
|
27
|
+
|
28
|
+
class ReturnContractError < ContractError
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
# Special contract error used internally to detect pattern failure during pattern matching
|
33
|
+
class PatternMatchingError < ContractBaseError
|
34
|
+
# Used to convert to ContractError from PatternMatchingError
|
35
|
+
def to_contract_error
|
36
|
+
ContractError.new(to_s, data)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Base invariant violation error
|
41
|
+
class InvariantError < StandardError
|
42
|
+
def to_contract_error
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Contracts
|
48
|
+
# Error issued when user haven't included Contracts in original class but used Contract definition in singleton class
|
49
|
+
#
|
50
|
+
# Provides useful description for user of the gem and an example of correct usage.
|
51
|
+
class ContractsNotIncluded < TypeError
|
52
|
+
DEFAULT_MESSAGE = %{In order to use contracts in singleton class, please include Contracts module in original class
|
53
|
+
Example:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class Example
|
57
|
+
include Contracts # this line is required
|
58
|
+
class << self
|
59
|
+
# you can use `Contract` definition here now
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```}
|
63
|
+
|
64
|
+
attr_reader :message
|
65
|
+
alias_method :to_s, :message
|
66
|
+
|
67
|
+
def initialize(message = DEFAULT_MESSAGE)
|
68
|
+
@message = message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Contracts
|
2
|
+
# A namespace for classes related to formatting.
|
3
|
+
module Formatters
|
4
|
+
# Used to format contracts for the `Expected:` field of error output.
|
5
|
+
class Expected
|
6
|
+
# @param full [Boolean] if false only unique `to_s` values will be output,
|
7
|
+
# non unique values become empty string.
|
8
|
+
def initialize(contract, full = true)
|
9
|
+
@contract, @full = contract, full
|
10
|
+
end
|
11
|
+
|
12
|
+
# Formats any type of Contract.
|
13
|
+
def contract(contract = @contract)
|
14
|
+
if contract.is_a?(Hash)
|
15
|
+
hash_contract(contract)
|
16
|
+
elsif contract.is_a?(Array)
|
17
|
+
array_contract(contract)
|
18
|
+
else
|
19
|
+
InspectWrapper.create(contract, @full)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Formats Hash contracts.
|
24
|
+
def hash_contract(hash)
|
25
|
+
@full = true # Complex values output completely, overriding @full
|
26
|
+
hash.inject({}) do |repr, (k, v)|
|
27
|
+
repr.merge(k => InspectWrapper.create(contract(v), @full))
|
28
|
+
end.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
# Formats Array contracts.
|
32
|
+
def array_contract(array)
|
33
|
+
@full = true
|
34
|
+
array.map { |v| InspectWrapper.create(contract(v), @full) }.inspect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# A wrapper class to produce correct inspect behaviour for different
|
39
|
+
# contract values - constants, Class contracts, instance contracts etc.
|
40
|
+
module InspectWrapper
|
41
|
+
# InspectWrapper is a factory, will never be an instance
|
42
|
+
# @return [ClassInspectWrapper, ObjectInspectWrapper]
|
43
|
+
def self.create(value, full = true)
|
44
|
+
if value.class == Class
|
45
|
+
ClassInspectWrapper
|
46
|
+
else
|
47
|
+
ObjectInspectWrapper
|
48
|
+
end.new(value, full)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param full [Boolean] if false only unique `to_s` values will be output,
|
52
|
+
# non unique values become empty string.
|
53
|
+
def initialize(value, full)
|
54
|
+
@value, @full = value, full
|
55
|
+
end
|
56
|
+
|
57
|
+
# Inspect different types of contract values.
|
58
|
+
# Contracts module prefix will be removed from classes.
|
59
|
+
# Custom to_s messages will be wrapped in round brackets to differentiate
|
60
|
+
# from standard Strings.
|
61
|
+
# Primitive values e.g. 42, true, nil will be left alone.
|
62
|
+
def inspect
|
63
|
+
return "" unless full?
|
64
|
+
return @value.inspect if empty_val?
|
65
|
+
return @value.to_s if plain?
|
66
|
+
return delim(@value.to_s) if useful_to_s?
|
67
|
+
useful_inspect
|
68
|
+
end
|
69
|
+
|
70
|
+
def delim(value)
|
71
|
+
@full ? "(#{value})" : "#{value}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Eliminates eronious quotes in output that plain inspect includes.
|
75
|
+
def to_s
|
76
|
+
inspect
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def empty_val?
|
82
|
+
@value.nil? || @value == ""
|
83
|
+
end
|
84
|
+
|
85
|
+
def full?
|
86
|
+
@full ||
|
87
|
+
@value.is_a?(Hash) || @value.is_a?(Array) ||
|
88
|
+
(!plain? && useful_to_s?)
|
89
|
+
end
|
90
|
+
|
91
|
+
def plain?
|
92
|
+
# Not a type of contract that can have a custom to_s defined
|
93
|
+
!@value.is_a?(Builtin::CallableClass) && @value.class != Class
|
94
|
+
end
|
95
|
+
|
96
|
+
def useful_to_s?
|
97
|
+
# Useless to_s value or no custom to_s behavious defined
|
98
|
+
!empty_to_s? && custom_to_s?
|
99
|
+
end
|
100
|
+
|
101
|
+
def empty_to_s?
|
102
|
+
@value.to_s.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
def strip_prefix(val)
|
106
|
+
val.gsub(/^Contracts::Builtin::/, "")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class ClassInspectWrapper
|
111
|
+
include InspectWrapper
|
112
|
+
|
113
|
+
def custom_to_s?
|
114
|
+
@value.to_s != @value.name
|
115
|
+
end
|
116
|
+
|
117
|
+
def useful_inspect
|
118
|
+
strip_prefix(empty_to_s? ? @value.name : @value.inspect)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class ObjectInspectWrapper
|
123
|
+
include InspectWrapper
|
124
|
+
|
125
|
+
def custom_to_s?
|
126
|
+
!@value.to_s.match(/#\<\w+:.+\>/)
|
127
|
+
end
|
128
|
+
|
129
|
+
def useful_inspect
|
130
|
+
strip_prefix(empty_to_s? ? @value.class.name : @value.inspect)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Contracts
|
2
|
+
module Invariants
|
3
|
+
def self.included(base)
|
4
|
+
common base
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.extended(base)
|
8
|
+
common base
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.common(base)
|
12
|
+
return if base.respond_to?(:Invariant)
|
13
|
+
|
14
|
+
base.extend(InvariantExtension)
|
15
|
+
end
|
16
|
+
|
17
|
+
def verify_invariants!(method)
|
18
|
+
return unless self.class.respond_to?(:invariants)
|
19
|
+
|
20
|
+
self.class.invariants.each do |invariant|
|
21
|
+
invariant.check_on(self, method)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module InvariantExtension
|
26
|
+
def invariant(name, &condition)
|
27
|
+
return if ENV["NO_CONTRACTS"]
|
28
|
+
|
29
|
+
invariants << Invariant.new(self, name, &condition)
|
30
|
+
end
|
31
|
+
|
32
|
+
def invariants
|
33
|
+
@invariants ||= []
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Invariant
|
38
|
+
def initialize(klass, name, &condition)
|
39
|
+
@klass, @name, @condition = klass, name, condition
|
40
|
+
end
|
41
|
+
|
42
|
+
def expected
|
43
|
+
"#{@name} condition to be true"
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_on(target, method)
|
47
|
+
return if target.instance_eval(&@condition)
|
48
|
+
|
49
|
+
self.class.failure_callback(:expected => expected,
|
50
|
+
:actual => false,
|
51
|
+
:target => target,
|
52
|
+
:method => method)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.failure_callback(data)
|
56
|
+
fail InvariantError, failure_msg(data)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.failure_msg(data)
|
60
|
+
%{Invariant violation:
|
61
|
+
Expected: #{data[:expected]}
|
62
|
+
Actual: #{data[:actual]}
|
63
|
+
Value guarded in: #{data[:target].class}::#{Support.method_name(data[:method])}
|
64
|
+
At: #{Support.method_position(data[:method])}}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Contracts
|
2
|
+
# Handles class and instance methods addition
|
3
|
+
# Represents single such method
|
4
|
+
class MethodHandler
|
5
|
+
METHOD_REFERENCE_FACTORY = {
|
6
|
+
:class_methods => SingletonMethodReference,
|
7
|
+
:instance_methods => MethodReference
|
8
|
+
}
|
9
|
+
|
10
|
+
RAW_METHOD_STRATEGY = {
|
11
|
+
:class_methods => lambda { |target, name| target.method(name) },
|
12
|
+
:instance_methods => lambda { |target, name| target.instance_method(name) }
|
13
|
+
}
|
14
|
+
|
15
|
+
# Creates new instance of MethodHandler
|
16
|
+
#
|
17
|
+
# @param [Symbol] method_name
|
18
|
+
# @param [Bool] is_class_method
|
19
|
+
# @param [Class] target - class that method got added to
|
20
|
+
def initialize(method_name, is_class_method, target)
|
21
|
+
@method_name = method_name
|
22
|
+
@is_class_method = is_class_method
|
23
|
+
@target = target
|
24
|
+
end
|
25
|
+
|
26
|
+
# Handles method addition
|
27
|
+
def handle
|
28
|
+
return unless engine?
|
29
|
+
return if decorators.empty?
|
30
|
+
|
31
|
+
validate_decorators!
|
32
|
+
validate_pattern_matching!
|
33
|
+
|
34
|
+
engine.add_method_decorator(method_type, method_name, decorator)
|
35
|
+
mark_pattern_matching_decorators
|
36
|
+
method_reference.make_alias(target)
|
37
|
+
redefine_method
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :method_name, :is_class_method, :target
|
43
|
+
|
44
|
+
def engine?
|
45
|
+
Engine.applied?(target)
|
46
|
+
end
|
47
|
+
|
48
|
+
def engine
|
49
|
+
Engine.fetch_from(target)
|
50
|
+
end
|
51
|
+
|
52
|
+
def decorators
|
53
|
+
@_decorators ||= engine.all_decorators
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_type
|
57
|
+
@_method_type ||= is_class_method ? :class_methods : :instance_methods
|
58
|
+
end
|
59
|
+
# _method_type is required for assigning it to local variable with
|
60
|
+
# the same name. See: #redefine_method
|
61
|
+
alias_method :_method_type, :method_type
|
62
|
+
|
63
|
+
def method_reference
|
64
|
+
@_method_reference ||= METHOD_REFERENCE_FACTORY[method_type].new(method_name, raw_method)
|
65
|
+
end
|
66
|
+
|
67
|
+
def raw_method
|
68
|
+
RAW_METHOD_STRATEGY[method_type].call(target, method_name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ignore_decorators?
|
72
|
+
ENV["NO_CONTRACTS"] && !pattern_matching?
|
73
|
+
end
|
74
|
+
|
75
|
+
def decorated_methods
|
76
|
+
@_decorated_methods ||= engine.decorated_methods_for(method_type, method_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def pattern_matching?
|
80
|
+
return @_pattern_matching if defined?(@_pattern_matching)
|
81
|
+
@_pattern_matching = decorated_methods.any? { |x| x.method != method_reference }
|
82
|
+
end
|
83
|
+
|
84
|
+
def mark_pattern_matching_decorators
|
85
|
+
return unless pattern_matching?
|
86
|
+
decorated_methods.each(&:pattern_match!)
|
87
|
+
end
|
88
|
+
|
89
|
+
def decorator
|
90
|
+
@_decorator ||= decorator_class.new(target, method_reference, *decorator_args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def decorator_class
|
94
|
+
decorators.first[0]
|
95
|
+
end
|
96
|
+
|
97
|
+
def decorator_args
|
98
|
+
decorators.first[1]
|
99
|
+
end
|
100
|
+
|
101
|
+
def redefine_method
|
102
|
+
return if ignore_decorators?
|
103
|
+
|
104
|
+
# Those are required for instance_eval to be able to refer them
|
105
|
+
name = method_name
|
106
|
+
method_type = _method_type
|
107
|
+
current_engine = engine
|
108
|
+
|
109
|
+
# We are gonna redefine original method here
|
110
|
+
method_reference.make_definition(target) do |*args, &blk|
|
111
|
+
engine = current_engine.nearest_decorated_ancestor
|
112
|
+
|
113
|
+
# If we weren't able to find any ancestor that has decorated methods
|
114
|
+
# FIXME : this looks like untested code (commenting it out doesn't make specs red)
|
115
|
+
unless engine
|
116
|
+
fail "Couldn't find decorator for method " + self.class.name + ":#{name}.\nDoes this method look correct to you? If you are using contracts from rspec, rspec wraps classes in it's own class.\nLook at the specs for contracts.ruby as an example of how to write contracts in this case."
|
117
|
+
end
|
118
|
+
|
119
|
+
# Fetch decorated methods out of the contracts engine
|
120
|
+
decorated_methods = engine.decorated_methods_for(method_type, name)
|
121
|
+
|
122
|
+
# This adds support for overloading methods. Here we go
|
123
|
+
# through each method and call it with the arguments.
|
124
|
+
# If we get a failure_exception, we move to the next
|
125
|
+
# function. Otherwise we return the result.
|
126
|
+
# If we run out of functions, we raise the last error, but
|
127
|
+
# convert it to_contract_error.
|
128
|
+
success = false
|
129
|
+
i = 0
|
130
|
+
result = nil
|
131
|
+
expected_error = decorated_methods[0].failure_exception
|
132
|
+
|
133
|
+
until success
|
134
|
+
decorated_method = decorated_methods[i]
|
135
|
+
i += 1
|
136
|
+
begin
|
137
|
+
success = true
|
138
|
+
result = decorated_method.call_with(self, *args, &blk)
|
139
|
+
rescue expected_error => error
|
140
|
+
success = false
|
141
|
+
unless decorated_methods[i]
|
142
|
+
begin
|
143
|
+
::Contract.failure_callback(error.data, false)
|
144
|
+
rescue expected_error => final_error
|
145
|
+
raise final_error.to_contract_error
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Return the result of successfully called method
|
152
|
+
result
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def validate_decorators!
|
157
|
+
return if decorators.size == 1
|
158
|
+
|
159
|
+
fail %{
|
160
|
+
Oops, it looks like method '#{name}' has multiple contracts:
|
161
|
+
#{decorators.map { |x| x[1][0].inspect }.join("\n")}
|
162
|
+
|
163
|
+
Did you accidentally put more than one contract on a single function, like so?
|
164
|
+
|
165
|
+
Contract String => String
|
166
|
+
Contract Num => String
|
167
|
+
def foo x
|
168
|
+
end
|
169
|
+
|
170
|
+
If you did NOT, then you have probably discovered a bug in this library.
|
171
|
+
Please file it along with the relevant code at:
|
172
|
+
https://github.com/egonSchiele/contracts.ruby/issues
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_pattern_matching!
|
177
|
+
new_args_contract = decorator.args_contracts
|
178
|
+
matched = decorated_methods.select do |contract|
|
179
|
+
contract.args_contracts == new_args_contract
|
180
|
+
end
|
181
|
+
|
182
|
+
return if matched.empty?
|
183
|
+
|
184
|
+
fail ContractError.new(%{
|
185
|
+
It looks like you are trying to use pattern-matching, but
|
186
|
+
multiple definitions for function '#{method_name}' have the same
|
187
|
+
contract for input parameters:
|
188
|
+
|
189
|
+
#{(matched + [decorator]).map(&:to_s).join("\n")}
|
190
|
+
|
191
|
+
Each definition needs to have a different contract for the parameters.
|
192
|
+
}, {})
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|