Skip to content

Switch error_formatter to keyword arguments; expose status and headers#2712

Draft
ericproulx wants to merge 1 commit into
masterfrom
feature/error-formatter-kwargs-2527
Draft

Switch error_formatter to keyword arguments; expose status and headers#2712
ericproulx wants to merge 1 commit into
masterfrom
feature/error-formatter-kwargs-2527

Conversation

@ericproulx
Copy link
Copy Markdown
Contributor

@ericproulx ericproulx commented May 13, 2026

Summary

Resolves #2527.

The error_formatter contract previously took five positional arguments and there was no way to expose the HTTP status (or the response headers) to a custom formatter without a workaround through env[Grape::Env::API_ENDPOINT].status. The thread on #2527 (with 👍 from @dblock) converged on switching to keyword arguments so additional context can be threaded through without re-breaking the signature every time.

New signature in Grape::ErrorFormatter::Base:

def call(message:, backtrace: [], include_backtrace: false, env: nil, status: nil, headers: {},
         original_exception: nil, include_original_exception: false)

Only message: is required; everything else has a sensible default (matching the old positional signature for the pre-existing fields and nil/false/{} for the new ones), so future kwargs added on the call site won't break overrides.

Two design choices worth calling out:

  • No options: kwarg. The previous positional contract leaked the entire middleware-options hash. The body only ever read options.dig(:rescue_options, :backtrace|:original_exception) — both of which are include this in the response body? booleans, not the actual values. Replaced with two flat kwargs (include_backtrace:, include_original_exception:) that read naturally next to backtrace: (the Array) and original_exception: (the Exception). The other middleware-options keys (default_status, format, rescue_handlers, …) were framework-internal and were never part of the documented contract.
  • status: and headers: are first-class. Call sites in Middleware::Error#error_response and #error! pass the resolved status and the merged response headers (already containing the resolved Content-Type). Both were previously only reachable via env[Grape::Env::API_ENDPOINT].status and friends.

Side cleanups in Middleware::Error and DSL::RequestResponse

While in the file:

  • :rescue_options is now memoized in Middleware::Error#initialize and exposed via attr_reader like every other option (default_status, default_message, …); the request-time path no longer reads options[:rescue_options] and format_message no longer needs the per-request || {} fallback.
  • Two stragglers inside error_response (options[:default_status], options[:default_message]) replaced with the corresponding attr readers.
  • DSL::RequestResponse#rescue_from switched from **options to explicit kwargs (with:, rescue_subclasses: true, backtrace: false, original_exception: false); :rescue_subclasses defaults to true so the nil-treated-as-true case-when collapses to a one-liner. The stackable now stores a contract-shaped { backtrace:, original_exception: } rather than the raw kwargs blob.
  • rescue_from YARD now documents original_exception:, which has been a real (but undocumented) option since Bubble up to the error_formatter the original exception and the backtrace #1652.

What this unlocks

JSON:API-style error responses become a one-liner:

error_formatter :json, ->(message:, status:, **) {
  { errors: [{ status: status.to_s, detail: message }] }.to_json
}

Header-aware formatters work the same way:

error_formatter :txt, ->(message:, status:, headers:, **) {
  "[#{status}] #{message} (#{headers['x-marker']})"
}

Breaking change

Custom formatters that override call with the old positional signature will break. UPGRADING.md has a before/after section under ### Upgrading to >= 3.3 covering both the new kwarg names and the rename from options[:rescue_options][:backtrace]include_backtrace: (and the equivalent for :original_exception).

Built-in formatters (Json, Txt, Xml, SerializableHash) are unaffected — they only override format_structured_message, not call. Existing tests in spec/grape/api_spec.rb, spec/grape/middleware/exception_spec.rb, and spec/grape/dsl/request_response_spec.rb have been migrated.

Test plan

  • Full suite: bundle exec rspec — 2293 examples, 0 failures
  • RuboCop clean on touched files
  • New spec in spec/grape/api_spec.rb (with status and headers exposed (issue 2527)) verifying status: and headers: reach a custom formatter
  • CI green across Gemfile variants

🤖 Generated with Claude Code

@ericproulx ericproulx force-pushed the feature/error-formatter-kwargs-2527 branch from a299398 to 835aed6 Compare May 13, 2026 20:35
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

Danger Report

No issues found.

View run

@ericproulx ericproulx force-pushed the feature/error-formatter-kwargs-2527 branch from 835aed6 to 6113acc Compare May 13, 2026 20:38
@ericproulx ericproulx requested a review from dblock May 13, 2026 20:38
@ericproulx ericproulx force-pushed the feature/error-formatter-kwargs-2527 branch 7 times, most recently from 6af34ba to 238d183 Compare May 14, 2026 10:50
The error_formatter contract previously took five positional arguments —
`(message, backtrace, options, env, original_exception)` — which made it
impossible to thread additional context through to custom formatters
without breaking every existing override. Switch to keyword arguments and
add two fields that have been asked for in #2527:

    def call(message:, backtrace:, options:, env:,
             status:, headers:, original_exception:)

`status:` makes JSON:API-style error bodies straightforward (the spec
embeds the HTTP status code in the response). `headers:` lets formatters
react to the response content-type or trace the marker headers set by
`error!`. Both were previously only reachable via
`env[Grape::Env::API_ENDPOINT].status` and friends.

This is a contract break — existing custom formatters that re-declare
`call` with the positional signature will need to migrate. See
UPGRADING.md for the before/after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ericproulx ericproulx force-pushed the feature/error-formatter-kwargs-2527 branch from 238d183 to bc9faba Compare May 14, 2026 10:52
@ericproulx ericproulx marked this pull request as draft May 14, 2026 11:14
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.

Was it intentional to hide the http response status from the error formatter?

1 participant