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
data/benchmarks/bench.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require "./lib/contracts"
|
2
|
+
require "benchmark"
|
3
|
+
require "rubygems"
|
4
|
+
require "method_profiler"
|
5
|
+
require "ruby-prof"
|
6
|
+
|
7
|
+
include Contracts
|
8
|
+
|
9
|
+
def add a, b
|
10
|
+
a + b
|
11
|
+
end
|
12
|
+
|
13
|
+
Contract Num, Num => Num
|
14
|
+
def contracts_add a, b
|
15
|
+
a + b
|
16
|
+
end
|
17
|
+
|
18
|
+
def explicit_add a, b
|
19
|
+
fail unless a.is_a?(Numeric)
|
20
|
+
fail unless b.is_a?(Numeric)
|
21
|
+
c = a + b
|
22
|
+
fail unless c.is_a?(Numeric)
|
23
|
+
c
|
24
|
+
end
|
25
|
+
|
26
|
+
def benchmark
|
27
|
+
Benchmark.bm 30 do |x|
|
28
|
+
x.report "testing add" do
|
29
|
+
1_000_000.times do |_|
|
30
|
+
add(rand(1000), rand(1000))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
x.report "testing contracts add" do
|
34
|
+
1_000_000.times do |_|
|
35
|
+
contracts_add(rand(1000), rand(1000))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def profile
|
42
|
+
profilers = []
|
43
|
+
profilers << MethodProfiler.observe(Contract)
|
44
|
+
profilers << MethodProfiler.observe(Object)
|
45
|
+
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
|
46
|
+
profilers << MethodProfiler.observe(Contracts::Decorator)
|
47
|
+
profilers << MethodProfiler.observe(Contracts::Support)
|
48
|
+
profilers << MethodProfiler.observe(UnboundMethod)
|
49
|
+
10_000.times do |_|
|
50
|
+
contracts_add(rand(1000), rand(1000))
|
51
|
+
end
|
52
|
+
profilers.each { |p| puts p.report }
|
53
|
+
end
|
54
|
+
|
55
|
+
def ruby_prof
|
56
|
+
RubyProf.start
|
57
|
+
100_000.times do |_|
|
58
|
+
contracts_add(rand(1000), rand(1000))
|
59
|
+
end
|
60
|
+
result = RubyProf.stop
|
61
|
+
printer = RubyProf::FlatPrinter.new(result)
|
62
|
+
printer.print(STDOUT)
|
63
|
+
end
|
64
|
+
|
65
|
+
benchmark
|
66
|
+
profile
|
67
|
+
ruby_prof if ENV["FULL_BENCH"] # takes some time
|
data/benchmarks/hash.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "./lib/contracts"
|
2
|
+
require "benchmark"
|
3
|
+
require "rubygems"
|
4
|
+
require "method_profiler"
|
5
|
+
require "ruby-prof"
|
6
|
+
|
7
|
+
include Contracts
|
8
|
+
|
9
|
+
def add opts
|
10
|
+
opts[:a] + opts[:b]
|
11
|
+
end
|
12
|
+
|
13
|
+
Contract ({ :a => Num, :b => Num}) => Num
|
14
|
+
def contracts_add opts
|
15
|
+
opts[:a] + opts[:b]
|
16
|
+
end
|
17
|
+
|
18
|
+
def explicit_add opts
|
19
|
+
a = opts[:a]
|
20
|
+
b = opts[:b]
|
21
|
+
fail unless a.is_a?(Numeric)
|
22
|
+
fail unless b.is_a?(Numeric)
|
23
|
+
c = a + b
|
24
|
+
fail unless c.is_a?(Numeric)
|
25
|
+
c
|
26
|
+
end
|
27
|
+
|
28
|
+
def benchmark
|
29
|
+
Benchmark.bm 30 do |x|
|
30
|
+
x.report "testing add" do
|
31
|
+
1_000_000.times do |_|
|
32
|
+
add(:a => rand(1000), :b => rand(1000))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
x.report "testing contracts add" do
|
36
|
+
1_000_000.times do |_|
|
37
|
+
contracts_add(:a => rand(1000), :b => rand(1000))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def profile
|
44
|
+
profilers = []
|
45
|
+
profilers << MethodProfiler.observe(Contract)
|
46
|
+
profilers << MethodProfiler.observe(Object)
|
47
|
+
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
|
48
|
+
profilers << MethodProfiler.observe(Contracts::Decorator)
|
49
|
+
profilers << MethodProfiler.observe(Contracts::Support)
|
50
|
+
profilers << MethodProfiler.observe(UnboundMethod)
|
51
|
+
10_000.times do |_|
|
52
|
+
contracts_add(:a => rand(1000), :b => rand(1000))
|
53
|
+
end
|
54
|
+
profilers.each { |p| puts p.report }
|
55
|
+
end
|
56
|
+
|
57
|
+
def ruby_prof
|
58
|
+
RubyProf.start
|
59
|
+
100_000.times do |_|
|
60
|
+
contracts_add(:a => rand(1000), :b => rand(1000))
|
61
|
+
end
|
62
|
+
result = RubyProf.stop
|
63
|
+
printer = RubyProf::FlatPrinter.new(result)
|
64
|
+
printer.print(STDOUT)
|
65
|
+
end
|
66
|
+
|
67
|
+
benchmark
|
68
|
+
profile
|
69
|
+
ruby_prof if ENV["FULL_BENCH"] # takes some time
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "./lib/contracts"
|
2
|
+
require "benchmark"
|
3
|
+
require "rubygems"
|
4
|
+
require "method_profiler"
|
5
|
+
require "ruby-prof"
|
6
|
+
|
7
|
+
class Obj
|
8
|
+
include Contracts
|
9
|
+
|
10
|
+
attr_accessor :value
|
11
|
+
def initialize value
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
Contract Num, Num => Num
|
16
|
+
def contracts_add a, b
|
17
|
+
a + b
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ObjWithInvariants
|
22
|
+
include Contracts
|
23
|
+
include Contracts::Invariants
|
24
|
+
|
25
|
+
invariant(:value_not_nil) { value != nil }
|
26
|
+
invariant(:value_not_string) { !value.is_a?(String) }
|
27
|
+
|
28
|
+
attr_accessor :value
|
29
|
+
def initialize value
|
30
|
+
@value = value
|
31
|
+
end
|
32
|
+
|
33
|
+
Contract Num, Num => Num
|
34
|
+
def contracts_add a, b
|
35
|
+
a + b
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def benchmark
|
40
|
+
obj = Obj.new(3)
|
41
|
+
obj_with_invariants = ObjWithInvariants.new(3)
|
42
|
+
|
43
|
+
Benchmark.bm 30 do |x|
|
44
|
+
x.report "testing contracts add" do
|
45
|
+
1_000_000.times do |_|
|
46
|
+
obj.contracts_add(rand(1000), rand(1000))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
x.report "testing contracts add with invariants" do
|
50
|
+
1_000_000.times do |_|
|
51
|
+
obj_with_invariants.contracts_add(rand(1000), rand(1000))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def profile
|
58
|
+
obj_with_invariants = ObjWithInvariants.new(3)
|
59
|
+
|
60
|
+
profilers = []
|
61
|
+
profilers << MethodProfiler.observe(Contract)
|
62
|
+
profilers << MethodProfiler.observe(Object)
|
63
|
+
profilers << MethodProfiler.observe(Contracts::Support)
|
64
|
+
profilers << MethodProfiler.observe(Contracts::Invariants)
|
65
|
+
profilers << MethodProfiler.observe(Contracts::Invariants::InvariantExtension)
|
66
|
+
profilers << MethodProfiler.observe(UnboundMethod)
|
67
|
+
|
68
|
+
10_000.times do |_|
|
69
|
+
obj_with_invariants.contracts_add(rand(1000), rand(1000))
|
70
|
+
end
|
71
|
+
|
72
|
+
profilers.each { |p| puts p.report }
|
73
|
+
end
|
74
|
+
|
75
|
+
def ruby_prof
|
76
|
+
RubyProf.start
|
77
|
+
|
78
|
+
obj_with_invariants = ObjWithInvariants.new(3)
|
79
|
+
|
80
|
+
100_000.times do |_|
|
81
|
+
obj_with_invariants.contracts_add(rand(1000), rand(1000))
|
82
|
+
end
|
83
|
+
|
84
|
+
result = RubyProf.stop
|
85
|
+
printer = RubyProf::FlatPrinter.new(result)
|
86
|
+
printer.print(STDOUT)
|
87
|
+
end
|
88
|
+
|
89
|
+
benchmark
|
90
|
+
profile
|
91
|
+
ruby_prof if ENV["FULL_BENCH"] # takes some time
|
data/benchmarks/io.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "./lib/contracts"
|
2
|
+
require "benchmark"
|
3
|
+
require "rubygems"
|
4
|
+
require "method_profiler"
|
5
|
+
require "ruby-prof"
|
6
|
+
require "open-uri"
|
7
|
+
|
8
|
+
include Contracts
|
9
|
+
|
10
|
+
def download url
|
11
|
+
open("http://www.#{url}/").read
|
12
|
+
end
|
13
|
+
|
14
|
+
Contract String => String
|
15
|
+
def contracts_download url
|
16
|
+
open("http://www.#{url}").read
|
17
|
+
end
|
18
|
+
|
19
|
+
@urls = %w{google.com bing.com}
|
20
|
+
|
21
|
+
def benchmark
|
22
|
+
Benchmark.bm 30 do |x|
|
23
|
+
x.report "testing download" do
|
24
|
+
100.times do |_|
|
25
|
+
download(@urls.sample)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
x.report "testing contracts download" do
|
29
|
+
100.times do |_|
|
30
|
+
contracts_download(@urls.sample)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def profile
|
37
|
+
profilers = []
|
38
|
+
profilers << MethodProfiler.observe(Contract)
|
39
|
+
profilers << MethodProfiler.observe(Object)
|
40
|
+
profilers << MethodProfiler.observe(Contracts::MethodDecorators)
|
41
|
+
profilers << MethodProfiler.observe(Contracts::Decorator)
|
42
|
+
profilers << MethodProfiler.observe(Contracts::Support)
|
43
|
+
profilers << MethodProfiler.observe(UnboundMethod)
|
44
|
+
10.times do |_|
|
45
|
+
contracts_download(@urls.sample)
|
46
|
+
end
|
47
|
+
profilers.each { |p| puts p.report }
|
48
|
+
end
|
49
|
+
|
50
|
+
def ruby_prof
|
51
|
+
RubyProf.start
|
52
|
+
10.times do |_|
|
53
|
+
contracts_download(@urls.sample)
|
54
|
+
end
|
55
|
+
result = RubyProf.stop
|
56
|
+
printer = RubyProf::FlatPrinter.new(result)
|
57
|
+
printer.print(STDOUT)
|
58
|
+
end
|
59
|
+
|
60
|
+
benchmark
|
61
|
+
profile
|
62
|
+
ruby_prof if ENV["FULL_BENCH"] # takes some time
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
3
|
+
module Wrapper
|
4
|
+
def self.extended(klass)
|
5
|
+
klass.class_eval do
|
6
|
+
@@methods = {}
|
7
|
+
def self.methods
|
8
|
+
@@methods
|
9
|
+
end
|
10
|
+
def self.set_method k, v
|
11
|
+
@@methods[k] = v
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_added name
|
17
|
+
return if methods.include?(name)
|
18
|
+
puts "#{name} added"
|
19
|
+
set_method(name, instance_method(name))
|
20
|
+
class_eval %{
|
21
|
+
def #{name}(*args)
|
22
|
+
self.class.methods[#{name.inspect}].bind(self).call(*args)
|
23
|
+
end
|
24
|
+
}, __FILE__, __LINE__ + 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class NotWrapped
|
29
|
+
def add a, b
|
30
|
+
a + b
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Wrapped
|
35
|
+
extend ::Wrapper
|
36
|
+
def add a, b
|
37
|
+
a + b
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
w = Wrapped.new
|
42
|
+
nw = NotWrapped.new
|
43
|
+
# p w.add(1, 4)
|
44
|
+
# exit
|
45
|
+
# 30 is the width of the output column
|
46
|
+
Benchmark.bm 30 do |x|
|
47
|
+
x.report "wrapped" do
|
48
|
+
100_000.times do |_|
|
49
|
+
w.add(rand(1000), rand(1000))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
x.report "not wrapped" do
|
53
|
+
100_000.times do |_|
|
54
|
+
nw.add(rand(1000), rand(1000))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/contracts.gemspec
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.join(__FILE__, "../lib/contracts/version"))
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "contracts-lite"
|
5
|
+
s.version = Contracts::VERSION
|
6
|
+
s.summary = "Contracts for Ruby. (fork)"
|
7
|
+
s.description = "This library provides contracts for Ruby. Contracts let you clearly express how your code behaves, and free you from writing tons of boilerplate, defensive code."
|
8
|
+
s.author = "Aditya Bhargava"
|
9
|
+
s.email = "bluemangroupie@gmail.com"
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
s.homepage = "http://github.com/ddd-ruby/contracts.ruby"
|
12
|
+
s.license = "BSD-2-Clause"
|
13
|
+
end
|
data/lib/contracts.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
require "contracts/builtin_contracts"
|
2
|
+
require "contracts/decorators"
|
3
|
+
require "contracts/errors"
|
4
|
+
require "contracts/error_formatter"
|
5
|
+
require "contracts/formatters"
|
6
|
+
require "contracts/invariants"
|
7
|
+
require "contracts/method_reference"
|
8
|
+
require "contracts/support"
|
9
|
+
require "contracts/engine"
|
10
|
+
require "contracts/method_handler"
|
11
|
+
require "contracts/validators"
|
12
|
+
require "contracts/call_with"
|
13
|
+
require "contracts/core"
|
14
|
+
|
15
|
+
module Contracts
|
16
|
+
def self.included(base)
|
17
|
+
base.send(:include, Core)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.extended(base)
|
21
|
+
base.send(:extend, Core)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This is the main Contract class. When you write a new contract, you'll
|
26
|
+
# write it as:
|
27
|
+
#
|
28
|
+
# Contract [contract names] => return_value
|
29
|
+
#
|
30
|
+
# This class also provides useful callbacks and a validation method.
|
31
|
+
#
|
32
|
+
# For #make_validator and related logic see file
|
33
|
+
# lib/contracts/validators.rb
|
34
|
+
# For #call_with and related logic see file
|
35
|
+
# lib/contracts/call_with.rb
|
36
|
+
class Contract < Contracts::Decorator
|
37
|
+
extend Contracts::Validators
|
38
|
+
include Contracts::CallWith
|
39
|
+
|
40
|
+
# Default implementation of failure_callback. Provided as a block to be able
|
41
|
+
# to monkey patch #failure_callback only temporary and then switch it back.
|
42
|
+
# First important usage - for specs.
|
43
|
+
DEFAULT_FAILURE_CALLBACK = proc do |data|
|
44
|
+
if data[:return_value]
|
45
|
+
# this failed on the return contract
|
46
|
+
fail ReturnContractError.new(failure_msg(data), data)
|
47
|
+
else
|
48
|
+
# this failed for a param contract
|
49
|
+
fail data[:contracts].failure_exception.new(failure_msg(data), data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :args_contracts, :ret_contract, :klass, :method
|
54
|
+
def initialize(klass, method, *contracts)
|
55
|
+
unless contracts.last.is_a?(Hash)
|
56
|
+
unless contracts.one?
|
57
|
+
fail %{
|
58
|
+
It looks like your contract for #{method.name} doesn't have a return
|
59
|
+
value. A contract should be written as `Contract arg1, arg2 =>
|
60
|
+
return_value`.
|
61
|
+
}.strip
|
62
|
+
end
|
63
|
+
contracts = [nil => contracts[-1]]
|
64
|
+
end
|
65
|
+
|
66
|
+
# internally we just convert that return value syntax back to an array
|
67
|
+
@args_contracts = contracts[0, contracts.size - 1] + contracts[-1].keys
|
68
|
+
|
69
|
+
@ret_contract = contracts[-1].values[0]
|
70
|
+
|
71
|
+
@args_validators = args_contracts.map do |contract|
|
72
|
+
Contract.make_validator(contract)
|
73
|
+
end
|
74
|
+
|
75
|
+
@args_contract_index = args_contracts.index do |contract|
|
76
|
+
contract.is_a? Contracts::Args
|
77
|
+
end
|
78
|
+
|
79
|
+
@ret_validator = Contract.make_validator(ret_contract)
|
80
|
+
|
81
|
+
@pattern_match = false
|
82
|
+
|
83
|
+
# == @has_proc_contract
|
84
|
+
last_contract = args_contracts.last
|
85
|
+
is_a_proc = last_contract.is_a?(Class) && (last_contract <= Proc || last_contract <= Method)
|
86
|
+
maybe_a_proc = last_contract.is_a?(Contracts::Maybe) && last_contract.include_proc?
|
87
|
+
|
88
|
+
@has_proc_contract = is_a_proc || maybe_a_proc || last_contract.is_a?(Contracts::Func)
|
89
|
+
|
90
|
+
# ====
|
91
|
+
|
92
|
+
# == @has_options_contract
|
93
|
+
last_contract = args_contracts.last
|
94
|
+
penultimate_contract = args_contracts[-2]
|
95
|
+
@has_options_contract = if @has_proc_contract
|
96
|
+
penultimate_contract.is_a?(Hash) || penultimate_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
97
|
+
else
|
98
|
+
last_contract.is_a?(Hash) || last_contract.is_a?(Contracts::Builtin::KeywordArgs)
|
99
|
+
end
|
100
|
+
# ===
|
101
|
+
|
102
|
+
@klass, @method = klass, method
|
103
|
+
end
|
104
|
+
|
105
|
+
def pretty_contract c
|
106
|
+
c.is_a?(Class) ? c.name : c.class.name
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
args = args_contracts.map { |c| pretty_contract(c) }.join(", ")
|
111
|
+
ret = pretty_contract(ret_contract)
|
112
|
+
("#{args} => #{ret}").gsub("Contracts::Builtin::", "")
|
113
|
+
end
|
114
|
+
|
115
|
+
# Given a hash, prints out a failure message.
|
116
|
+
# This function is used by the default #failure_callback method
|
117
|
+
# and uses the hash passed into the failure_callback method.
|
118
|
+
def self.failure_msg(data)
|
119
|
+
Contracts::ErrorFormatters.failure_msg(data)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Callback for when a contract fails. By default it raises
|
123
|
+
# an error and prints detailed info about the contract that
|
124
|
+
# failed. You can also monkeypatch this callback to do whatever
|
125
|
+
# you want...log the error, send you an email, print an error
|
126
|
+
# message, etc.
|
127
|
+
#
|
128
|
+
# Example of monkeypatching:
|
129
|
+
#
|
130
|
+
# def Contract.failure_callback(data)
|
131
|
+
# puts "You had an error!"
|
132
|
+
# puts failure_msg(data)
|
133
|
+
# exit
|
134
|
+
# end
|
135
|
+
def self.failure_callback(data, use_pattern_matching = true)
|
136
|
+
if data[:contracts].pattern_match? && use_pattern_matching
|
137
|
+
return DEFAULT_FAILURE_CALLBACK.call(data)
|
138
|
+
end
|
139
|
+
|
140
|
+
fetch_failure_callback.call(data)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Used to override failure_callback without monkeypatching.
|
144
|
+
#
|
145
|
+
# Takes: block parameter, that should accept one argument - data.
|
146
|
+
#
|
147
|
+
# Example usage:
|
148
|
+
#
|
149
|
+
# Contract.override_failure_callback do |data|
|
150
|
+
# puts "You had an error"
|
151
|
+
# puts failure_msg(data)
|
152
|
+
# exit
|
153
|
+
# end
|
154
|
+
def self.override_failure_callback(&blk)
|
155
|
+
@failure_callback = blk
|
156
|
+
end
|
157
|
+
|
158
|
+
# Used to restore default failure callback
|
159
|
+
def self.restore_failure_callback
|
160
|
+
@failure_callback = DEFAULT_FAILURE_CALLBACK
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.fetch_failure_callback
|
164
|
+
@failure_callback ||= DEFAULT_FAILURE_CALLBACK
|
165
|
+
end
|
166
|
+
|
167
|
+
# Used to verify if an argument satisfies a contract.
|
168
|
+
#
|
169
|
+
# Takes: an argument and a contract.
|
170
|
+
#
|
171
|
+
# Returns: a tuple: [Boolean, metadata]. The boolean indicates
|
172
|
+
# whether the contract was valid or not. If it wasn't, metadata
|
173
|
+
# contains some useful information about the failure.
|
174
|
+
def self.valid?(arg, contract)
|
175
|
+
make_validator(contract)[arg]
|
176
|
+
end
|
177
|
+
|
178
|
+
def [](*args, &blk)
|
179
|
+
call(*args, &blk)
|
180
|
+
end
|
181
|
+
|
182
|
+
def call(*args, &blk)
|
183
|
+
call_with(nil, *args, &blk)
|
184
|
+
end
|
185
|
+
|
186
|
+
# if we specified a proc in the contract but didn't pass one in,
|
187
|
+
# it's possible we are going to pass in a block instead. So lets
|
188
|
+
# append a nil to the list of args just so it doesn't fail.
|
189
|
+
|
190
|
+
# a better way to handle this might be to take this into account
|
191
|
+
# before throwing a "mismatched # of args" error.
|
192
|
+
# returns true if it appended nil
|
193
|
+
def maybe_append_block! args, blk
|
194
|
+
return false unless @has_proc_contract && !blk &&
|
195
|
+
(@args_contract_index || args.size < args_contracts.size)
|
196
|
+
args << nil
|
197
|
+
true
|
198
|
+
end
|
199
|
+
|
200
|
+
# Same thing for when we have named params but didn't pass any in.
|
201
|
+
# returns true if it appended nil
|
202
|
+
def maybe_append_options! args, blk
|
203
|
+
return false unless @has_options_contract
|
204
|
+
if @has_proc_contract && (args_contracts[-2].is_a?(Hash) || args_contracts[-2].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-2].is_a?(Hash)
|
205
|
+
args.insert(-2, {})
|
206
|
+
elsif (args_contracts[-1].is_a?(Hash) || args_contracts[-1].is_a?(Contracts::Builtin::KeywordArgs)) && !args[-1].is_a?(Hash)
|
207
|
+
args << {}
|
208
|
+
end
|
209
|
+
true
|
210
|
+
end
|
211
|
+
|
212
|
+
# Used to determine type of failure exception this contract should raise in case of failure
|
213
|
+
def failure_exception
|
214
|
+
if pattern_match?
|
215
|
+
PatternMatchingError
|
216
|
+
else
|
217
|
+
ParamContractError
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# @private
|
222
|
+
# Used internally to mark contract as pattern matching contract
|
223
|
+
def pattern_match!
|
224
|
+
@pattern_match = true
|
225
|
+
end
|
226
|
+
|
227
|
+
# Used to determine if contract is a pattern matching contract
|
228
|
+
def pattern_match?
|
229
|
+
@pattern_match == true
|
230
|
+
end
|
231
|
+
end
|