Skip to content

Commit b2a3cf1

Browse files
committed
feat: Type failures now raise a Delivered::ArgumentError exception
fixes #1
1 parent 636ab2e commit b2a3cf1

File tree

4 files changed

+49
-23
lines changed

4 files changed

+49
-23
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Delivered: Simple runtime type checking for Ruby method signatures
22

3+
> Signed, Sealed, Delivered 🎹
4+
35
Delivered gives you the ability to define method signatures in Ruby, and have them checked at
46
runtime. This is useful for ensuring that your methods are being called with the correct arguments,
57
and for providing better error messages when they are not. It also serves as a nice way of
@@ -22,7 +24,7 @@ end
2224
```
2325

2426
If an invalid argument is given to `User#create`, for example, if `age` is a `String` instead of
25-
the required `Integer`, a `NoMatchingPatternError` exception will be raised.
27+
the required `Integer`, a `Delivered::ArgumentError` exception will be raised.
2628

2729
### Return Types
2830

lib/delivered.rb

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# frozen_string_literal: true
22

33
module Delivered
4+
class ArgumentError < ArgumentError
5+
end
6+
47
autoload :Signature, 'delivered/signature'
58
autoload :Types, 'delivered/types'
69
end

lib/delivered/signature.rb

+30-9
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,41 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
1111
# Hashrocket return
1212
if sig_kwargs.keys[0].is_a?(Array)
1313
unless returns.nil?
14-
raise ArgumentError, 'Cannot mix block and hash for return type. Use one or the other.'
14+
raise Delivered::ArgumentError,
15+
'Cannot mix block and hash for return type. Use one or the other.', caller
1516
end
1617

1718
returns = sig_kwargs.values[0]
1819
sig_args = sig_kwargs.keys[0]
19-
sig_kwargs = sig_args.pop
20+
sig_kwargs = sig_args.pop if sig_args.last.is_a?(Hash)
2021
end
2122

22-
# ap sig_args
23-
# ap sig_kwargs
24-
# ap returns
23+
# ap [sig_args, sig_kwargs, returns]
2524

2625
meta = class << self; self; end
27-
sig_check = lambda do |klass, name, *args, **kwargs, &block|
26+
sig_check = lambda do |klass, class_method, name, *args, **kwargs, &block|
27+
cname = if class_method
28+
"#{klass.name}.#{name}"
29+
else
30+
"#{klass.class.name}##{name}"
31+
end
32+
2833
sig_args.each.with_index do |arg, i|
2934
args[i] => ^arg
35+
rescue NoMatchingPatternError => e
36+
raise Delivered::ArgumentError,
37+
"`#{cname}` expected `#{arg}` as positional arg #{i}, but received " \
38+
"`#{args[i].inspect}`",
39+
caller, cause: e
3040
end
3141

3242
kwargs.each do |key, value|
3343
value => ^(sig_kwargs[key])
44+
rescue NoMatchingPatternError => e
45+
raise Delivered::ArgumentError,
46+
"`#{cname}` expected `#{sig_kwargs[key]}` as keyword arg :#{key}, but received " \
47+
"`#{value.inspect}`",
48+
caller, cause: e
3449
end
3550

3651
result = if block
@@ -39,7 +54,13 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
3954
klass.send(:"__#{name}", *args, **kwargs)
4055
end
4156

42-
result => ^returns unless returns.nil?
57+
begin
58+
result => ^returns unless returns.nil?
59+
rescue NoMatchingPatternError => e
60+
raise Delivered::ArgumentError,
61+
"`#{cname}` expected to return `#{returns}`, but returned `#{result.inspect}`",
62+
caller, cause: e
63+
end
4364

4465
result
4566
end
@@ -50,7 +71,7 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
5071

5172
alias_method :"__#{name}", name
5273
define_method name do |*args, **kwargs, &block|
53-
sig_check.call(self, name, *args, **kwargs, &block)
74+
sig_check.call(self, false, name, *args, **kwargs, &block)
5475
end
5576
end
5677

@@ -62,7 +83,7 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
6283

6384
meta.alias_method :"__#{name}", name
6485
define_singleton_method name do |*args, **kwargs, &block|
65-
sig_check.call(self, name, *args, **kwargs, &block)
86+
sig_check.call(self, true, name, *args, **kwargs, &block)
6687
end
6788
end
6889
end

test/delivered/signature.rb

+13-13
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ def self.find_by_name(name) = User.new(name)
5353

5454
it 'raises on incorrect types' do
5555
user = User.new('Joel')
56-
expect { user.with_hash_return }.to raise_exception NoMatchingPatternError
56+
expect { user.with_hash_return }.to raise_exception Delivered::ArgumentError
5757
end
5858

5959
it 'raises on incorrect return type' do
6060
user = User.new('Joel')
61-
expect { user.with_incorrect_hash_return }.to raise_exception NoMatchingPatternError
61+
expect { user.with_incorrect_hash_return }.to raise_exception Delivered::ArgumentError
6262
end
6363
end
6464

@@ -70,12 +70,12 @@ def self.find_by_name(name) = User.new(name)
7070

7171
it 'raises on incorrect types' do
7272
user = User.new('Joel')
73-
expect { user.with_block_return }.to raise_exception NoMatchingPatternError
73+
expect { user.with_block_return }.to raise_exception Delivered::ArgumentError
7474
end
7575

7676
it 'raises on incorrect return type' do
7777
user = User.new('Joel')
78-
expect { user.with_incorrect_block_return }.to raise_exception NoMatchingPatternError
78+
expect { user.with_incorrect_block_return }.to raise_exception Delivered::ArgumentError
7979
end
8080
end
8181

@@ -105,29 +105,29 @@ def self.find_by_name(name) = User.new(name)
105105

106106
it 'raise on incorrect type' do
107107
user = User.new('Joel', 47)
108-
expect { user.age = '47' }.to raise_exception NoMatchingPatternError
108+
expect { user.age = '47' }.to raise_exception Delivered::ArgumentError
109109
end
110110
end
111111

112112
it 'raises on incorrect Delivered type' do
113113
user = User.new('Joel')
114-
expect { user.active = 1 }.to raise_exception NoMatchingPatternError
114+
expect { user.active = 1 }.to raise_exception Delivered::ArgumentError
115115
end
116116

117117
it 'raises on missing args' do
118-
expect { User.new }.to raise_exception NoMatchingPatternError
118+
expect { User.new }.to raise_exception Delivered::ArgumentError
119119
end
120120

121121
it 'raises on incorrect arg type' do
122-
expect { User.new 1 }.to raise_exception NoMatchingPatternError
122+
expect { User.new 1 }.to raise_exception Delivered::ArgumentError
123123
end
124124

125125
it 'supports keyword args' do
126126
expect(User.new('Joel', 47, town: 'Chorley').town).to be == 'Chorley'
127127
end
128128

129129
it 'raises on incorrect kwarg type' do
130-
expect { User.new('Joel', town: 1) }.to raise_exception NoMatchingPatternError
130+
expect { User.new('Joel', town: 1) }.to raise_exception Delivered::ArgumentError
131131
end
132132

133133
with 'class methods' do
@@ -136,23 +136,23 @@ def self.find_by_name(name) = User.new(name)
136136
end
137137

138138
it 'raises on incorrect return type' do
139-
expect { User.find_by_name('Hugo') }.to raise_exception NoMatchingPatternError
139+
expect { User.find_by_name('Hugo') }.to raise_exception Delivered::ArgumentError
140140
end
141141

142142
it 'raises on missing args' do
143-
expect { User.where }.to raise_exception NoMatchingPatternError
143+
expect { User.where }.to raise_exception Delivered::ArgumentError
144144
end
145145

146146
it 'raises on incorrect arg type' do
147-
expect { User.where(1) }.to raise_exception NoMatchingPatternError
147+
expect { User.where(1) }.to raise_exception Delivered::ArgumentError
148148
end
149149

150150
it 'supports keyword args' do
151151
expect(User.where('hugo', _age: 27)).to be == []
152152
end
153153

154154
it 'raises on incorrect kwarg type' do
155-
expect { User.where(1, _age: 'twentyseven') }.to raise_exception NoMatchingPatternError
155+
expect { User.where(1, _age: 'twentyseven') }.to raise_exception Delivered::ArgumentError
156156
end
157157
end
158158
end

0 commit comments

Comments
 (0)