Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions lib/grape/declared_params_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# frozen_string_literal: true

module Grape
class DeclaredParamsHandler
def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)
@include_missing = include_missing
@evaluate_given = evaluate_given
@stringify = stringify
@contract_key_map = contract_key_map
end

def call(passed_params, declared_params, route_params, renamed_params)
recursive_declared(
passed_params,
declared_params: declared_params,
route_params: route_params,
renamed_params: renamed_params
)
end

private

def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])
res = if passed_params.is_a?(Array)
passed_params.map do |passed_param|
recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)
end
else
declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
end

@contract_key_map&.each { |key_map| key_map.write(passed_params, res) }

res
end

def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)

declared_hash_attr(
passed_params,
declared_param: declared_param_attr.key,
params_nested_path:,
memo:,
renamed_params:,
route_params:
)
end
end

def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)
if declared_param.is_a?(Hash)
declared_param.each_pair do |declared_parent_param, declared_children_params|
next unless @include_missing || passed_params.key?(declared_parent_param)

memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_parent_param.to_s

memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do
recursive_declared(
passed_children_params,
declared_params: declared_children_params,
params_nested_path: params_nested_path_dup,
renamed_params:,
route_params:
)
end
end
else
# If it is not a Hash then it does not have children.
# Find its value or set it to nil.
return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))

memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)
passed_param = passed_params[declared_param]

params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_param.to_s

memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do
passed_param
end
end
end

def build_memo_key(params_nested_path, declared_param, renamed_params)
rename_path = params_nested_path + [declared_param.to_s]
renamed_param_name = renamed_params[rename_path]

param = renamed_param_name || declared_param
@stringify ? param.to_s : param.to_sym
end

def handle_passed_param(params_nested_path, route_params:, has_passed_children: false, &_block)
return yield if has_passed_children

key = params_nested_path[0]
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1

type = route_params.dig(key, :type)
has_children = route_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }

if type == 'Hash' && !has_children
{}
elsif type == 'Array' || (type&.start_with?('[') && !type.include?(','))
[]
elsif type == 'Set' || type&.start_with?('#<Set', 'Set')
Set.new
else
yield
end
end
end
end
111 changes: 6 additions & 105 deletions lib/grape/dsl/declared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,115 +19,16 @@ def initialize(msg = '#declared is not available prior to parameter validation')
# @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`
# options. `:include_parent_namespaces` defaults to true, hence must be set to false if
# you want only to return params declared against the current/target endpoint.
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false)
raise MethodNotYetAvailable unless before_filter_passed

options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false)
declared_params ||= optioned_declared_params(options[:include_parent_namespaces])

res = if passed_params.is_a?(Array)
declared_array(passed_params, options, declared_params, params_nested_path)
else
declared_hash(passed_params, options, declared_params, params_nested_path)
end

if (key_maps = inheritable_setting.namespace_stackable[:contract_key_map])
key_maps.each { |key_map| key_map.write(passed_params, res) }
end

res
end

private

def declared_array(passed_params, options, declared_params, params_nested_path)
passed_params.map do |passed_param|
declared(passed_param || {}, options, declared_params, params_nested_path)
end
end

def declared_hash(passed_params, options, declared_params, params_nested_path)
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params)

declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
end
end

def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map]
handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map: contract_key_map)
declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || [])
renamed_params = inheritable_setting.route[:renamed_params] || {}
if declared_param.is_a?(Hash)
declared_param.each_pair do |declared_parent_param, declared_children_params|
params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_parent_param.to_s
next unless options[:include_missing] || passed_params.key?(declared_parent_param)

rename_path = params_nested_path + [declared_parent_param.to_s]
renamed_param_name = renamed_params[rename_path]

memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

memo[memo_key] = handle_passed_param(params_nested_path_dup, has_passed_children: passed_children_params.any?) do
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
end
end
else
# If it is not a Hash then it does not have children.
# Find its value or set it to nil.
return unless options[:include_missing] || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))

rename_path = params_nested_path + [declared_param.to_s]
renamed_param_name = renamed_params[rename_path]

memo_key = optioned_param_key(renamed_param_name || declared_param, options)
passed_param = passed_params[declared_param]

params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_param.to_s
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
passed_param
end
end
end

def handle_passed_param(params_nested_path, has_passed_children: false, &_block)
return yield if has_passed_children

key = params_nested_path[0]
key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1

route_options_params = options[:route_options][:params] || {}
type = route_options_params.dig(key, :type)
has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") }

if type == 'Hash' && !has_children
{}
elsif type == 'Array' || (type&.start_with?('[') && !type.include?(','))
[]
elsif type == 'Set' || type&.start_with?('#<Set', 'Set')
Set.new
else
yield
end
end

def optioned_param_key(declared_param, options)
options[:stringify] ? declared_param.to_s : declared_param.to_sym
end

def optioned_declared_params(include_parent_namespaces)
declared_params = if include_parent_namespaces
# Declared params including parent namespaces
inheritable_setting.route[:declared_params]
else
# Declared params at current namespace
inheritable_setting.namespace_stackable[:declared_params].last || []
end

raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params
route_params = options.dig(:route_options, :params) || {} # options = endpoint's option

declared_params
handler.call(passed_params, declared_params, route_params, renamed_params)
end
end
end
Expand Down
Loading