Skip to content

Commit

Permalink
feat: Pass through rest args and kwargs without checking
Browse files Browse the repository at this point in the history
  • Loading branch information
joelmoss committed May 30, 2024
1 parent a497287 commit 1d2f475
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 15 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ end
If an invalid argument is given to `User#create`, for example, if `age` is a `String` instead of
the required `Integer`, a `Delivered::ArgumentError` exception will be raised.

### Single and Double Splat Arguments

You can use single and double splats in your method signatures, and Delivered will pass them through
without checking, while still checking the other named positional and keyword arguments.

```ruby
sig String
def create(name, *args, foo:, **kwargs); end
```
### Return Types
You can also check the return value of the method by passing a Hash with an Array as the key, and
Expand Down
30 changes: 15 additions & 15 deletions lib/delivered/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
module Delivered
module Signature
def sig(*sig_args, **sig_kwargs, &return_blk)
# ap [sig_args, sig_kwargs, return_blk]

# Block return
returns = return_blk&.call

Expand All @@ -20,15 +18,13 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
sig_kwargs = sig_args.pop if sig_args.last.is_a?(Hash)
end

# ap [sig_args, sig_kwargs, returns]
# ap(sig_args:, sig_kwargs:)

meta = class << self; self; end
sig_check = lambda do |klass, class_method, name, *args, **kwargs, &block| # rubocop:disable Metrics/BlockLength
cname = if class_method
"#{klass.name}.#{name}"
else
"#{klass.class.name}##{name}"
end
# ap(args:, kwargs:, params: klass.method(:"__#{name}").parameters)

cname = class_method ? "#{klass.name}.#{name}" : "#{klass.class.name}##{name}"

sig_args.each.with_index do |arg, i|
args[i] => ^arg
Expand All @@ -39,13 +35,15 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
caller, cause: e
end

kwargs.each do |key, value|
value => ^(sig_kwargs[key])
rescue NoMatchingPatternError => e
raise Delivered::ArgumentError,
"`#{cname}` expected #{sig_kwargs[key].inspect} as keyword argument :#{key}, " \
"but received `#{value.inspect}`",
caller, cause: e
unless sig_kwargs.empty?
kwargs.each do |key, value|
value => ^(sig_kwargs[key])
rescue NoMatchingPatternError => e
raise Delivered::ArgumentError,
"`#{cname}` expected #{sig_kwargs[key].inspect} as keyword argument :#{key}, " \
"but received `#{value.inspect}`",
caller, cause: e
end
end

result = if block
Expand All @@ -66,6 +64,7 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
result
end

# Instance method redefinition
meta.send :define_method, :method_added do |name|
meta.send :remove_method, :method_added
meta.send :remove_method, :singleton_method_added
Expand All @@ -76,6 +75,7 @@ def sig(*sig_args, **sig_kwargs, &return_blk)
end
end

# Class method redefinition
meta.send :define_method, :singleton_method_added do |name|
next if name == :singleton_method_added

Expand Down
126 changes: 126 additions & 0 deletions test/delivered/signature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,132 @@ def self.find_by_name(name) = User.new(name)
expect(user.to_s).to be == 'Joel, 47'
end

with 'rest args' do
it 'no args given' do
class Name
extend Delivered::Signature

sig String
def rest(name, *attributes)
[name, attributes]
end
end

expect(Name.new.rest('Joel')).to be == ['Joel', []]
end

it 'args given' do
class Name
extend Delivered::Signature

sig String
def rest(name, *attributes)
[name, attributes]
end
end

expect(Name.new.rest('Joel', :foo)).to be == ['Joel', [:foo]]
end

it 'sig args defined' do
class Name
extend Delivered::Signature

sig String, String
def rest(name, last_name, *attributes)
[name, last_name, attributes]
end
end

expect(Name.new.rest('Joel', 'Moss', :foo)).to be == ['Joel', 'Moss', [:foo]]
end
end

with 'rest kwargs' do
it 'no kwargs given' do
class Name
extend Delivered::Signature

sig String
def rest(name, **attributes)
[name, attributes]
end
end

expect(Name.new.rest('Joel')).to be == ['Joel', {}]
end

it 'kwargs given' do
class Name
extend Delivered::Signature

sig String
def rest(name, **attributes)
[name, attributes]
end
end

expect(Name.new.rest('Joel', foo: :bar)).to be == ['Joel', { foo: :bar }]
end
end

with 'named and rest kwargs' do
it 'named kwarg given' do
class Name
extend Delivered::Signature

sig String
def rest(name, age:, **attributes)
[name, age:, **attributes]
end
end

expect(Name.new.rest('Joel', age: 47)).to be == ['Joel', { age: 47 }]
end

it 'named and rest kwarg given' do
class Name
extend Delivered::Signature

sig String
def rest(name, age:, **attributes)
[name, age:, **attributes]
end
end

expect(Name.new.rest('Joel', age: 47, town: 'Chorley'))
.to be == ['Joel', { age: 47, town: 'Chorley' }]
end
end

with 'rest args and kwargs' do
it 'no args given' do
class Name
extend Delivered::Signature

sig String
def rest(name, *args, **kwargs)
[name, args, kwargs]
end
end

expect(Name.new.rest('Joel')).to be == ['Joel', [], {}]
end

it 'args given' do
class Name
extend Delivered::Signature

sig String
def rest(name, *args, **kwargs)
[name, args, kwargs]
end
end

expect(Name.new.rest('Joel', :foo, bar: :foo)).to be == ['Joel', [:foo], { bar: :foo }]
end
end

it 'supports block' do
user = User.new('Joel', 47) { 'Hello' }
expect(user.blk.call).to be == 'Hello'
Expand Down

0 comments on commit 1d2f475

Please sign in to comment.