Fix documentation of `additionalProperties` field when used with array parameters (#840)
* Fix typo in CHANGELOG * Allow usage of additional_properties, deprecate additionalProperties All other options are snake-cased rather than camel-cased, so this keeps things consistent. * Ignore .byebug_history * Add test covering additional_properties * Fix additionalProperties for arrays of objects * Fix settings additional_properties to false * Handle receiving types and entities in additional_properties This includes a fix to MoveParams.document_as_property for an issue found while testing these changes. * Refactor to use Enumerable#any? * Document additional_properties * Update CHANGELOG
This commit is contained in:
parent
46e112c048
commit
1fea1f8d19
|
@ -42,3 +42,4 @@ spec/params_entity_spec.rb
|
||||||
vendor/bundle/
|
vendor/bundle/
|
||||||
spec/swagger_v2/x-dummy.rb
|
spec/swagger_v2/x-dummy.rb
|
||||||
coverage/
|
coverage/
|
||||||
|
.byebug_history
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
|
|
||||||
#### Fixes
|
#### 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-)
|
* [#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.
|
* Your contribution here.
|
||||||
|
|
||||||
|
|
||||||
|
|
31
README.md
31
README.md
|
@ -451,6 +451,7 @@ add_swagger_documentation \
|
||||||
* [Collection Format](#collection-format)
|
* [Collection Format](#collection-format)
|
||||||
* [Hiding parameters](#hiding-parameters)
|
* [Hiding parameters](#hiding-parameters)
|
||||||
* [Setting a Swagger default value](#default-value)
|
* [Setting a Swagger default value](#default-value)
|
||||||
|
* [Setting `additionalProperties` for `object`-type parameters](#additional-properties)
|
||||||
* [Example parameter value](#param-example)
|
* [Example parameter value](#param-example)
|
||||||
* [Response documentation](#response)
|
* [Response documentation](#response)
|
||||||
* [Changing default status codes](#change-status)
|
* [Changing default status codes](#change-status)
|
||||||
|
@ -779,6 +780,36 @@ params do
|
||||||
end
|
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>
|
#### 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.
|
The example parameter will populate the Swagger UI with the example value, and can be used for optional or required parameters.
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
## Upgrading Grape-swagger
|
## 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
|
### Upgrading to >= 1.4.0
|
||||||
|
|
||||||
- Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
|
- Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
|
||||||
|
|
|
@ -103,9 +103,9 @@ module GrapeSwagger
|
||||||
|
|
||||||
def document_as_property(param)
|
def document_as_property(param)
|
||||||
property_keys.each_with_object({}) do |x, memo|
|
property_keys.each_with_object({}) do |x, memo|
|
||||||
value = param[x]
|
next unless param.key?(x)
|
||||||
next if value.blank?
|
|
||||||
|
|
||||||
|
value = param[x]
|
||||||
if x == :type && @definitions[value].present?
|
if x == :type && @definitions[value].present?
|
||||||
memo['$ref'] = "#/definitions/#{value}"
|
memo['$ref'] = "#/definitions/#{value}"
|
||||||
else
|
else
|
||||||
|
@ -181,7 +181,8 @@ module GrapeSwagger
|
||||||
end
|
end
|
||||||
|
|
||||||
def property_keys
|
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
|
end
|
||||||
|
|
||||||
def deletable?(param)
|
def deletable?(param)
|
||||||
|
@ -193,8 +194,7 @@ module GrapeSwagger
|
||||||
end
|
end
|
||||||
|
|
||||||
def includes_body_param?(params)
|
def includes_body_param?(params)
|
||||||
params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
|
params.any? { |x| x[:in] == 'body' || x[:param_type] == 'body' }
|
||||||
false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_expose_as_array?(params)
|
def should_expose_as_array?(params)
|
||||||
|
@ -202,8 +202,7 @@ module GrapeSwagger
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_exposed_as(params)
|
def should_exposed_as(params)
|
||||||
params.map { |x| return 'object' if x[:type] && x[:type] != 'array' }
|
params.any? { |x| x[:type] && x[:type] != 'array' } ? 'object' : 'array'
|
||||||
'array'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ module GrapeSwagger
|
||||||
document_default_value(settings) unless value_type[:is_array]
|
document_default_value(settings) unless value_type[:is_array]
|
||||||
document_range_values(settings) unless value_type[:is_array]
|
document_range_values(settings) unless value_type[:is_array]
|
||||||
document_required(settings)
|
document_required(settings)
|
||||||
document_additional_properties(settings)
|
document_additional_properties(definitions, settings) unless value_type[:is_array]
|
||||||
document_add_extensions(settings)
|
document_add_extensions(settings)
|
||||||
document_example(settings)
|
document_example(settings)
|
||||||
|
|
||||||
|
@ -105,12 +105,38 @@ module GrapeSwagger
|
||||||
|
|
||||||
array_items[:default] = value_type[:default] if value_type[:default].present?
|
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
|
array_items
|
||||||
end
|
end
|
||||||
|
|
||||||
def document_additional_properties(settings)
|
def document_additional_properties(definitions, settings)
|
||||||
additional_properties = settings[:additionalProperties]
|
set_additional_properties, additional_properties = parse_additional_properties(definitions, settings)
|
||||||
@parsed_param[:additionalProperties] = additional_properties if additional_properties
|
@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
|
end
|
||||||
|
|
||||||
def document_example(settings)
|
def document_example(settings)
|
||||||
|
|
|
@ -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
|
|
@ -13,7 +13,7 @@ describe 'moving body/formData Params to definitions' do
|
||||||
detail: 'more details description',
|
detail: 'more details description',
|
||||||
success: Entities::UseNestedWithAddress
|
success: Entities::UseNestedWithAddress
|
||||||
params do
|
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' }
|
requires :name, type: String, documentation: { desc: 'name', in: 'body' }
|
||||||
optional :addresses, type: Array do
|
optional :addresses, type: Array do
|
||||||
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
|
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
|
||||||
|
@ -96,6 +96,27 @@ describe 'moving body/formData Params to definitions' do
|
||||||
end
|
end
|
||||||
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
|
add_swagger_documentation
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -126,6 +147,7 @@ describe 'moving body/formData Params to definitions' do
|
||||||
'properties' => {
|
'properties' => {
|
||||||
'contact' => {
|
'contact' => {
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
|
'additionalProperties' => true,
|
||||||
'properties' => {
|
'properties' => {
|
||||||
'name' => { 'type' => 'string', 'description' => 'name' },
|
'name' => { 'type' => 'string', 'description' => 'name' },
|
||||||
'addresses' => {
|
'addresses' => {
|
||||||
|
@ -280,4 +302,54 @@ describe 'moving body/formData Params to definitions' do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue