Skip to content

Pass Grape::Exceptions::ErrorResponse to error_formatter#call#2712

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

Pass Grape::Exceptions::ErrorResponse to error_formatter#call#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.

Middleware::Error#error_response already constructs a Grape::Exceptions::ErrorResponse value object — it then unpacks it into five separate positional/keyword arguments to hand to the formatter, which immediately repacks the pieces. Cut out the round-trip: pass the value object through.

New formatter signature

def call(error:, env: nil, include_backtrace: false, include_original_exception: false)
  • error: — a frozen Grape::Exceptions::ErrorResponse carrying status, message, headers, backtrace, original_exception. Built once by error_response (with defaults from default_status / default_message / the resolved content-type / fallback backtrace already applied) and threaded through unchanged.
  • env: — Rack env, still needed by present(message, env) for entity-presenter resolution.
  • include_backtrace: / include_original_exception: — request-time toggles forwarded from the matching rescue_from options. Renamed from the old rescue_options[:backtrace] / [:original_exception] Hash lookups; previously buried inside options[:rescue_options].

Internal plumbing

  • Middleware::Error#format_message now takes a single error arg and forwards it directly to the formatter. The unsupported-format throw path reads error.backtrace / error.original_exception off the same value object.
  • Middleware::Error#error_response builds the resolved ErrorResponse via raw.with(status: …, message: …, headers: …, backtrace: …) instead of plucking the five fields into separate locals.
  • Middleware::Error#error! wraps its positional args into an ErrorResponse before calling format_message.

Side cleanups (carried from earlier on the branch)

  • :rescue_options is now a Grape::DSL::RescueOptions value object (built on Data.define(:backtrace, :original_exception) with defaults) instead of a plain Hash. Middleware::Error exposes include_backtrace / include_original_exception via Forwardable.def_delegator :rescue_options, :backtrace, :include_backtrace (same for original_exception).
  • 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 RescueOptions instance.
  • 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.
  • Endpoint#build_stack reads the latest stacked RescueOptions directly (namespace_stackable[:rescue_options]&.last) instead of the hash-only namespace_stackable_with_hash.
  • Two options[:default_status] / options[:default_message] lookups in Middleware::Error#error_response replaced with the existing default_status / default_message attr_readers.

What this unlocks

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

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

Header-aware formatters work the same way:

error_formatter :txt, ->(error:, **) {
  "[#{error.status}] #{error.message} (#{error.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 with a per-field migration table covering each old positional arg → new accessor (messageerror.message, backtraceerror.backtrace, options[:rescue_options][:backtrace]include_backtrace, etc.).

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 — 2308 examples, 0 failures
  • RuboCop clean on touched files
  • New spec in spec/grape/api_spec.rb (with status and headers exposed (issue 2527)) verifies error.status and error.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 8 times, most recently from 238d183 to bc9faba Compare May 14, 2026 10:52
@ericproulx ericproulx marked this pull request as draft May 14, 2026 11:14
@ericproulx ericproulx force-pushed the feature/error-formatter-kwargs-2527 branch 3 times, most recently from 92dd0aa to da0cf09 Compare May 14, 2026 17:18
@ericproulx ericproulx changed the title Switch error_formatter to keyword arguments; expose status and headers Pass Grape::Exceptions::ErrorResponse to error_formatter#call May 14, 2026
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 da0cf09 to 45bd7be Compare May 14, 2026 17:20
@ericproulx ericproulx marked this pull request as ready for review May 14, 2026 17:26
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