Skip to content

Require a formatter in error_formatter and drop the unreachable 406 throw - #2901

Merged
ericproulx merged 1 commit into
masterfrom
error-formatter-nil-guard
Sep 5, 2026
Merged

Require a formatter in error_formatter and drop the unreachable 406 throw#2901
ericproulx merged 1 commit into
masterfrom
error-formatter-nil-guard

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

error_formatter :json — with no formatter, positionally or as with: — registered nil:

def error_formatter(format, options = nil, with: nil)
  formatter = with || options   # nil
  inheritable_setting.add_error_formatter(format.to_sym, formatter)
end

Grape::ErrorFormatter.formatter_for hands a registered value back verbatim through its key? short-circuit, so that format resolved to no formatter at all and Middleware::Error#format_message fell through to a throw :error, 406.

That throw could never deliver its 406. Nothing catches :error around the call: #call! has already left its catch by the time error_response runs with the caught value, and run_rescue_handler re-renders outside its own catch. So it raised UncaughtThrowError, render_response rescued it, and the request answered with the failsafe 500 Internal Server Error in text/plain — whatever status and message the API had asked for:

path (format :json + error_formatter :json) response format_message calls uncaught throws
error!('nope', 422) 500 text/plain 2 2
validation failure 500 text/plain 3 2

Both ends are fixed here:

  • DSL::RequestResponse#error_formatter raises ArgumentError when no formatter is given, so the mistake surfaces where it is made rather than on the first error the API renders.
  • format_message drops the dead branch — formatter_for always resolves to a registered formatter, default_error_formatter or ErrorFormatter::Txt.

An API that passes a formatter is unaffected, and a format with no error_formatter of its own keeps falling back to default_error_formatter and then to Txt (pinned by a new middleware spec).

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the error-formatter-nil-guard branch from 87971ea to 5fa824e Compare September 4, 2026 21:37
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

…hrow

`error_formatter :json` — with no formatter, positionally or as `with:` —
registered `nil`. `ErrorFormatter.formatter_for` returns a registered value
verbatim through its `key?` short-circuit, so that format resolved to no
formatter at all, and `Middleware::Error#format_message` fell through to a
`throw :error, 406`.

That throw could never deliver its 406: nothing catches `:error` around the
call. `#call!` has already left its `catch` by the time `error_response` runs,
and `run_rescue_handler` renders outside its own. So it raised
`UncaughtThrowError`, `render_response` rescued it, and the request answered
with the failsafe `500 Internal Server Error` in `text/plain` instead of the
status and message the API asked for.

Raise `ArgumentError` from the DSL when no formatter is given, so the mistake
surfaces where it is made, and drop the dead branch — `formatter_for` always
resolves to a registered formatter, `default_error_formatter` or
`ErrorFormatter::Txt`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the error-formatter-nil-guard branch from 3ee9d16 to e006314 Compare September 5, 2026 09:27
@ericproulx
ericproulx merged commit 078be99 into master Sep 5, 2026
69 checks passed
@ericproulx
ericproulx deleted the error-formatter-nil-guard branch September 5, 2026 09:32
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.

2 participants