From 87e5b8776ad126a5f40ba0bb5a70dd427193d21b Mon Sep 17 00:00:00 2001 From: Andrey Subbota Date: Tue, 10 Nov 2026 15:01:27 +0100 Subject: [PATCH] Honor documented default: and stop reading route.desc The README documented desc ..., default: but grape-swagger only read default_response:, so the documented spelling was a silent no-op. route.default cannot be the fallback: it is OrderedOptions / Hash#default and answers nil even when the key is set. Read default_response first, then settings[:description][:default], which is where Grape < 4 keeps it. Also stop reading route.desc. Grape's desc DSL stores :description; get '/', desc: is a passthrough onto the route, not that DSL, and is no longer treated as a swagger summary. The default: example runs on every supported Grape, with the deprecation Grape 4 emits for it silenced. --- CHANGELOG.md | 1 + README.md | 8 +-- UPGRADING.md | 2 + lib/grape-swagger/endpoint.rb | 6 +- ...pi_swagger_v2_response_with_models_spec.rb | 41 ++++++++++++++ spec/swagger_v2/namespaced_api_spec.rb | 56 ++++++++++++++----- 6 files changed, 93 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 572f9683c..1ce50edac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * [#1011](https://github.com/ruby-grape/grape-swagger/pull/1011): Preserve string length, array item and object property limits in body parameter definitions, and emit `minLength`/`maxLength` only for strings (`minProperties`/`maxProperties` for hashes) instead of any non-array type - [@numbata](https://github.com/numbata). * [#1009](https://github.com/ruby-grape/grape-swagger/pull/1009): Bump the Danger `danger-comment.yml` workflow from 0.1.0 to 0.1.1 - [@dependabot[bot]](https://github.com/dependabot%5Bbot%5D). * [#1008](https://github.com/ruby-grape/grape-swagger/pull/1008): Bump the Danger `danger-run.yml` workflow from 0.1.0 to 0.1.1 - [@dependabot[bot]](https://github.com/dependabot%5Bbot%5D). +* [#990](https://github.com/ruby-grape/grape-swagger/pull/990): Honor the documented `desc ..., default:` spelling for a default response (alongside `default_response:`), and stop reading `route.desc` (`get '/', desc:` is no longer a swagger summary). See [UPGRADING](UPGRADING.md) - [@numbata](https://github.com/numbata). * [#986](https://github.com/ruby-grape/grape-swagger/pull/986): Read a route's tags through `route.tags` instead of `route.options`; a blank `tags:` (`nil` or `[]`) now means "not specified" and derives the tag from the path. See [UPGRADING](UPGRADING.md) - [@ericproulx](https://github.com/ericproulx). * [#988](https://github.com/ruby-grape/grape-swagger/pull/988): Fix CI by replacing single-statement `rubocop:disable`/`enable` pairs with `rubocop:disable-next`, per rubocop 1.90's new `Style/DirectiveScope` cop - [@numbata](https://github.com/numbata). * [#992](https://github.com/ruby-grape/grape-swagger/pull/992): Fix CI by pinning `json` to `< 3.0` in the Gemfile; `json` 3.0 dropped support for the `quirks_mode:` keyword that `multi_json`'s adapter still passes by default, breaking JSON request parsing wherever Grape resolves `Grape::Json` to `MultiJson` - [@numbata](https://github.com/numbata). diff --git a/README.md b/README.md index dfcdab596..31329adb9 100644 --- a/README.md +++ b/README.md @@ -1123,13 +1123,13 @@ end #### Default response -By setting the `default` option you can also define a default response that is the result returned for all unspecified status codes. -The definition supports the same syntax as `success` or `failure`. +By setting the `default_response` option you can also define a default response that is the result returned for all unspecified status codes. +The definition supports the same syntax as `success` or `failure`. Prefer `default_response:`; `default:` is still read until grape-swagger 3.0. In the following cases, the schema ref would be taken from route. ```ruby -desc 'thing', default: { message: 'the default response' } +desc 'thing', default_response: { message: 'the default response' } get '/thing' do # ... end @@ -1147,7 +1147,7 @@ Just like with `success` or `failure` you can also provide a `model` parameter. ```ruby desc 'Get a list of stuff', - default: { model: Entities::UseResponse, message: 'the default response' } + default_response: { model: Entities::UseResponse, message: 'the default response' } get do # your code comes here end diff --git a/UPGRADING.md b/UPGRADING.md index df20233d7..3fa040ab1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,8 @@ ### Upgrading to >= 2.3.0 +- **`desc(..., default_response:)` is the supported spelling for a default response.** The README previously documented `default:`, but grape-swagger only ever read `default_response:`, so the documented form was a silent no-op. 2.3 reads both. Prefer `default_response:`; `default:` remains until grape-swagger 3.0. (Grape itself is also renaming that key; see [ruby-grape/grape#2862](https://github.com/ruby-grape/grape/pull/2862).) +- **`get '/', desc: '…'` no longer sets the swagger `summary`.** That option is a passthrough onto the route, not Grape's `desc` DSL (which stores `:description`). Use `desc '…'` for the operation description, or `desc '…', summary: '…'` for a summary. - **A blank `desc(..., tags:)` now means "not specified".** `tags: nil` no longer suppresses the tag and `tags: []` no longer emits an empty `"tags": []`; both derive the tag from the path, as if `tags:` had been omitted. `tags:` is an override, and only a non-empty list overrides anything. The old `nil` behaviour was never a deliberate one: [#523](https://github.com/ruby-grape/grape-swagger/pull/523) introduced route-level tags to *override* path-derived grouping and read them with `route.options.fetch(:tags, tag_object(route))`, whose default-on-absence semantics happened to give `nil` a meaning of its own. It was never documented or tested. diff --git a/lib/grape-swagger/endpoint.rb b/lib/grape-swagger/endpoint.rb index 25fcde2f6..e116e6084 100644 --- a/lib/grape-swagger/endpoint.rb +++ b/lib/grape-swagger/endpoint.rb @@ -140,7 +140,6 @@ def security_object(route) end def summary_object(route) - summary = route.desc if route.desc summary = route.description if route.description.present? && route.detail summary = route.summary if route.summary @@ -267,7 +266,10 @@ def tag_object(route, path) private def default_code_from_route(route) - entity = route.default_response + # `route.default` is OrderedOptions / Hash#default, not the desc key. Grape < 4 keeps + # `desc ..., default:` under settings[:description][:default] instead of remapping it to + # `default_response`; drop this fallback with `default:` in grape-swagger 3.0. + entity = route.default_response || route.settings.dig(:description, :default) return [] if entity.nil? default_code = { code: 'default', message: 'Default Response' } diff --git a/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb b/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb index 316559f55..d2a6b8c30 100644 --- a/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb +++ b/spec/swagger_v2/api_swagger_v2_response_with_models_spec.rb @@ -52,4 +52,45 @@ def app expect(subject['definitions']).to eql(swagger_entity_as_response_object) end end + + # The README spelling that grape-swagger used to ignore. Grape 2.x/3.x keep it as + # `:default`, which grape-swagger reads itself; Grape 4 remaps it onto `default_response:` + # and deprecates it, so there the example only covers Grape's remap. + describe 'uses the documented default: spelling' do + before :all do + module TheApi + class ResponseApiDefaultAlias < Grape::API + format :json + + # Grape 4 emits its `default:` deprecation from `desc`. + Grape.deprecator.silence do + desc 'This returns something', + success: [{ code: 200 }], + default: { message: 'Error', model: Entities::ApiError } + end + get '/use-default' do + { 'declared_params' => declared(params) } + end + + add_swagger_documentation(models: [Entities::UseResponse]) + end + end + end + + def app + TheApi::ResponseApiDefaultAlias + end + + subject do + get '/swagger_doc/use-default' + JSON.parse(last_response.body) + end + + specify do + expect(subject['paths']['/use-default']['get']['responses']['default']).to eql( + 'description' => 'Error', + 'schema' => { '$ref' => '#/definitions/ApiError' } + ) + end + end end diff --git a/spec/swagger_v2/namespaced_api_spec.rb b/spec/swagger_v2/namespaced_api_spec.rb index 790902663..a848fd4ce 100644 --- a/spec/swagger_v2/namespaced_api_spec.rb +++ b/spec/swagger_v2/namespaced_api_spec.rb @@ -7,7 +7,8 @@ def app Class.new(Grape::API) do namespace :aspace do - get '/', desc: 'Description for aspace' + desc 'Description for aspace' + get '/' end add_swagger_documentation format: :json end @@ -18,8 +19,8 @@ def app JSON.parse(last_response.body)['paths']['/aspace']['get'] end - it 'shows the namespace summary in the json spec' do - expect(subject['summary']).to eql('Description for aspace') + it 'shows the namespace description in the json spec' do + expect(subject['description']).to eql('Description for aspace') end end @@ -27,7 +28,8 @@ def app def app Class.new(Grape::API) do namespace :camelCases do - get '/', desc: 'Look! An endpoint.' + desc 'Look! An endpoint.' + get '/' end add_swagger_documentation format: :json end @@ -38,8 +40,8 @@ def app JSON.parse(last_response.body)['paths']['/camelCases']['get'] end - it 'shows the namespace summary in the json spec' do - expect(subject['summary']).to eql('Look! An endpoint.') + it 'shows the namespace description in the json spec' do + expect(subject['description']).to eql('Look! An endpoint.') end end @@ -47,7 +49,8 @@ def app def app namespaced_api = Class.new(Grape::API) do namespace :bspace do - get '/', desc: 'Description for aspace' + desc 'Description for aspace' + get '/' end end @@ -62,8 +65,8 @@ def app JSON.parse(last_response.body)['paths']['/bspace']['get'] end - it 'shows the namespace summary in the json spec' do - expect(subject['summary']).to eql('Description for aspace') + it 'shows the namespace description in the json spec' do + expect(subject['description']).to eql('Description for aspace') end end @@ -71,7 +74,8 @@ def app def app namespaced_api = Class.new(Grape::API) do namespace :bspace do - get '/', desc: 'Description for aspace' + desc 'Description for aspace' + get '/' end end @@ -86,8 +90,8 @@ def app JSON.parse(last_response.body)['paths']['/mounted/bspace']['get'] end - it 'shows the namespace summary in the json spec' do - expect(subject['summary']).to eql('Description for aspace') + it 'shows the namespace description in the json spec' do + expect(subject['description']).to eql('Description for aspace') end end @@ -95,7 +99,8 @@ def app def app inner_namespaced_api = Class.new(Grape::API) do namespace :bspace do - get '/', desc: 'Description for aspace' + desc 'Description for aspace' + get '/' end end @@ -114,8 +119,29 @@ def app JSON.parse(last_response.body)['paths']['/mounted/bspace']['get'] end - it 'shows the namespace summary in the json spec' do - expect(subject['summary']).to eql('Description for aspace') + it 'shows the namespace description in the json spec' do + expect(subject['description']).to eql('Description for aspace') + end + end + + context 'get with desc: option' do + def app + Class.new(Grape::API) do + namespace :aspace do + get '/', desc: 'passthrough, not the desc DSL' + end + add_swagger_documentation format: :json + end + end + + subject do + get '/swagger_doc' + JSON.parse(last_response.body)['paths']['/aspace']['get'] + end + + it 'does not treat get desc: as a swagger summary or description' do + expect(subject).not_to include('summary') + expect(subject).not_to include('description') end end end