Skip to content

Commit 6e16af5

Browse files
blakenumbata
authored andcommitted
Fix test
1 parent c121c12 commit 6e16af5

File tree

3 files changed

+244
-8
lines changed

3 files changed

+244
-8
lines changed

lib/grape-swagger/openapi_3/doc_methods.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
require 'grape-swagger/doc_methods/path_string'
1010
require 'grape-swagger/doc_methods/tag_name_description'
1111
require 'grape-swagger/openapi_3/doc_methods/parse_params'
12-
require 'grape-swagger/doc_methods/move_params'
12+
require 'grape-swagger/openapi_3/doc_methods/move_params'
1313
require 'grape-swagger/doc_methods/headers'
1414
require 'grape-swagger/doc_methods/build_model_definition'
1515
require 'grape-swagger/doc_methods/version'
@@ -56,7 +56,7 @@ def setup(options)
5656
output[:tags] = tags unless tags.empty? || paths.blank?
5757
output[:paths] = paths unless paths.blank?
5858
unless definitions.blank?
59-
output[:components] = {}
59+
output[:components] ||= {}
6060
output[:components][:schemas] = definitions
6161
end
6262

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# frozen_string_literal: true
2+
3+
require 'active_support/core_ext/hash/deep_merge'
4+
5+
module GrapeSwagger
6+
module DocMethods
7+
class MoveParams
8+
class << self
9+
attr_accessor :definitions
10+
11+
def can_be_moved?(params, http_verb)
12+
move_methods.include?(http_verb) && includes_body_param?(params)
13+
end
14+
15+
def to_definition(path, params, route, definitions)
16+
@definitions = definitions
17+
unify!(params)
18+
19+
params_to_move = movable_params(params)
20+
21+
return (params + correct_array_param(params_to_move)) if should_correct_array?(params_to_move)
22+
23+
params << parent_definition_of_params(params_to_move, path, route)
24+
25+
params
26+
end
27+
28+
private
29+
30+
def should_correct_array?(param)
31+
param.length == 1 && param.first[:in] == 'body' && param.first[:type] == 'array'
32+
end
33+
34+
def correct_array_param(param)
35+
param.first[:schema] = { type: param.first.delete(:type), items: param.first.delete(:items) }
36+
37+
param
38+
end
39+
40+
def parent_definition_of_params(params, path, route)
41+
definition_name = OperationId.manipulate(parse_model(path))
42+
referenced_definition = build_definition(definition_name, params, route.request_method.downcase)
43+
definition = @definitions[referenced_definition]
44+
45+
move_params_to_new(definition, params)
46+
47+
definition[:description] = route.description if route.try(:description)
48+
49+
build_body_parameter(referenced_definition, definition_name, route.options)
50+
end
51+
52+
def move_params_to_new(definition, params)
53+
params, nested_params = params.partition { |x| !x[:name].to_s.include?('[') }
54+
55+
unless params.blank?
56+
properties, required = build_properties(params)
57+
add_properties_to_definition(definition, properties, required)
58+
end
59+
60+
nested_properties = build_nested_properties(nested_params) unless nested_params.blank?
61+
add_properties_to_definition(definition, nested_properties, []) unless nested_params.blank?
62+
end
63+
64+
def build_properties(params)
65+
properties = {}
66+
required = []
67+
68+
prepare_nested_types(params) if should_expose_as_array?(params)
69+
70+
params.each do |param|
71+
name = param[:name].to_sym
72+
73+
properties[name] = if should_expose_as_array?([param])
74+
document_as_array(param)
75+
else
76+
document_as_property(param)
77+
end
78+
79+
required << name if deletable?(param) && param[:required]
80+
end
81+
82+
[properties, required]
83+
end
84+
85+
def document_as_array(param)
86+
{}.tap do |property|
87+
property[:type] = 'array'
88+
property[:description] = param.delete(:description) unless param[:description].nil?
89+
property[:items] = document_as_property(param)[:items]
90+
end
91+
end
92+
93+
def document_as_property(param)
94+
property_keys.each_with_object({}) do |x, memo|
95+
value = param[x]
96+
value = param[:schema][x] if value.blank?
97+
next if value.blank?
98+
99+
if x == :type && @definitions[value].present?
100+
memo['$ref'] = "#/components/schemas/#{value}"
101+
else
102+
memo[x] = value
103+
end
104+
end
105+
end
106+
107+
def build_nested_properties(params, properties = {})
108+
property = params.bsearch { |x| x[:name].include?('[') }[:name].split('[').first
109+
110+
nested_params, params = params.partition { |x| x[:name].start_with?("#{property}[") }
111+
prepare_nested_names(property, nested_params)
112+
113+
recursive_call(properties, property, nested_params) unless nested_params.empty?
114+
build_nested_properties(params, properties) unless params.empty?
115+
116+
properties
117+
end
118+
119+
def recursive_call(properties, property, nested_params)
120+
if should_expose_as_array?(nested_params)
121+
properties[property.to_sym] = array_type
122+
move_params_to_new(properties[property.to_sym][:items], nested_params)
123+
else
124+
properties[property.to_sym] = object_type
125+
move_params_to_new(properties[property.to_sym], nested_params)
126+
end
127+
end
128+
129+
def movable_params(params)
130+
to_delete = params.each_with_object([]) { |x, memo| memo << x if deletable?(x) }
131+
delete_from(params, to_delete)
132+
133+
to_delete
134+
end
135+
136+
def delete_from(params, to_delete)
137+
to_delete.each { |x| params.delete(x) }
138+
end
139+
140+
def add_properties_to_definition(definition, properties, required)
141+
if definition.key?(:items)
142+
definition[:items][:properties].deep_merge!(properties)
143+
add_to_required(definition[:items], required)
144+
else
145+
definition[:properties].deep_merge!(properties)
146+
add_to_required(definition, required)
147+
end
148+
end
149+
150+
def add_to_required(definition, value)
151+
return if value.blank?
152+
153+
definition[:required] ||= []
154+
definition[:required].push(*value)
155+
end
156+
157+
def build_body_parameter(reference, name, options)
158+
{}.tap do |x|
159+
x[:name] = options[:body_name] || name
160+
x[:in] = 'body'
161+
x[:required] = true
162+
x[:schema] = { '$ref' => "#/components/schemas/#{reference}" }
163+
end
164+
end
165+
166+
def build_definition(name, params, verb = nil)
167+
name = "#{verb}#{name}" if verb
168+
@definitions[name] = should_expose_as_array?(params) ? array_type : object_type
169+
170+
name
171+
end
172+
173+
def array_type
174+
{ type: 'array', items: { type: 'object', properties: {} } }
175+
end
176+
177+
def object_type
178+
{ type: 'object', properties: {} }
179+
end
180+
181+
def prepare_nested_types(params)
182+
params.each do |param|
183+
next unless param[:items]
184+
185+
param[:schema][:type] = if param[:items][:type] == 'array'
186+
'string'
187+
elsif param[:items].key?('$ref')
188+
param[:schema][:type] = 'object'
189+
else
190+
param[:items][:type]
191+
end
192+
param[:schema][:format] = param[:items][:format] if param[:items][:format]
193+
param.delete(:items) if param[:schema][:type] != 'object'
194+
end
195+
end
196+
197+
def prepare_nested_names(property, params)
198+
params.each { |x| x[:name] = x[:name].sub(property, '').sub('[', '').sub(']', '') }
199+
end
200+
201+
def unify!(params)
202+
params.each { |x| x[:in] = x.delete(:param_type) if x[:param_type] }
203+
params.each { |x| x[:in] = 'body' if x[:in] == 'formData' } if includes_body_param?(params)
204+
end
205+
206+
def parse_model(ref)
207+
parts = ref.split('/')
208+
parts.last.include?('{') ? parts[0..-2].join('/') : parts[0..-1].join('/')
209+
end
210+
211+
def property_keys
212+
%i[type format description minimum maximum items enum default]
213+
end
214+
215+
def deletable?(param)
216+
param[:in] == 'body'
217+
end
218+
219+
def move_methods
220+
[:post, :put, :patch, 'POST', 'PUT', 'PATCH']
221+
end
222+
223+
def includes_body_param?(params)
224+
params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
225+
false
226+
end
227+
228+
def should_expose_as_array?(params)
229+
should_exposed_as(params) == 'array'
230+
end
231+
232+
def should_exposed_as(params)
233+
params.map { |x| return 'object' if x[:schema][:type] && x[:schema][:type] != 'array' }
234+
'array'
235+
end
236+
end
237+
end
238+
end
239+
end

spec/openapi_3/params_array_spec.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
describe 'retrieves the documentation for grouped parameters' do
8181
subject do
8282
get '/swagger_doc/groups'
83-
puts last_response.body
8483
JSON.parse(last_response.body)
8584
end
8685

@@ -101,7 +100,6 @@
101100
describe 'retrieves the documentation for typed group parameters' do
102101
subject do
103102
get '/swagger_doc/type_given'
104-
puts last_response.body
105103
JSON.parse(last_response.body)
106104
end
107105

@@ -152,7 +150,7 @@
152150
'type' => 'string', 'description' => 'nested array of strings'
153151
},
154152
'array_of_integer' => {
155-
'type' => 'integer', 'description' => 'nested array of integers'
153+
'type' => 'integer', 'format' => 'int32', 'description' => 'nested array of integers'
156154
}
157155
},
158156
'required' => %w[array_of_string array_of_integer]
@@ -163,13 +161,12 @@
163161
describe 'documentation for simple and array parameters' do
164162
subject do
165163
get '/swagger_doc/object_and_array'
166-
puts last_response.body
167164
JSON.parse(last_response.body)
168165
end
169166

170167
specify do
171-
expect(subject['definitions']['postObjectAndArray']['type']).to eql 'object'
172-
expect(subject['definitions']['postObjectAndArray']['properties']).to eql(
168+
expect(subject['components']['schemas']['postObjectAndArray']['type']).to eql 'object'
169+
expect(subject['components']['schemas']['postObjectAndArray']['properties']).to eql(
173170
'array_of_string' => {
174171
'type' => 'array',
175172
'description' => 'array of strings',

0 commit comments

Comments
 (0)