Skip to content

Fix documentation of additionalProperties field when used with array parameters #840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 21, 2021
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ spec/params_entity_spec.rb
vendor/bundle/
spec/swagger_v2/x-dummy.rb
coverage/
.byebug_history
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

#### Fixes

* [#840](https://github.com/ruby-grape/grape-swagger/pull/840): Fixes documentation of `additionalProperties` field when used with array parameters, or when setting it to `false` - [@magni-](https://github.com/magni-)
* [#841](https://github.com/ruby-grape/grape-swagger/pull/839): Fixes `type` and `format` values for object fields nested in an array ([#832](https://github.com/ruby-grape/grape-swagger/issue/832)) - [@magni-](https://github.com/magni-)
* #[#839](https://github.com/ruby-grape/grape-swagger/pull/839): Fixes documentation of `false` or `nil` default parameter values - [@magni-](https://github.com/magni-)
* [#839](https://github.com/ruby-grape/grape-swagger/pull/839): Fixes documentation of `false` or `nil` default parameter values - [@magni-](https://github.com/magni-)
* Your contribution here.


Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ add_swagger_documentation \
* [Collection Format](#collection-format)
* [Hiding parameters](#hiding-parameters)
* [Setting a Swagger default value](#default-value)
* [Setting `additionalProperties` for `object`-type parameters](#additional-properties)
* [Example parameter value](#param-example)
* [Response documentation](#response)
* [Changing default status codes](#change-status)
Expand Down Expand Up @@ -779,6 +780,36 @@ params do
end
```

### Setting `additionalProperties` for `object`-type parameters <a name="additional-properties">

Use the `additional_properties` option in the `documentation` hash for `object`-type parameters to set [`additionalProperties`](https://swagger.io/specification/v2/#model-with-mapdictionary-properties).

#### Allow any additional properties
```ruby
params do
optional :thing, type: Hash, documentation: { additional_properties: true }
end
```

#### Allow any additional properties of a particular type
```ruby
params do
optional :thing, type: Hash, documentation: { additional_properties: String }
end
```

#### Allow any additional properties matching a defined schema
```ruby
class Entity < Grape::Entity
expose :this
end

params do
optional :thing, type: Hash, documentation: { additional_properties: Entity }
end
```


#### Example parameter value <a name="param-example"></a>

The example parameter will populate the Swagger UI with the example value, and can be used for optional or required parameters.
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Upgrading Grape-swagger

### Upgrading to >= 1.4.1

- `additionalProperties` has been deprecated and will be removed in a future version of `grape-swagger`. It has been replaced with `additional_properties`.

### Upgrading to >= 1.4.0

- Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
Expand Down
13 changes: 6 additions & 7 deletions lib/grape-swagger/doc_methods/move_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ def document_as_array(param)

def document_as_property(param)
property_keys.each_with_object({}) do |x, memo|
value = param[x]
next if value.blank?
next unless param.key?(x)

value = param[x]
if x == :type && @definitions[value].present?
memo['$ref'] = "#/definitions/#{value}"
else
Expand Down Expand Up @@ -181,7 +181,8 @@ def parse_model(ref)
end

def property_keys
%i[type format description minimum maximum items enum default additionalProperties example]
%i[type format description minimum maximum items enum default additional_properties additionalProperties
example]
end

def deletable?(param)
Expand All @@ -193,17 +194,15 @@ def move_methods
end

def includes_body_param?(params)
params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
false
params.any? { |x| x[:in] == 'body' || x[:param_type] == 'body' }
end

def should_expose_as_array?(params)
should_exposed_as(params) == 'array'
end

def should_exposed_as(params)
params.map { |x| return 'object' if x[:type] && x[:type] != 'array' }
'array'
params.any? { |x| x[:type] && x[:type] != 'array' } ? 'object' : 'array'
end
end
end
Expand Down
34 changes: 30 additions & 4 deletions lib/grape-swagger/doc_methods/parse_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def call(param, settings, path, route, definitions)
document_default_value(settings) unless value_type[:is_array]
document_range_values(settings) unless value_type[:is_array]
document_required(settings)
document_additional_properties(settings)
document_additional_properties(definitions, settings) unless value_type[:is_array]
document_add_extensions(settings)
document_example(settings)

Expand Down Expand Up @@ -105,12 +105,38 @@ def parse_array_item(definitions, type, value_type)

array_items[:default] = value_type[:default] if value_type[:default].present?

set_additional_properties, additional_properties = parse_additional_properties(definitions, value_type)
array_items[:additionalProperties] = additional_properties if set_additional_properties

array_items
end

def document_additional_properties(settings)
additional_properties = settings[:additionalProperties]
@parsed_param[:additionalProperties] = additional_properties if additional_properties
def document_additional_properties(definitions, settings)
set_additional_properties, additional_properties = parse_additional_properties(definitions, settings)
@parsed_param[:additionalProperties] = additional_properties if set_additional_properties
end

def parse_additional_properties(definitions, settings)
return false unless settings.key?(:additionalProperties) || settings.key?(:additional_properties)

value =
if settings.key?(:additionalProperties)
GrapeSwagger::Errors::SwaggerSpecDeprecated.tell!(:additionalProperties)
settings[:additionalProperties]
else
settings[:additional_properties]
end

parsed_value =
if definitions[value.to_s]
{ '$ref': "#/definitions/#{value}" }
elsif value.is_a?(Class)
{ type: DataType.call(value) }
else
value
end

[true, parsed_value]
end

def document_example(settings)
Expand Down
83 changes: 83 additions & 0 deletions spec/swagger_v2/api_swagger_v2_additional_properties_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# frozen_string_literal: true

require 'spec_helper'

describe 'parsing additional_parameters' do
let(:app) do
Class.new(Grape::API) do
namespace :things do
class Element < Grape::Entity
expose :id
end

params do
optional :closed, type: Hash, documentation: { additional_properties: false, in: 'body' } do
requires :only
end
optional :open, type: Hash, documentation: { additional_properties: true }
optional :type_limited, type: Hash, documentation: { additional_properties: String }
optional :ref_limited, type: Hash, documentation: { additional_properties: Element }
optional :fallback, type: Hash, documentation: { additional_properties: { type: 'integer' } }
end
post do
present params
end
end

add_swagger_documentation format: :json, models: [Element]
end
end

subject do
get '/swagger_doc/things'
JSON.parse(last_response.body)
end

describe 'POST' do
specify do
expect(subject.dig('paths', '/things', 'post', 'parameters')).to eql(
[
{ 'name' => 'Things', 'in' => 'body', 'required' => true, 'schema' => { '$ref' => '#/definitions/postThings' } }
]
)
end

specify do
expect(subject.dig('definitions', 'postThings')).to eql(
'type' => 'object',
'properties' => {
'closed' => {
'type' => 'object',
'additionalProperties' => false,
'properties' => {
'only' => { 'type' => 'string' }
},
'required' => ['only']
},
'open' => {
'type' => 'object',
'additionalProperties' => true
},
'type_limited' => {
'type' => 'object',
'additionalProperties' => {
'type' => 'string'
}
},
'ref_limited' => {
'type' => 'object',
'additionalProperties' => {
'$ref' => '#/definitions/Element'
}
},
'fallback' => {
'type' => 'object',
'additionalProperties' => {
'type' => 'integer'
}
}
}
)
end
end
end
74 changes: 73 additions & 1 deletion spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class NestedBodyParamTypeApi < Grape::API
detail: 'more details description',
success: Entities::UseNestedWithAddress
params do
optional :contact, type: Hash do
optional :contact, type: Hash, documentation: { additional_properties: true } do
requires :name, type: String, documentation: { desc: 'name', in: 'body' }
optional :addresses, type: Array do
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
Expand Down Expand Up @@ -96,6 +96,27 @@ class NestedBodyParamTypeApi < Grape::API
end
end

namespace :nested_params_array do
desc 'post in body with array of nested parameters',
detail: 'more details description',
success: Entities::UseNestedWithAddress
params do
optional :contacts, type: Array, documentation: { additional_properties: false } do
requires :name, type: String, documentation: { desc: 'name', in: 'body' }
optional :addresses, type: Array do
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
requires :postcode, type: String, documentation: { desc: 'postcode', in: 'body' }
requires :city, type: String, documentation: { desc: 'city', in: 'body' }
optional :country, type: String, documentation: { desc: 'country', in: 'body' }
end
end
end

post '/in_body' do
{ 'declared_params' => declared(params) }
end
end

add_swagger_documentation
end
end
Expand Down Expand Up @@ -126,6 +147,7 @@ def app
'properties' => {
'contact' => {
'type' => 'object',
'additionalProperties' => true,
'properties' => {
'name' => { 'type' => 'string', 'description' => 'name' },
'addresses' => {
Expand Down Expand Up @@ -280,4 +302,54 @@ def app
end
end
end

describe 'array of nested body parameters given' do
subject do
get '/swagger_doc/nested_params_array'
JSON.parse(last_response.body)
end

describe 'POST' do
specify do
expect(subject['paths']['/nested_params_array/in_body']['post']['parameters']).to eql(
[
{ 'name' => 'NestedParamsArrayInBody', 'in' => 'body', 'required' => true, 'schema' => { '$ref' => '#/definitions/postNestedParamsArrayInBody' } }
]
)
end

specify do
expect(subject['definitions']['postNestedParamsArrayInBody']).to eql(
'type' => 'object',
'properties' => {
'contacts' => {
'type' => 'array',
'items' => {
'type' => 'object',
'additionalProperties' => false,
'properties' => {
'name' => { 'type' => 'string', 'description' => 'name' },
'addresses' => {
'type' => 'array',
'items' => {
'type' => 'object',
'properties' => {
'street' => { 'type' => 'string', 'description' => 'street' },
'postcode' => { 'type' => 'string', 'description' => 'postcode' },
'city' => { 'type' => 'string', 'description' => 'city' },
'country' => { 'type' => 'string', 'description' => 'country' }
},
'required' => %w[street postcode city]
}
}
},
'required' => %w[name]
}
}
},
'description' => 'post in body with array of nested parameters'
)
end
end
end
end