Skip to content

Commit 187e88f

Browse files
magni-aka-momo
authored andcommitted
Fix documentation of additionalProperties field when used with array parameters (ruby-grape#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
1 parent df4e3ee commit 187e88f

8 files changed

+230
-13
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ spec/params_entity_spec.rb
4242
vendor/bundle/
4343
spec/swagger_v2/x-dummy.rb
4444
coverage/
45+
.byebug_history

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
#### Fixes
88

9+
* [#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-)
910
* [#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-)
10-
* #[#839](https://github.com/ruby-grape/grape-swagger/pull/839): Fixes documentation of `false` or `nil` default parameter values - [@magni-](https://github.com/magni-)
11+
* [#839](https://github.com/ruby-grape/grape-swagger/pull/839): Fixes documentation of `false` or `nil` default parameter values - [@magni-](https://github.com/magni-)
1112
* Your contribution here.
1213

1314

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ add_swagger_documentation \
451451
* [Collection Format](#collection-format)
452452
* [Hiding parameters](#hiding-parameters)
453453
* [Setting a Swagger default value](#default-value)
454+
* [Setting `additionalProperties` for `object`-type parameters](#additional-properties)
454455
* [Example parameter value](#param-example)
455456
* [Response documentation](#response)
456457
* [Changing default status codes](#change-status)
@@ -779,6 +780,36 @@ params do
779780
end
780781
```
781782

783+
### Setting `additionalProperties` for `object`-type parameters <a name="additional-properties">
784+
785+
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).
786+
787+
#### Allow any additional properties
788+
```ruby
789+
params do
790+
optional :thing, type: Hash, documentation: { additional_properties: true }
791+
end
792+
```
793+
794+
#### Allow any additional properties of a particular type
795+
```ruby
796+
params do
797+
optional :thing, type: Hash, documentation: { additional_properties: String }
798+
end
799+
```
800+
801+
#### Allow any additional properties matching a defined schema
802+
```ruby
803+
class Entity < Grape::Entity
804+
expose :this
805+
end
806+
807+
params do
808+
optional :thing, type: Hash, documentation: { additional_properties: Entity }
809+
end
810+
```
811+
812+
782813
#### Example parameter value <a name="param-example"></a>
783814

784815
The example parameter will populate the Swagger UI with the example value, and can be used for optional or required parameters.

UPGRADING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Upgrading Grape-swagger
22

3+
### Upgrading to >= 1.4.1
4+
5+
- `additionalProperties` has been deprecated and will be removed in a future version of `grape-swagger`. It has been replaced with `additional_properties`.
6+
37
### Upgrading to >= 1.4.0
48

59
- Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.

lib/grape-swagger/doc_methods/move_params.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ def document_as_array(param)
103103

104104
def document_as_property(param)
105105
property_keys.each_with_object({}) do |x, memo|
106-
value = param[x]
107-
next if value.blank?
106+
next unless param.key?(x)
108107

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

183183
def property_keys
184-
%i[type format description minimum maximum items enum default additionalProperties example]
184+
%i[type format description minimum maximum items enum default additional_properties additionalProperties
185+
example]
185186
end
186187

187188
def deletable?(param)
@@ -193,17 +194,15 @@ def move_methods
193194
end
194195

195196
def includes_body_param?(params)
196-
params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
197-
false
197+
params.any? { |x| x[:in] == 'body' || x[:param_type] == 'body' }
198198
end
199199

200200
def should_expose_as_array?(params)
201201
should_exposed_as(params) == 'array'
202202
end
203203

204204
def should_exposed_as(params)
205-
params.map { |x| return 'object' if x[:type] && x[:type] != 'array' }
206-
'array'
205+
params.any? { |x| x[:type] && x[:type] != 'array' } ? 'object' : 'array'
207206
end
208207
end
209208
end

lib/grape-swagger/doc_methods/parse_params.rb

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def call(param, settings, path, route, definitions)
2525
document_default_value(settings) unless value_type[:is_array]
2626
document_range_values(settings) unless value_type[:is_array]
2727
document_required(settings)
28-
document_additional_properties(settings)
28+
document_additional_properties(definitions, settings) unless value_type[:is_array]
2929
document_add_extensions(settings)
3030
document_example(settings)
3131

@@ -105,12 +105,38 @@ def parse_array_item(definitions, type, value_type)
105105

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

108+
set_additional_properties, additional_properties = parse_additional_properties(definitions, value_type)
109+
array_items[:additionalProperties] = additional_properties if set_additional_properties
110+
108111
array_items
109112
end
110113

111-
def document_additional_properties(settings)
112-
additional_properties = settings[:additionalProperties]
113-
@parsed_param[:additionalProperties] = additional_properties if additional_properties
114+
def document_additional_properties(definitions, settings)
115+
set_additional_properties, additional_properties = parse_additional_properties(definitions, settings)
116+
@parsed_param[:additionalProperties] = additional_properties if set_additional_properties
117+
end
118+
119+
def parse_additional_properties(definitions, settings)
120+
return false unless settings.key?(:additionalProperties) || settings.key?(:additional_properties)
121+
122+
value =
123+
if settings.key?(:additionalProperties)
124+
GrapeSwagger::Errors::SwaggerSpecDeprecated.tell!(:additionalProperties)
125+
settings[:additionalProperties]
126+
else
127+
settings[:additional_properties]
128+
end
129+
130+
parsed_value =
131+
if definitions[value.to_s]
132+
{ '$ref': "#/definitions/#{value}" }
133+
elsif value.is_a?(Class)
134+
{ type: DataType.call(value) }
135+
else
136+
value
137+
end
138+
139+
[true, parsed_value]
114140
end
115141

116142
def document_example(settings)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe 'parsing additional_parameters' do
6+
let(:app) do
7+
Class.new(Grape::API) do
8+
namespace :things do
9+
class Element < Grape::Entity
10+
expose :id
11+
end
12+
13+
params do
14+
optional :closed, type: Hash, documentation: { additional_properties: false, in: 'body' } do
15+
requires :only
16+
end
17+
optional :open, type: Hash, documentation: { additional_properties: true }
18+
optional :type_limited, type: Hash, documentation: { additional_properties: String }
19+
optional :ref_limited, type: Hash, documentation: { additional_properties: Element }
20+
optional :fallback, type: Hash, documentation: { additional_properties: { type: 'integer' } }
21+
end
22+
post do
23+
present params
24+
end
25+
end
26+
27+
add_swagger_documentation format: :json, models: [Element]
28+
end
29+
end
30+
31+
subject do
32+
get '/swagger_doc/things'
33+
JSON.parse(last_response.body)
34+
end
35+
36+
describe 'POST' do
37+
specify do
38+
expect(subject.dig('paths', '/things', 'post', 'parameters')).to eql(
39+
[
40+
{ 'name' => 'Things', 'in' => 'body', 'required' => true, 'schema' => { '$ref' => '#/definitions/postThings' } }
41+
]
42+
)
43+
end
44+
45+
specify do
46+
expect(subject.dig('definitions', 'postThings')).to eql(
47+
'type' => 'object',
48+
'properties' => {
49+
'closed' => {
50+
'type' => 'object',
51+
'additionalProperties' => false,
52+
'properties' => {
53+
'only' => { 'type' => 'string' }
54+
},
55+
'required' => ['only']
56+
},
57+
'open' => {
58+
'type' => 'object',
59+
'additionalProperties' => true
60+
},
61+
'type_limited' => {
62+
'type' => 'object',
63+
'additionalProperties' => {
64+
'type' => 'string'
65+
}
66+
},
67+
'ref_limited' => {
68+
'type' => 'object',
69+
'additionalProperties' => {
70+
'$ref' => '#/definitions/Element'
71+
}
72+
},
73+
'fallback' => {
74+
'type' => 'object',
75+
'additionalProperties' => {
76+
'type' => 'integer'
77+
}
78+
}
79+
}
80+
)
81+
end
82+
end
83+
end

spec/swagger_v2/api_swagger_v2_param_type_body_nested_spec.rb

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class NestedBodyParamTypeApi < Grape::API
1313
detail: 'more details description',
1414
success: Entities::UseNestedWithAddress
1515
params do
16-
optional :contact, type: Hash do
16+
optional :contact, type: Hash, documentation: { additional_properties: true } do
1717
requires :name, type: String, documentation: { desc: 'name', in: 'body' }
1818
optional :addresses, type: Array do
1919
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
@@ -96,6 +96,27 @@ class NestedBodyParamTypeApi < Grape::API
9696
end
9797
end
9898

99+
namespace :nested_params_array do
100+
desc 'post in body with array of nested parameters',
101+
detail: 'more details description',
102+
success: Entities::UseNestedWithAddress
103+
params do
104+
optional :contacts, type: Array, documentation: { additional_properties: false } do
105+
requires :name, type: String, documentation: { desc: 'name', in: 'body' }
106+
optional :addresses, type: Array do
107+
requires :street, type: String, documentation: { desc: 'street', in: 'body' }
108+
requires :postcode, type: String, documentation: { desc: 'postcode', in: 'body' }
109+
requires :city, type: String, documentation: { desc: 'city', in: 'body' }
110+
optional :country, type: String, documentation: { desc: 'country', in: 'body' }
111+
end
112+
end
113+
end
114+
115+
post '/in_body' do
116+
{ 'declared_params' => declared(params) }
117+
end
118+
end
119+
99120
add_swagger_documentation
100121
end
101122
end
@@ -126,6 +147,7 @@ def app
126147
'properties' => {
127148
'contact' => {
128149
'type' => 'object',
150+
'additionalProperties' => true,
129151
'properties' => {
130152
'name' => { 'type' => 'string', 'description' => 'name' },
131153
'addresses' => {
@@ -280,4 +302,54 @@ def app
280302
end
281303
end
282304
end
305+
306+
describe 'array of nested body parameters given' do
307+
subject do
308+
get '/swagger_doc/nested_params_array'
309+
JSON.parse(last_response.body)
310+
end
311+
312+
describe 'POST' do
313+
specify do
314+
expect(subject['paths']['/nested_params_array/in_body']['post']['parameters']).to eql(
315+
[
316+
{ 'name' => 'NestedParamsArrayInBody', 'in' => 'body', 'required' => true, 'schema' => { '$ref' => '#/definitions/postNestedParamsArrayInBody' } }
317+
]
318+
)
319+
end
320+
321+
specify do
322+
expect(subject['definitions']['postNestedParamsArrayInBody']).to eql(
323+
'type' => 'object',
324+
'properties' => {
325+
'contacts' => {
326+
'type' => 'array',
327+
'items' => {
328+
'type' => 'object',
329+
'additionalProperties' => false,
330+
'properties' => {
331+
'name' => { 'type' => 'string', 'description' => 'name' },
332+
'addresses' => {
333+
'type' => 'array',
334+
'items' => {
335+
'type' => 'object',
336+
'properties' => {
337+
'street' => { 'type' => 'string', 'description' => 'street' },
338+
'postcode' => { 'type' => 'string', 'description' => 'postcode' },
339+
'city' => { 'type' => 'string', 'description' => 'city' },
340+
'country' => { 'type' => 'string', 'description' => 'country' }
341+
},
342+
'required' => %w[street postcode city]
343+
}
344+
}
345+
},
346+
'required' => %w[name]
347+
}
348+
}
349+
},
350+
'description' => 'post in body with array of nested parameters'
351+
)
352+
end
353+
end
354+
end
283355
end

0 commit comments

Comments
 (0)