Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
* [#2726](https://github.com/ruby-grape/grape/pull/2726): Reuse one `AttributesIterator` per validator and drop the unused `Enumerable` mixin - [@ericproulx](https://github.com/ericproulx).
* [#2728](https://github.com/ruby-grape/grape/pull/2728): Deprecate passing a positional options Hash to `auth`/`http_basic`/`http_digest`; pass keyword arguments instead - [@ericproulx](https://github.com/ericproulx).
* [#2733](https://github.com/ruby-grape/grape/pull/2733): Drop the dead `active_support/core_ext/hash/reverse_merge` require; call `ActiveSupport::HashWithIndifferentAccess.new(...)` directly at call sites - [@ericproulx](https://github.com/ericproulx).
* [#2737](https://github.com/ruby-grape/grape/pull/2737): `rescue_from` raises `ArgumentError` when a meta selector (`:all`, `:grape_exceptions`, `:internal_grape_exceptions`) is mixed with exception classes instead of silently dropping the classes - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes
Expand Down
16 changes: 16 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ Upgrading Grape

### Upgrading to >= 3.3

#### `rescue_from` rejects meta selectors mixed with exception classes

`rescue_from` used to silently drop additional exception classes when its first argument was a meta selector (`:all`, `:grape_exceptions`, `:internal_grape_exceptions`). It now raises `ArgumentError` so the misuse is caught at definition time:

```ruby
# previously: MyError was silently dropped — only :all took effect
rescue_from :all, MyError, with: :handler

# now: ArgumentError ("rescue_from :all does not accept additional arguments")
# split into two declarations instead:
rescue_from :all, with: :handler
rescue_from MyError, with: :other_handler
```

Calls that only use one meta selector or only use exception classes (the documented forms) are unaffected.

#### `auth`, `http_basic` and `http_digest` now take keyword arguments

`Grape::Middleware::Auth::DSL#auth`, `#http_basic` and `#http_digest` now accept their options as keyword arguments instead of a positional `Hash`. Calls using bare keyword syntax or a block are unaffected:
Expand Down
38 changes: 27 additions & 11 deletions lib/grape/dsl/request_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,14 @@ def default_error_status(new_status = nil)
# rescue_from CustomError
# end
#
META_RESCUE_SELECTORS = %i[all grape_exceptions internal_grape_exceptions].freeze
private_constant :META_RESCUE_SELECTORS

# @overload rescue_from(*exception_classes, **options)
# @param [Array] exception_classes A list of classes that you want to rescue, or
# the symbol :all to rescue from all exceptions.
# one of the meta selectors +:all+, +:grape_exceptions+,
# +:internal_grape_exceptions+. Meta selectors must be used alone;
# mixing with exception classes raises +ArgumentError+.
# @param [Block] block Execution block to handle the given exception.
# @param [Proc] with Execution proc to handle the given exception as an alternative
# to passing a block.
Expand All @@ -93,19 +98,30 @@ def default_error_status(new_status = nil)
# in the rescue response body.
def rescue_from(*args, with: nil, rescue_subclasses: true, backtrace: false, original_exception: false, &block)
handler = extract_handler(args, with:, block:)
meta_selector = (args & META_RESCUE_SELECTORS).first
raise ArgumentError, "rescue_from #{meta_selector.inspect} does not accept additional arguments" if meta_selector && args.size > 1

namespace_inheritable = nil
arg = nil

if args.one?
arg = args.first
namespace_inheritable = inheritable_setting.namespace_inheritable
end

if args.include?(:all)
inheritable_setting.namespace_inheritable[:rescue_all] = true
inheritable_setting.namespace_inheritable[:all_rescue_handler] = handler
elsif args.include?(:grape_exceptions)
inheritable_setting.namespace_inheritable[:rescue_all] = true
inheritable_setting.namespace_inheritable[:rescue_grape_exceptions] = true
inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler] = handler
elsif args.include?(:internal_grape_exceptions)
inheritable_setting.namespace_inheritable[:internal_grape_exceptions_rescue_handler] = handler
case arg
when :all
namespace_inheritable[:rescue_all] = true
namespace_inheritable[:all_rescue_handler] = handler
when :grape_exceptions
namespace_inheritable[:rescue_all] = true
namespace_inheritable[:rescue_grape_exceptions] = true
namespace_inheritable[:grape_exceptions_rescue_handler] = handler
when :internal_grape_exceptions
namespace_inheritable[:internal_grape_exceptions_rescue_handler] = handler
else
handler_type = rescue_subclasses ? :rescue_handlers : :base_only_rescue_handlers
inheritable_setting.namespace_reverse_stackable[handler_type] = args.to_h { |arg| [arg, handler] }
inheritable_setting.namespace_reverse_stackable[handler_type] = args.to_h { |klass| [klass, handler] }
end

inheritable_setting.namespace_stackable[:rescue_options] = RescueOptions.new(backtrace:, original_exception:)
Expand Down
17 changes: 17 additions & 0 deletions spec/grape/dsl/request_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@
end
end

describe 'meta selector mixed with exception classes' do
it 'raises ArgumentError for :all + exception class' do
expect { subject.rescue_from :all, StandardError }
.to raise_error(ArgumentError, 'rescue_from :all does not accept additional arguments')
end

it 'raises ArgumentError for :grape_exceptions + exception class' do
expect { subject.rescue_from :grape_exceptions, StandardError }
.to raise_error(ArgumentError, 'rescue_from :grape_exceptions does not accept additional arguments')
end

it 'raises ArgumentError for :internal_grape_exceptions + exception class' do
expect { subject.rescue_from :internal_grape_exceptions, StandardError }
.to raise_error(ArgumentError, 'rescue_from :internal_grape_exceptions does not accept additional arguments')
end
end

describe 'list of exceptions is passed' do
let(:default_rescue_options) { [Grape::DSL::RescueOptions.new] }

Expand Down
Loading