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/
|
||||
spec/swagger_v2/x-dummy.rb
|
||||
coverage/
|
||||
.byebug_history
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
31
README.md
31
README.md
|
@ -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)
|
||||
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -103,9 +103,9 @@ module GrapeSwagger
|
|||
|
||||
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
|
||||
|
@ -181,7 +181,8 @@ module GrapeSwagger
|
|||
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)
|
||||
|
@ -193,8 +194,7 @@ module GrapeSwagger
|
|||
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)
|
||||
|
@ -202,8 +202,7 @@ module GrapeSwagger
|
|||
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
|
||||
|
|
|
@ -25,7 +25,7 @@ module GrapeSwagger
|
|||
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)
|
||||
|
||||
|
@ -105,12 +105,38 @@ module GrapeSwagger
|
|||
|
||||
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)
|
||||
|
|
|
@ -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',
|
||||
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' }
|
||||
|
@ -96,6 +96,27 @@ describe 'moving body/formData Params to definitions' do
|
|||
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
|
||||
|
@ -126,6 +147,7 @@ describe 'moving body/formData Params to definitions' do
|
|||
'properties' => {
|
||||
'contact' => {
|
||||
'type' => 'object',
|
||||
'additionalProperties' => true,
|
||||
'properties' => {
|
||||
'name' => { 'type' => 'string', 'description' => 'name' },
|
||||
'addresses' => {
|
||||
|
@ -280,4 +302,54 @@ describe 'moving body/formData Params to definitions' do
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue