Skip to content

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

Description

@ericproulx

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions