Skip to content

Warn when a rescue_from handler can never run - #2842

Open
ericproulx wants to merge 1 commit into
masterfrom
fix/warn-shadowed-rescue-from
Open

Warn when a rescue_from handler can never run#2842
ericproulx wants to merge 1 commit into
masterfrom
fix/warn-shadowed-rescue-from

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

Summary

Grape::Middleware::Error resolves a registered handler with #find, so within a scope the first matching class wins. A handler registered for a class an earlier one already covers is therefore dead code, and silently so:

rescue_from StandardError do ... end   # wins
rescue_from ArgumentError do ... end   # never runs

Swapping the two lines is all it takes, but nothing said so — and the neighbouring rescue_from :all behaves the other way round, since it lives in all_rescue_handler and is consulted only after the registered handlers:

declaration raising ArgumentError
rescue_from :all then rescue_from ArgumentError ArgumentError handler ✅
rescue_from StandardError then rescue_from ArgumentError StandardError handler

Two documented ways of saying "catch everything", ordering differently against a later specific handler — while the README describes rescue_from :all as rescuing "all StandardError exceptions".

Approach

Warn at definition time rather than reorder. Which handler should win is the author's call, and :all already offers "broad first, specific still wins" to anyone who wants it. No behaviour changes.

Grape: rescue_from ArgumentError will never run — StandardError was registered earlier
in the same scope and is matched first. Register the more specific class before the broader one.

Deliberately quiet in the cases that are not mistakes:

case warns?
broader class registered first
same class registered twice (first handler is kept) ✅ (own message)
narrower class registered first
unrelated classes
rescue_from A, B sharing one handler
same handler via with: on both
rescue_subclasses: false pairs
inner scope shadowing an enclosing one
rescue_from :all then a specific class

Across scopes the nearest registration deliberately wins, so an inner rescue_from StandardError shadowing an outer rescue_from ArgumentError is documented behaviour, not a mistake. Exact-match handlers are consulted before the subclass-matching ones and never match a descendant, so they cannot shadow each other either.

The check lives in Grape::Util::ShadowedRescueHandlers, called from #add_rescue_handlers where a scope's own registrations are known — kept out of InheritableSetting itself, which is at its Metrics/ClassLength limit and is a settings store rather than a place for DSL diagnostics.

README gains the ordering rule, which was only implied.

Test plan

  • 7 new examples in inheritable_setting_spec.rb covering both warning messages and the five quiet cases; verified the two warning ones fail without the lib/ change.
  • Full RSpec suite passes locally (2550 examples, 0 failures) — no existing spec registers a shadowed handler, so no new noise.
  • RuboCop clean.
  • CI green.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the fix/warn-shadowed-rescue-from branch from a4cead1 to 3953456 Compare August 1, 2026 10:01
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

Grape::Middleware::Error resolves a registered handler with #find, so within a
scope the first matching class wins. A handler registered for a class an
earlier one already covers is therefore dead code, and silently so:

    rescue_from StandardError do ... end   # wins
    rescue_from ArgumentError do ... end   # never runs

Swapping the two lines is all it takes, but nothing said so -- and the
neighbouring `rescue_from :all` behaves the other way round, since it is
consulted only after the registered handlers, so a specific class registered
after it still wins. Two documented ways of saying "catch everything",
ordering differently against a later specific handler.

Warn at definition time rather than reorder: which handler should win is the
author's call, and :all already covers "broad first, specific still wins".
The check lives in Grape::Util::ShadowedRescueHandlers, called from
#add_rescue_handlers where the scope's own registrations are known.

Compared within a scope only -- across scopes the nearest registration
deliberately wins, so an inner rescue_from StandardError shadowing an outer
rescue_from ArgumentError is the documented behaviour, not a mistake. Classes
sharing a handler object are skipped too, since `rescue_from A, B` registers
one handler for both and the entry that loses changes nothing. Exact-match
handlers (rescue_subclasses: false) are consulted before the subclass-matching
ones and never match a descendant, so they cannot shadow each other either.

README gains the ordering rule, which was only implied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the fix/warn-shadowed-rescue-from branch from 3953456 to 8f6202a Compare August 1, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant