diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c69ed9..572f9683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * [#1016](https://github.com/ruby-grape/grape-swagger/pull/1016): Hide the nested parameters of a Hash parameter marked `documentation: { hidden: true }`, and pass the resolved `token_owner` to parameter-level `hidden:` procs like route-level ones (raising `TokenOwnerNotFound` if the configured method is missing) - [@numbata](https://github.com/numbata). +* [#1013](https://github.com/ruby-grape/grape-swagger/pull/1013): Fix array `default` values being placed inside `items` instead of on the array parameter or property, and emit an empty array `default` - [@numbata](https://github.com/numbata). * [#1012](https://github.com/ruby-grape/grape-swagger/pull/1012): Keep the Danger PR check running in CI by pinning `git` below 6 - [@numbata](https://github.com/numbata). * [#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). diff --git a/UPGRADING.md b/UPGRADING.md index f8fb7dbd..df20233d 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -5,6 +5,7 @@ - **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. +- **An `Array` default on an array parameter is now documented on the array, not inside `items`.** `optional :ids, type: [String], default: %w[a b]` emits `default: ["a", "b"]` next to `type: array` (as a body property or a query/formData parameter) instead of `items.default`, which is not a valid place for a list. Consumers that read `items.default` for such a default must read the parameter/property `default` instead; scalar defaults on array parameters still land in `items.default`. An empty array default (`default: []`) is now emitted as well. See [#1013](https://github.com/ruby-grape/grape-swagger/pull/1013). ### Upgrading to >= 2.2.0 diff --git a/lib/grape-swagger/doc_methods/move_params.rb b/lib/grape-swagger/doc_methods/move_params.rb index 0afd7983..fd1823e1 100644 --- a/lib/grape-swagger/doc_methods/move_params.rb +++ b/lib/grape-swagger/doc_methods/move_params.rb @@ -183,7 +183,7 @@ def property_keys end def array_schema_keys - %i[minItems maxItems] + %i[minItems maxItems default] end def deletable?(param) diff --git a/lib/grape-swagger/doc_methods/parse_params.rb b/lib/grape-swagger/doc_methods/parse_params.rb index 66d99a79..3e12889d 100644 --- a/lib/grape-swagger/doc_methods/parse_params.rb +++ b/lib/grape-swagger/doc_methods/parse_params.rb @@ -100,7 +100,12 @@ def parse_array_item(definitions, type, value_type) enum_or_range_values = parse_enum_or_range_values(values) array_items.merge!(enum_or_range_values) if enum_or_range_values - array_items[:default] = value_type[:default] if value_type[:default].present? + default = value_type[:default] + if default.is_a?(Array) + @parsed_param[:default] = default + elsif default.present? + array_items[:default] = default + end set_additional_properties, additional_properties = parse_additional_properties(definitions, value_type) array_items[:additionalProperties] = additional_properties if set_additional_properties diff --git a/spec/issues/950_array_default_spec.rb b/spec/issues/950_array_default_spec.rb new file mode 100644 index 00000000..3c8eb679 --- /dev/null +++ b/spec/issues/950_array_default_spec.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe '#950 array default belongs to the array, not its items' do + let(:app) do + Class.new(Grape::API) do + params do + optional :dns_servers, type: [String], default: %w[8.8.8.8 8.8.4.4] + end + get '/query' do + {} + end + + params do + optional :dns_servers, type: [String], default: %w[8.8.8.8 8.8.4.4] + end + post '/body' do + {} + end + + params do + optional :dns_servers, type: [String], default: %w[8.8.8.8 8.8.4.4] + end + desc 'form', consumes: ['application/x-www-form-urlencoded'] + post '/form' do + {} + end + + params do + optional :dns_servers, type: [String], default: [] + end + get '/empty' do + {} + end + + params do + optional :dns_servers, type: [String], default: 'z' + end + get '/scalar' do + {} + end + + params do + optional :dns_servers, type: [String], default: 'z' + end + post '/scalar_body' do + {} + end + + params do + optional :things, type: [JSON], default: [{ 'a' => 1 }] do + optional :a, type: Integer + end + end + post '/json' do + {} + end + + add_swagger_documentation + end + end + + subject do + get '/swagger_doc' + JSON.parse(last_response.body) + end + + it 'puts the default on the array for a query parameter' do + expect(subject['paths']['/query']['get']['parameters']).to eql( + [ + { 'in' => 'query', 'name' => 'dns_servers', 'type' => 'array', 'items' => { 'type' => 'string' }, + 'default' => %w[8.8.8.8 8.8.4.4], 'required' => false } + ] + ) + end + + it 'puts the default on the array property of a body definition' do + expect(subject['definitions']['postBody']['properties']['dns_servers']).to eql( + 'type' => 'array', 'items' => { 'type' => 'string' }, 'default' => %w[8.8.8.8 8.8.4.4] + ) + end + + it 'puts the default on the array for a formData parameter' do + expect(subject['paths']['/form']['post']['parameters']).to eql( + [ + { 'in' => 'formData', 'name' => 'dns_servers', 'type' => 'array', 'items' => { 'type' => 'string' }, + 'default' => %w[8.8.8.8 8.8.4.4], 'required' => false } + ] + ) + end + + it 'emits an empty array default on the array' do + expect(subject['paths']['/empty']['get']['parameters']).to eql( + [ + { 'in' => 'query', 'name' => 'dns_servers', 'type' => 'array', 'items' => { 'type' => 'string' }, + 'default' => [], 'required' => false } + ] + ) + end + + it 'keeps a scalar default of an array parameter in items' do + expect(subject['paths']['/scalar']['get']['parameters']).to eql( + [ + { 'in' => 'query', 'name' => 'dns_servers', 'type' => 'array', + 'items' => { 'type' => 'string', 'default' => 'z' }, 'required' => false } + ] + ) + end + + it 'keeps a scalar default of an array body property in items' do + expect(subject['definitions']['postScalarBody']['properties']['dns_servers']).to eql( + 'type' => 'array', 'items' => { 'type' => 'string', 'default' => 'z' } + ) + end + + it 'puts the default of an array of nested objects on the array property' do + expect(subject['definitions']['postJson']['properties']['things']).to eql( + 'type' => 'array', + 'items' => { 'type' => 'object', 'properties' => { 'a' => { 'type' => 'integer', 'format' => 'int32' } } }, + 'default' => [{ 'a' => 1 }] + ) + end +end