|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +module Grape |
| 4 | + module DSL |
| 5 | + module Declared |
| 6 | + # Denotes a situation where a DSL method has been invoked in a |
| 7 | + # filter which it should not yet be available in |
| 8 | + class MethodNotYetAvailable < StandardError |
| 9 | + def initialize(msg = '#declared is not available prior to parameter validation') |
| 10 | + super |
| 11 | + end |
| 12 | + end |
| 13 | + |
| 14 | + # A filtering method that will return a hash |
| 15 | + # consisting only of keys that have been declared by a |
| 16 | + # `params` statement against the current/target endpoint or parent |
| 17 | + # namespaces. |
| 18 | + # @param params [Hash] The initial hash to filter. Usually this will just be `params` |
| 19 | + # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces` |
| 20 | + # options. `:include_parent_namespaces` defaults to true, hence must be set to false if |
| 21 | + # you want only to return params declared against the current/target endpoint. |
| 22 | + def declared(passed_params, options = {}, declared_params = nil, params_nested_path = []) |
| 23 | + raise MethodNotYetAvailable unless before_filter_passed |
| 24 | + |
| 25 | + options.reverse_merge!(include_missing: true, include_parent_namespaces: true, evaluate_given: false) |
| 26 | + declared_params ||= optioned_declared_params(options[:include_parent_namespaces]) |
| 27 | + |
| 28 | + res = if passed_params.is_a?(Array) |
| 29 | + declared_array(passed_params, options, declared_params, params_nested_path) |
| 30 | + else |
| 31 | + declared_hash(passed_params, options, declared_params, params_nested_path) |
| 32 | + end |
| 33 | + |
| 34 | + if (key_maps = inheritable_setting.namespace_stackable[:contract_key_map]) |
| 35 | + key_maps.each { |key_map| key_map.write(passed_params, res) } |
| 36 | + end |
| 37 | + |
| 38 | + res |
| 39 | + end |
| 40 | + |
| 41 | + private |
| 42 | + |
| 43 | + def declared_array(passed_params, options, declared_params, params_nested_path) |
| 44 | + passed_params.map do |passed_param| |
| 45 | + declared(passed_param || {}, options, declared_params, params_nested_path) |
| 46 | + end |
| 47 | + end |
| 48 | + |
| 49 | + def declared_hash(passed_params, options, declared_params, params_nested_path) |
| 50 | + declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo| |
| 51 | + next if options[:evaluate_given] && !declared_param_attr.scope.attr_meets_dependency?(passed_params) |
| 52 | + |
| 53 | + declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo) |
| 54 | + end |
| 55 | + end |
| 56 | + |
| 57 | + def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo) |
| 58 | + renamed_params = inheritable_setting.route[:renamed_params] || {} |
| 59 | + if declared_param.is_a?(Hash) |
| 60 | + declared_param.each_pair do |declared_parent_param, declared_children_params| |
| 61 | + params_nested_path_dup = params_nested_path.dup |
| 62 | + params_nested_path_dup << declared_parent_param.to_s |
| 63 | + next unless options[:include_missing] || passed_params.key?(declared_parent_param) |
| 64 | + |
| 65 | + rename_path = params_nested_path + [declared_parent_param.to_s] |
| 66 | + renamed_param_name = renamed_params[rename_path] |
| 67 | + |
| 68 | + memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options) |
| 69 | + passed_children_params = passed_params[declared_parent_param] || passed_params.class.new |
| 70 | + |
| 71 | + memo[memo_key] = handle_passed_param(params_nested_path_dup, has_passed_children: passed_children_params.any?) do |
| 72 | + declared(passed_children_params, options, declared_children_params, params_nested_path_dup) |
| 73 | + end |
| 74 | + end |
| 75 | + else |
| 76 | + # If it is not a Hash then it does not have children. |
| 77 | + # Find its value or set it to nil. |
| 78 | + return unless options[:include_missing] || passed_params.try(:key?, declared_param) |
| 79 | + |
| 80 | + rename_path = params_nested_path + [declared_param.to_s] |
| 81 | + renamed_param_name = renamed_params[rename_path] |
| 82 | + |
| 83 | + memo_key = optioned_param_key(renamed_param_name || declared_param, options) |
| 84 | + passed_param = passed_params[declared_param] |
| 85 | + |
| 86 | + params_nested_path_dup = params_nested_path.dup |
| 87 | + params_nested_path_dup << declared_param.to_s |
| 88 | + memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do |
| 89 | + passed_param |
| 90 | + end |
| 91 | + end |
| 92 | + end |
| 93 | + |
| 94 | + def handle_passed_param(params_nested_path, has_passed_children: false, &_block) |
| 95 | + return yield if has_passed_children |
| 96 | + |
| 97 | + key = params_nested_path[0] |
| 98 | + key += "[#{params_nested_path[1..].join('][')}]" if params_nested_path.size > 1 |
| 99 | + |
| 100 | + route_options_params = options[:route_options][:params] || {} |
| 101 | + type = route_options_params.dig(key, :type) |
| 102 | + has_children = route_options_params.keys.any? { |k| k != key && k.start_with?("#{key}[") } |
| 103 | + |
| 104 | + if type == 'Hash' && !has_children |
| 105 | + {} |
| 106 | + elsif type == 'Array' || (type&.start_with?('[') && type.exclude?(',')) |
| 107 | + [] |
| 108 | + elsif type == 'Set' || type&.start_with?('#<Set') |
| 109 | + Set.new |
| 110 | + else |
| 111 | + yield |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + def optioned_param_key(declared_param, options) |
| 116 | + options[:stringify] ? declared_param.to_s : declared_param.to_sym |
| 117 | + end |
| 118 | + |
| 119 | + def optioned_declared_params(include_parent_namespaces) |
| 120 | + declared_params = if include_parent_namespaces |
| 121 | + # Declared params including parent namespaces |
| 122 | + inheritable_setting.route[:declared_params] |
| 123 | + else |
| 124 | + # Declared params at current namespace |
| 125 | + inheritable_setting.namespace_stackable[:declared_params].last || [] |
| 126 | + end |
| 127 | + |
| 128 | + raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params |
| 129 | + |
| 130 | + declared_params |
| 131 | + end |
| 132 | + end |
| 133 | + end |
| 134 | +end |
0 commit comments