lib/grape-swagger/doc_methods/move_params.rb
module GrapeSwagger
module DocMethods
class MoveParams
class << self
attr_accessor :definitions
def can_be_moved?(params, http_verb)
move_methods.include?(http_verb) && includes_body_param?(params)
end
def to_definition(params, route, definitions)
@definitions = definitions
unify!(params)
params_to_move = movable_params(params)
return (params + correct_array_param(params_to_move)) if should_correct_array?(params_to_move)
params << parent_definition_of_params(params_to_move, route)
params
end
private
def should_correct_array?(param)
param.length == 1 && param.first[:in] == 'body' && param.first[:type] == 'array'
end
def correct_array_param(param)
param.first[:schema] = { type: param.first.delete(:type), items: param.first.delete(:items) }
param
end
def parent_definition_of_params(params, route)
definition_name = OperationId.manipulate(parse_model(route.path))
referenced_definition = build_definition(definition_name, params, route.request_method.downcase)
definition = @definitions[referenced_definition]
move_params_to_new(definition, params)
definition[:description] = route.description if route.respond_to?(:description)
build_body_parameter(referenced_definition, definition_name)
end
def move_params_to_new(definition, params)
params, nested_params = params.partition { |x| !x[:name].to_s.include?('[') }
unless params.blank?
properties, required = build_properties(params)
add_properties_to_definition(definition, properties, required)
end
nested_properties = build_nested_properties(nested_params) unless nested_params.blank?
add_properties_to_definition(definition, nested_properties, []) unless nested_params.blank?
end
def build_properties(params)
properties = {}
required = []
prepare_nested_types(params) if should_expose_as_array?(params)
params.each do |param|
name = param[:name].to_sym
properties[name] = if should_expose_as_array?([param])
document_as_array(param)
else
document_as_property(param)
end
required << name if deletable?(param) && param[:required]
end
[properties, required]
end
def document_as_array(param)
{}.tap do |property|
property[:type] = 'array'
property[:description] = param.delete(:description) unless param[:description].nil?
property[:items] = document_as_property(param)[:items]
end
end
def document_as_property(param)
property_keys.each_with_object({}) do |x, memo|
value = param[x]
next if value.blank?
if x == :type && @definitions[value].present?
memo['$ref'] = "#/definitions/#{value}"
else
memo[x] = value
end
end
end
def build_nested_properties(params, properties = {})
property = params.bsearch { |x| x[:name].include?('[') }[:name].split('[').first
nested_params, params = params.partition { |x| x[:name].start_with?("#{property}[") }
prepare_nested_names(property, nested_params)
recursive_call(properties, property, nested_params) unless nested_params.empty?
build_nested_properties(params, properties) unless params.empty?
properties
end
def recursive_call(properties, property, nested_params)
if should_expose_as_array?(nested_params)
properties[property] = array_type
move_params_to_new(properties[property][:items], nested_params)
else
properties[property] = object_type
move_params_to_new(properties[property], nested_params)
end
end
def movable_params(params)
to_delete = params.each_with_object([]) { |x, memo| memo << x if deletable?(x) }
delete_from(params, to_delete)
to_delete
end
def delete_from(params, to_delete)
to_delete.each { |x| params.delete(x) }
end
def add_properties_to_definition(definition, properties, required)
if definition.key?(:items)
definition[:items][:properties].merge!(properties)
add_to_required(definition[:items], required)
else
definition[:properties].merge!(properties)
add_to_required(definition, required)
end
end
def add_to_required(definition, value)
return if value.blank?
definition[:required] ||= []
definition[:required].push(*value)
end
def build_body_parameter(reference, name)
{}.tap do |x|
x[:name] = name
x[:in] = 'body'
x[:required] = true
x[:schema] = { '$ref' => "#/definitions/#{reference}" }
end
end
def build_definition(name, params, verb = nil)
name = "#{verb}#{name}" if verb
@definitions[name] = should_expose_as_array?(params) ? array_type : object_type
name
end
def array_type
{ type: 'array', items: { type: 'object', properties: {} } }
end
def object_type
{ type: 'object', properties: {} }
end
def prepare_nested_types(params)
params.each do |param|
next unless param[:items]
param[:type] = param[:items][:type] == 'array' ? 'string' : param[:items][:type]
param[:format] = param[:items][:format] if param[:items][:format]
param.delete(:items)
end
end
def prepare_nested_names(property, params)
params.each { |x| x[:name] = x[:name].sub(property, '').sub('[', '').sub(']', '') }
end
def unify!(params)
params.each { |x| x[:in] = x.delete(:param_type) if x[:param_type] }
params.each { |x| x[:in] = 'body' if x[:in] == 'formData' } if includes_body_param?(params)
end
def parse_model(ref)
parts = ref.split('/')
parts.last.include?('{') ? parts[0..-2].join('/') : parts[0..-1].join('/')
end
def property_keys
[:type, :format, :description, :minimum, :maximum, :items]
end
def deletable?(param)
param[:in] == 'body'
end
def move_methods
[:post, :put, :patch, 'POST', 'PUT', 'PATCH']
end
def includes_body_param?(params)
params.map { |x| return true if x[:in] == 'body' || x[:param_type] == 'body' }
false
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'
end
end
end
end
end