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
Four of the route reads in
lib/resolve to no method Grape defines. They work only becauseGrape::Router::BaseRouteforwards unknown names to@optionswithdelegate_missing_to :@options:BaseRoute?route.successendpoint.rb:103,endpoint.rb:159:successroute.failureendpoint.rb:238:failureroute.default_responseendpoint.rb:266:default_responseroute.descendpoint.rb:143:descAudited by checking every
route.<name>read inlib/againstGrape::Router::BaseRoute.method_defined?. The other eighteen —tags,hidden,entity,http_codes,summary,detail,settings,security,deprecated,produces, … — are genuineattr_readers,@patterndelegates, ordef_delegatorsoverGrape::Util::ApiDescription::DSL_METHODS. Only these four aremethod_missing.Verified on grape 3.3.5:
Nothing is on fire:
delegate_missing_tois 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.successandfailureare the sharp onesThese two are not merely undefined — they mean different things depending on which
descform the caller used, and grape-swagger reads only one of the two spellings.Grape::Util::ApiDescriptionaliases them in the block form:A Hash of options never reaches
ApiDescription, so the literal keys survive unaliased. Both forms, same API, grape 3.3.5:route.entityroute.successroute.http_codesroute.failuredesc 'a', success: X, failure: Y:success,:failurenilXnilYdesc 'b' do success X; failure Y end:entity,:http_codesXnilYnilSo there is no single reader that answers "what did the user declare as the success entity" —
route.entityis blind to the Hash form androute.successis blind to the block form. grape-swagger papers over this by reading both and taking whichever is non-nil (route.entity || route.successatendpoint.rb:103,route.http_codes || route.failureatendpoint.rb:238), which is correct but only becausemethod_missingis there to answer the second half.This also means the obvious cleanup — "just use
route.entity" — is wrong today. It would silently dropsuccess:for every user of the Hash form, which is the form the README teaches throughout.default_responsehas no spelling that works anywhereUnlike
success/failure,default_responsehas no alias, noDSL_METHODSentry, and no block-form equivalent. It exists only as a Hash key that Grape passes through untouched, andmethod_missingis 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 toroute.default_responsewhile sweeping 19route.optionsreads 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 asdefault::but the code has only ever read
:default_response. #868 introduced both spellings in the same commit —route.options[:default_response]inlib,default:in the README — and nothing has reconciled them since. End to end throughadd_swagger_documentation:The documented spelling silently produces no
defaultresponse. The spelling that works appears nowhere in the docs — only inspec/swagger_v2/api_swagger_v2_response_with_models_spec.rb:19andapi_swagger_v2_response_with_models_and_primitive_types_spec.rb.defaultis inDSL_METHODS, soroute.defaultwould 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 :@optionsfromBaseRoute— it identified these same four names as grape-swagger's entire exposure, and proposed normalizing the Hash form insidedescsosuccess:/failure:would store:entity/:http_codeslike 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 lastroute.optionsread fromlib/, 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
master(2.3.0 development)🤖 Generated with Claude Code