ruby-grape / ruby-grape/grape-swagger

Four route reads have no reader on Grape's side: success, failure, default_response, desc

Open
#987 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Ruby
Stars
1.1k
Forks
479
Avg merge
2d 14h
Merged PRs (30d)
6

Description

Four of the route reads in lib/ resolve to no method Grape defines. They work only because Grape::Router::BaseRoute forwards unknown names to @options with delegate_missing_to :@options:

read site option key reader on BaseRoute?
route.success endpoint.rb:103, endpoint.rb:159 :success no
route.failure endpoint.rb:238 :failure no
route.default_response endpoint.rb:266 :default_response no
route.desc endpoint.rb:143 :desc no

Audited by checking every route.<name> read in lib/ against Grape::Router::BaseRoute.method_defined?. The other eighteen — tags, hidden, entity, http_codes, summary, detail, settings, security, deprecated, produces, … — are genuine attr_readers, @pattern delegates, or def_delegators over Grape::Util::ApiDescription::DSL_METHODS. Only these four are method_missing.

Verified on grape 3.3.5:

Grape::Router::BaseRoute.method_defined?(:success)                # => false
Grape::Router::BaseRoute.method_defined?(:failure)                # => false
Grape::Router::BaseRoute.method_defined?(:default_response)       # => false
Grape::Router::BaseRoute.method_defined?(:desc)                   # => false
Grape::Util::ApiDescription::DSL_METHODS.include?(:success)       # => false

Nothing is on fire: delegate_missing_to is present in every released Grape, so all four read correctly today. But they are reads of an option bag dressed as reads of an API, and they are the reason grape-swagger cannot be made independent of that forwarding.

success and failure are the sharp ones

These two are not merely undefined — they mean different things depending on which desc form the caller used, and grape-swagger reads only one of the two spellings.

Grape::Util::ApiDescription aliases them in the block form:

alias success entity
alias failure http_codes

A Hash of options never reaches ApiDescription, so the literal keys survive unaliased. Both forms, same API, grape 3.3.5:

declaration stored keys route.entity route.success route.http_codes route.failure
desc 'a', success: X, failure: Y :success, :failure nil X nil Y
desc 'b' do success X; failure Y end :entity, :http_codes X nil Y nil

So there is no single reader that answers "what did the user declare as the success entity" — route.entity is blind to the Hash form and route.success is blind to the block form. grape-swagger papers over this by reading both and taking whichever is non-nil (route.entity || route.success at endpoint.rb:103, route.http_codes || route.failure at endpoint.rb:238), which is correct but only because method_missing is there to answer the second half.

This also means the obvious cleanup — "just use route.entity" — is wrong today. It would silently drop success: for every user of the Hash form, which is the form the README teaches throughout.

default_response has no spelling that works anywhere

Unlike success/failure, default_response has no alias, no DSL_METHODS entry, and no block-form equivalent. It exists only as a Hash key that Grape passes through untouched, and method_missing is the only thing that has ever read it back.

It also arrived at the reader form recently and by accident: #868 added it as route.options[:default_response], and #984 converted that line to route.default_response while sweeping 19 route.options reads down to 1. Every other line that sweep touched had a real delegator behind it.

And the README documents a different option

README.md:1124 ("Default response", linked from the TOC) documents the option as default::

desc 'thing', default: { message: 'the default response' }

but the code has only ever read :default_response. #868 introduced both spellings in the same commit — route.options[:default_response] in lib, default: in the README — and nothing has reconciled them since. End to end through add_swagger_documentation:

desc 'readme form', default: { message: 'the default response' }           => responses: ["200"]
desc 'code form',   default_response: { message: 'the default response' }  => responses: ["200", "default"]

The documented spelling silently produces no default response. The spelling that works appears nowhere in the docs — only in spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb:19 and api_swagger_v2_response_with_models_and_primitive_types_spec.rb.

default is in DSL_METHODS, so route.default would be a real reader: the README happens to describe the version of this feature that needs no forwarding, and the code implements the one that does.

Prior art

ruby-grape/grape#2857 proposed dropping delegate_missing_to :@options from BaseRoute — it identified these same four names as grape-swagger's entire exposure, and proposed normalizing the Hash form inside desc so success:/failure: would store :entity/:http_codes like the block form. It was closed on 2026-08-22 without being merged, so none of that normalization exists and there is no deadline attached to this issue. It is recorded here because the normalization half is the piece worth reviving on its own, independently of whether the forwarding is ever removed.

Constraint on the fix

Falling back to route.options[:success] and friends is not the answer here. #986 removed the last route.options read from lib/, and reaching back into the raw bag would undo that on purpose rather than by oversight: it keeps grape-swagger coupled to Grape's internal storage instead of its API, and it preserves the dual-spelling ambiguity rather than resolving it. Whatever the fix turns out to be, it has to land each of the four reads on a reader — one that already exists, or one Grape would define.

Environment
  • grape-swagger master (2.3.0 development)
  • grape 3.3.5
  • Ruby 4.0.5

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the four reads listed in lib/grape-swagger/endpoint.rb and inspect Grape::Router::BaseRoute and Grape::Util::ApiDescription for existing readers and aliases. Run the response specs named in the issue, then verify both Hash and block desc forms retain their success/failure behavior and that the documented default spelling matches the implemented behavior. Done means the four reads no longer depend on accidental method_missing forwarding.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
api, documentation
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.