Make ErrorFormatter.formatter_for a lookup and move the fallback to its caller - #2902
Merged
ericproulx merged 1 commit intoSep 5, 2026
Merged
Conversation
ericproulx
force-pushed
the
error-formatter-registry-lookup
branch
from
September 4, 2026 21:47
ffe373c to
72a75de
Compare
Danger ReportNo issues found. |
ericproulx
force-pushed
the
error-formatter-registry-lookup
branch
from
September 4, 2026 21:52
72a75de to
71aa77d
Compare
ericproulx
force-pushed
the
error-formatter-registry-lookup
branch
from
September 4, 2026 22:09
71aa77d to
c9da551
Compare
…ts caller `formatter_for` took the API's `default_error_formatter` as a third argument and ended in `default_error_formatter || Grape::ErrorFormatter::Txt`, so the global registry implemented the caller's precedence with the caller's own state. It was the only registry that did: `Parser.parser_for` answers nil and lets the caller decide, `Formatter.formatter_for` falls back to a constant of its own, and `ParamsBuilder.params_builder_for` raises. Give it the `Parser.parser_for` shape — API registrations, then the registry, then nil — and let `Middleware::Error` apply the fallback it owns, defaulting `default_error_formatter` to `ErrorFormatter::Txt` in its `Options` alongside `rescue_options` (both arrive as an explicit nil from `Endpoint#error_middleware_options`, which bypasses the keyword defaults). Resolution is unchanged, precedence included: a formatter registered for the requested format still wins over the API's `default_error_formatter`, which applies to formats that have none of their own. Reporting the miss also lets the DSL see it. `default_error_formatter` names an error formatter, so a name nothing is registered under now raises `Grape::Exceptions::UnknownErrorFormatter` rather than storing the `Txt` the lookup used to substitute, which read back as a working setting. `format` names a format, where having no error formatter of its own is ordinary, so it still succeeds and stores nil, with the middleware supplying `Txt` at render time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ericproulx
force-pushed
the
error-formatter-registry-lookup
branch
from
September 5, 2026 09:29
c9da551 to
49a3a0d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2901 — review that one first; this PR's base is
error-formatter-nil-guardand I'll retarget it tomasteronce #2901 merges.ErrorFormatter.formatter_fortook the API'sdefault_error_formatteras a third argument and ended indefault_error_formatter || Grape::ErrorFormatter::Txt, so the global registry implemented the caller's precedence out of the caller's own state. It was the only registry in the family that did:Parser.parser_for(format, parsers = nil)nil— caller decidesParamsBuilder.params_builder_for(short_name)Formatter.formatter_for(api_format, formatters)DEFAULT_LAMBDA_FORMATTER(its own constant)ErrorFormatter.formatter_for(format, error_formatters, default_error_formatter)default_error_formatter || Txterror_formattersis at least a table it can search;default_error_formatteris API policy threaded through a global module. The|| Txtalone would be fine —Formatterdoes the same — but it cannot stay once the other leaves: a caller can only insert its own default between "nothing registered" andTxtif the lookup reports the miss.So
formatter_fortakes theParser.parser_forshape, andMiddleware::Errorapplies the fallback it owns:Options#initializeis the right home for the default becauseEndpoint#error_middleware_optionspasses an explicitnilwhen the API called noformat, which bypasses theDatakeyword default — the same reasonrescue_options ||=is already there.Resolution is unchanged, precedence included: a formatter registered for the requested format still wins over the API's
default_error_formatter, which applies to formats with none of their own (format == nilincluded — that is what makes thedefault_error_formatter :jsonspec pass). Both directions are now pinned by specs inerror_spec.rb, and a newspec/grape/error_formatter_spec.rbpins the lookup contract, mirroringparser_spec.rb.Reporting the miss also lets the DSL see it
default_error_formatternames an error formatter, so a name nothing is registered under is a mistake. It used to store theTxtthe lookup substituted, so the typo read back as a working setting and errors quietly rendered as text:formatnames a format, where having no error formatter of its own is ordinary (format :xls), so it still succeeds and storesnil, with the middleware supplyingTxtat render time.Both are in UPGRADING, along with the signature change: a third argument to
formatter_fornow raisesArgumentError, and the setting reads backnilinstead ofTxtfor a format with no error formatter — visible only if you read it back, since error responses are unchanged.🤖 Generated with Claude Code