Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* [#905](https://github.com/ruby-grape/grape-swagger/pull/905): Allow Float, BigDecimal and beginless ranges in `values` to set `minimum` and `maximum`, and skip infinite bounds that cannot be serialized to JSON - [@artyomb](https://github.com/artyomb).
* [#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).
* [#957](https://github.com/ruby-grape/grape-swagger/pull/957): Fix issue where array params are sometimes omitted when `array_use_braces` is true - [@olivier-thatch](https://github.com/olivier-thatch).
* [#957](https://github.com/ruby-grape/grape-swagger/pull/957): Document nested arrays such as `Array[Array[String]]` with one `array` level per nesting instead of flattening them to a single level - [@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).
Expand Down
14 changes: 13 additions & 1 deletion lib/grape-swagger/doc_methods/parse_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def call(param, settings, path, route, definitions, consumes) # rubocop:disable
in: param_type(value_type, consumes),
name: settings[:full_name] || param
}
fix_name_of_body_array_param(value_type)

# optional properties
document_description(settings)
Expand All @@ -35,6 +36,12 @@ def call(param, settings, path, route, definitions, consumes) # rubocop:disable

private

def fix_name_of_body_array_param(value_type)
return unless @parsed_param[:in] == 'body' && value_type[:is_array]

@parsed_param[:name] = @parsed_param[:name].delete_suffix('[]')
end

def document_description(settings)
description = settings[:desc] || settings[:description]
@parsed_param[:description] = description if description
Expand Down Expand Up @@ -82,11 +89,16 @@ def document_array_param(value_type, definitions)
value_type
)

@parsed_param[:items] = array_items
@parsed_param[:items] = nest_array_items(array_items, value_type[:type])
@parsed_param[:type] = 'array'
@parsed_param[:collectionFormat] = collection_format if DataType.collections.include?(collection_format)
end

def nest_array_items(items, declared_type)
depth = declared_type.to_s[/\A\[*/].size
(depth - 1).times.reduce(items) { |inner, _| { type: 'array', items: inner } }
Comment thread
numbata marked this conversation as resolved.
Resolved
end

def parse_array_item(definitions, type, value_type)
array_items = {}
if definitions[value_type[:data_type]]
Expand Down
3 changes: 3 additions & 0 deletions lib/grape-swagger/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ def params_object(route, options, path, consumes)
memo << GrapeSwagger::DocMethods::ParseParams.call(param, value, path, route, @definitions, consumes)
end

# braced array names collapse onto their unbraced twins once body params are renamed
parameters.uniq! { |param| [param[:name], param[:in]] }

if GrapeSwagger::DocMethods::MoveParams.can_be_moved?(route.request_method, parameters)
parameters = GrapeSwagger::DocMethods::MoveParams.to_definition(path, parameters, route, @definitions)
end
Expand Down
266 changes: 266 additions & 0 deletions spec/swagger_v2/params_array_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,90 @@
{ 'declared_params' => declared(params) }
end

helpers do
params :common_params do
requires :array_of_string, type: Array[String]
requires :array_of_integer, type: Array[Integer]
end
end

params do
use :common_params
end
get '/endpoint_with_common_params' do
{ 'declared_params' => declared(params) }
end

params do
use :common_params
end
post '/endpoint_with_common_params' do
{ 'declared_params' => declared(params) }
end

params do
requires :typed_group, type: Array do
requires :id, type: Integer
optional :tags, type: Array[String]
end
end
post '/nested_group_body' do
{ 'declared_params' => declared(params) }
end

params do
requires :query_array, type: Array[String], documentation: { param_type: 'query' }
requires :body_array, type: Array[Integer]
end
post '/mixed_param_types' do
{ 'declared_params' => declared(params) }
end

params do
requires :explicit_body_array, type: Array[String], documentation: { param_type: 'body' }
requires :implicit_body_array, type: Array[Integer]
end
post '/explicit_body' do
{ 'declared_params' => declared(params) }
end

params do
requires :array_of_string, type: Array[String]
end
put '/put_array' do
{ 'declared_params' => declared(params) }
end

params do
requires :array_of_string, type: Array[String]
end
patch '/patch_array' do
{ 'declared_params' => declared(params) }
end

params do
requires :array_of_string, type: Array[String]
end
desc 'form encoded array', consumes: ['application/x-www-form-urlencoded']
post '/form_encoded_array' do
{ 'declared_params' => declared(params) }
end

params do
requires :matrix, type: Array[Array[String]]
requires :cube, type: Array[Array[Array[Integer]]]
end
post '/nested_arrays_body' do
{ 'declared_params' => declared(params) }
end

params do
requires :matrix, type: Array[Array[String]]
end
get '/nested_arrays_query' do
{ 'declared_params' => declared(params) }
end

add_swagger_documentation array_use_braces: array_use_braces
end
end
Expand Down Expand Up @@ -220,6 +304,188 @@
expect(subject['paths']['/array_of_entities']['post']['parameters']).to eql(parameters)
end
end

describe 'retrieves the documentation for parameters shared between GET and POST endpoints' do
subject do
get '/swagger_doc/endpoint_with_common_params'
JSON.parse(last_response.body)
end

describe 'for non-body parameters' do
specify do
expect(subject['paths']['/endpoint_with_common_params']['get']['parameters']).to eql(
[
{ 'in' => 'query', 'name' => "array_of_string#{braces}", 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => true },
{ 'in' => 'query', 'name' => "array_of_integer#{braces}", 'type' => 'array', 'items' => { 'type' => 'integer', 'format' => 'int32' }, 'required' => true }
]
)
end
end

describe 'for body parameters' do
specify do
expect(subject['paths']['/endpoint_with_common_params']['post']['parameters']).to eql(
[
{ 'in' => 'body', 'name' => 'postEndpointWithCommonParams', 'schema' => { '$ref' => '#/definitions/postEndpointWithCommonParams' }, 'required' => true }
]
)
expect(subject['definitions']['postEndpointWithCommonParams']).to eql(
'type' => 'object',
'properties' => {
'array_of_string' => { # no braces, even if array_use_braces is true
'type' => 'array',
'items' => {
'type' => 'string'
}
},
'array_of_integer' => { # no braces, even if array_use_braces is true
'type' => 'array',
'items' => {
'type' => 'integer',
'format' => 'int32'
}
}
},
'required' => %w[array_of_string array_of_integer]
)
end
end
end

describe 'retrieves the documentation for a group of parameters sent as body' do
subject do
get '/swagger_doc/nested_group_body'
JSON.parse(last_response.body)
end

specify do
expect(subject['definitions']['postNestedGroupBody']).to eql(
'type' => 'object',
'properties' => {
'typed_group' => {
'type' => 'array',
'items' => {
'type' => 'object',
'properties' => {
'id' => { 'type' => 'integer', 'format' => 'int32' },
'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' } }
},
'required' => ['id']
}
}
},
'required' => ['typed_group']
)
end
end

describe 'retrieves the documentation for an array that stays a query parameter on a POST endpoint' do
subject do
get '/swagger_doc/mixed_param_types'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/mixed_param_types']['post']['parameters'].first).to eql(
'in' => 'query', 'name' => "query_array#{braces}", 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => true
)
expect(subject['definitions']['postMixedParamTypes']).to eql(
'type' => 'object',
'properties' => { 'body_array' => { 'type' => 'array', 'items' => { 'type' => 'integer', 'format' => 'int32' } } },
'required' => ['body_array']
)
end
end

describe 'retrieves the documentation for arrays when a body parameter is declared explicitly' do
subject do
get '/swagger_doc/explicit_body'
JSON.parse(last_response.body)
end

specify do
expect(subject['definitions']['postExplicitBody']).to eql(
'type' => 'object',
'properties' => {
'explicit_body_array' => { 'type' => 'array', 'items' => { 'type' => 'string' } },
'implicit_body_array' => { 'type' => 'array', 'items' => { 'type' => 'integer', 'format' => 'int32' } }
},
'required' => %w[explicit_body_array implicit_body_array]
)
end
end

%w[put patch].each do |verb|
describe "retrieves the documentation for an array sent as body of a #{verb.upcase} endpoint" do
subject do
get "/swagger_doc/#{verb}_array"
JSON.parse(last_response.body)
end

specify do
expect(subject['definitions']["#{verb}#{verb.capitalize}Array"]).to eql(
'type' => 'object',
'properties' => { 'array_of_string' => { 'type' => 'array', 'items' => { 'type' => 'string' } } },
'required' => ['array_of_string']
)
end
end
end

describe 'retrieves the documentation for an array sent form encoded' do
subject do
get '/swagger_doc/form_encoded_array'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/form_encoded_array']['post']['parameters']).to eql(
[{ 'in' => 'formData', 'name' => "array_of_string#{braces}", 'type' => 'array', 'items' => { 'type' => 'string' }, 'required' => true }]
)
end
end

describe 'retrieves the documentation for nested arrays sent as body' do
subject do
get '/swagger_doc/nested_arrays_body'
JSON.parse(last_response.body)
end

specify do
expect(subject['definitions']['postNestedArraysBody']).to eql(
'type' => 'object',
'properties' => {
'matrix' => { 'type' => 'array', 'items' => { 'type' => 'array', 'items' => { 'type' => 'string' } } },
'cube' => {
'type' => 'array',
'items' => {
'type' => 'array',
'items' => { 'type' => 'array', 'items' => { 'type' => 'integer', 'format' => 'int32' } }
}
}
},
'required' => %w[matrix cube]
)
end
end

describe 'retrieves the documentation for nested arrays sent as query' do
subject do
get '/swagger_doc/nested_arrays_query'
JSON.parse(last_response.body)
end

specify do
expect(subject['paths']['/nested_arrays_query']['get']['parameters']).to eql(
[
{
'in' => 'query', 'name' => "matrix#{braces}", 'type' => 'array', 'required' => true,
'items' => { 'type' => 'array', 'items' => { 'type' => 'string' } }
}
]
)
end
end
end
end
end
Loading