class Grape::Validations::ParamsScope
Constants
- RESERVED_DOCUMENTATION_KEYWORDS
-
There are a number of documentation options on entities that don’t have corresponding validators. Since there is nowhere that enumerates them all, we maintain a list of them here and skip looking up validators for them.
Attributes
Public Class Methods
Source
# File lib/grape/validations/params_scope.rb, line 61 def initialize(opts, &block) @element = opts[:element] @element_renamed = opts[:element_renamed] @parent = opts[:parent] @api = opts[:api] @optional = opts[:optional] || false @type = opts[:type] @group = opts[:group] @dependent_on = opts[:dependent_on] @params_meeting_dependency = [] @declared_params = [] @index = nil instance_eval(&block) if block configure_declared_params end
Open up a new ParamsScope
, allowing parameter definitions per
Grape::DSL::Params.
@param opts [Hash] options for this scope @option opts :element [Symbol] the element that contains this scope; for
this to be relevant, @parent must be set
@option opts :element_renamed [Symbol, nil] whenever this scope should
be renamed and to what, given +nil+ no renaming is done
@option opts :parent [ParamsScope] the scope containing this scope @option opts :api [API] the API
endpoint to modify @option opts :optional [Boolean] whether or not this scope needs to have
any parameters set or not
@option opts :type [Class] a type meant to govern this scope (deprecated) @option opts :type [Hash] group options for this scope @option opts :dependent_on [Symbol] if present, this scope should only
validate if this param is present in the parent scope
@yield the instance context, open for parameter definitions
Public Instance Methods
Source
# File lib/grape/validations/params_scope.rb, line 107 def attr_meets_dependency?(params) return true unless @dependent_on return false if @parent.present? && !@parent.attr_meets_dependency?(params) meets_hash_dependency?(params) end
Source
# File lib/grape/validations/params_scope.rb, line 146 def brackets(val) "[#{val}]" if val end
Source
# File lib/grape/validations/params_scope.rb, line 79 def configuration @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration end
Source
# File lib/grape/validations/params_scope.rb, line 132 def full_name(name, index: nil) if nested? # Find our containing element's name, and append ours. "#{@parent.full_name(@element)}#{brackets(index || @index)}#{brackets(name)}" elsif lateral? # Find the name of the element as if it was at the same nesting level # as our parent. We need to forward our index upward to achieve this. @parent.full_name(name, index: @index) else # We must be the root scope, so no prefix needed. name.to_s end end
@return [String] the proper attribute name, with nesting considered.
Source
# File lib/grape/validations/params_scope.rb, line 164 def lateral? @parent && !@element end
A lateral scope is subordinate to its parent, but its keys are at the same level as its parent and thus is not contained within an element. @return [Boolean] whether or not this scope is lateral
Source
# File lib/grape/validations/params_scope.rb, line 95 def meets_dependency?(params, request_params) return true unless @dependent_on return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params) if params.is_a?(Array) @params_meeting_dependency = params.flatten.filter { |param| meets_dependency?(param, request_params) } return @params_meeting_dependency.present? end meets_hash_dependency?(params) end
Source
# File lib/grape/validations/params_scope.rb, line 114 def meets_hash_dependency?(params) # params might be anything what looks like a hash, so it must implement a `key?` method return false unless params.respond_to?(:key?) @dependent_on.each do |dependency| if dependency.is_a?(Hash) dependency_key = dependency.keys[0] proc = dependency.values[0] return false unless proc.call(params.try(:[], dependency_key)) elsif params.respond_to?(:key?) && params.try(:[], dependency).blank? return false end end true end
Source
# File lib/grape/validations/params_scope.rb, line 157 def nested? @parent && @element end
A nested scope is contained in one of its parent’s elements. @return [Boolean] whether or not this scope is nested
Source
# File lib/grape/validations/params_scope.rb, line 170 def required? !@optional end
@return [Boolean] whether or not this scope needs to be present, or can
be blank
Source
# File lib/grape/validations/params_scope.rb, line 174 def reset_index @index = nil end
Source
# File lib/grape/validations/params_scope.rb, line 151 def root? !@parent end
@return [Boolean] whether or not this scope is the root-level scope
Source
# File lib/grape/validations/params_scope.rb, line 85 def should_validate?(parameters) scoped_params = params(parameters) return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params)) return false unless meets_dependency?(scoped_params, parameters) return true if parent.nil? parent.should_validate?(parameters) end
@return [Boolean] whether or not this entire scope needs to be
validated
Protected Instance Methods
Source
# File lib/grape/validations/params_scope.rb, line 193 def full_path if nested? (@parent.full_path + [@element]) elsif lateral? @parent.full_path else [] end end
Get the full path of the parameter scope in the hierarchy.
@return [Array<Symbol>] the nesting/path of the current parameter scope
Source
# File lib/grape/validations/params_scope.rb, line 182 def push_declared_params(attrs, opts = {}) opts[:declared_params_scope] = self unless opts.key?(:declared_params_scope) return @parent.push_declared_params(attrs, opts) if lateral? push_renamed_param(full_path + [attrs.first], opts[:as]) if opts[:as] @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) }) end
Adds a parameter declaration to our list of validations. @param attrs [Array] (see Grape::DSL::Parameters#requires
)
Private Instance Methods
Source
# File lib/grape/validations/params_scope.rb, line 515 def all_element_blank?(scoped_params) scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?) end
Source
# File lib/grape/validations/params_scope.rb, line 421 def check_coerce_with(validations) return unless validations.key?(:coerce_with) # type must be supplied for coerce_with.. raise ArgumentError, 'must supply type for coerce_with' unless validations.key?(:coerce) # but not special JSON types, which # already imply coercion method return if [JSON, Array[JSON]].exclude? validations[:coerce] raise ArgumentError, 'coerce_with disallowed for type: JSON' end
Enforce correct usage of :coerce_with parameter. We do not allow coercion without a type, nor with JSON
as a type since this defines its own coercion method.
Source
# File lib/grape/validations/params_scope.rb, line 465 def check_incompatible_option_values(default, values, except_values, excepts) return unless default && !default.is_a?(Proc) raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) } if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) } raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) end return unless excepts && !excepts.is_a?(Proc) raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, excepts) \ unless Array(default).none? { |def_val| excepts.include?(def_val) } end
Source
# File lib/grape/validations/params_scope.rb, line 439 def coerce_type(validations, attrs, doc, opts) check_coerce_with(validations) return unless validations.key?(:coerce) coerce_options = { type: validations[:coerce], method: validations[:coerce_with], message: validations[:coerce_message] } validate('coerce', coerce_options, attrs, doc, opts) validations.delete(:coerce_with) validations.delete(:coerce) validations.delete(:coerce_message) end
Add type coercion validation to this scope, if any has been specified. This validation has special handling since it is composited from more than one requires
/optional
parameter, and needs to be run before most other validations.
Source
# File lib/grape/validations/params_scope.rb, line 312 def configure_declared_params push_renamed_param(full_path, @element_renamed) if @element_renamed if nested? @parent.push_declared_params [element => @declared_params] else @api.namespace_stackable(:declared_params, @declared_params) end # params were stored in settings, it can be cleaned from the params scope @declared_params = nil end
Pushes declared params to parent or settings
Source
# File lib/grape/validations/params_scope.rb, line 521 def derive_validator_options(validations) allow_blank = validations[:allow_blank] { allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank, fail_fast: validations.delete(:fail_fast) || false } end
Validators
don’t have access to each other and they don’t need, however, some validators might influence others, so their options should be shared
Source
# File lib/grape/validations/params_scope.rb, line 504 def extract_message_option(attrs) return nil unless attrs.is_a?(Array) opts = attrs.last.is_a?(Hash) ? attrs.pop : {} opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil end
Source
# File lib/grape/validations/params_scope.rb, line 455 def guess_coerce_type(coerce_type, *values_list) return coerce_type unless coerce_type == Array values_list.each do |values| next if !values || values.is_a?(Proc) return values.first.class if values.is_a?(Range) || !values.empty? end coerce_type end
Source
# File lib/grape/validations/params_scope.rb, line 392 def infer_coercion(validations) raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types) validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type) validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type) validations[:coerce] = (options_key?(:types, :value, validations) ? validations[:types][:value] : validations[:types]) if validations.key?(:types) validations[:coerce_message] = (options_key?(:types, :message, validations) ? validations[:types][:message] : nil) if validations.key?(:types) validations.delete(:types) if validations.key?(:types) coerce_type = validations[:coerce] # Special case - when the argument is a single type that is a # variant-type collection. if Types.multiple?(coerce_type) && validations.key?(:type) validations[:coerce] = Types::VariantCollectionCoercer.new( coerce_type, validations.delete(:coerce_with) ) end validations.delete(:type) coerce_type end
Validate and comprehend the :type
, :types
, and :coerce_with
options that have been supplied to the parameter declaration. The :type
and :types
options will be removed from the validations list, replaced appropriately with :coerce
and :coerce_with
options that will later be passed to {Validators::CoerceValidator}. The type that is returned may be used for documentation and further validation of parameter options.
@param validations [Hash] list of validations supplied to the
parameter declaration
@return [class-like] type to which the parameter will be coerced @raise [ArgumentError] if the given type options are invalid
Source
# File lib/grape/validations/params_scope.rb, line 307 def new_group_scope(attrs, &block) self.class.new(api: @api, parent: self, group: attrs.first, &block) end
Returns a new parameter scope, subordinate to the current one and nested under the parameter corresponding to ‘attrs.first`. @param attrs [Array] the attributes passed to the `requires` or
`optional` invocation that opened this scope.
@yield parameter scope
Source
# File lib/grape/validations/params_scope.rb, line 290 def new_lateral_scope(options, &block) self.class.new( api: @api, element: nil, parent: self, options: @optional, type: type == Array ? Array : Hash, dependent_on: options[:dependent_on], &block ) end
Returns a new parameter scope, not nested under any current-level param but instead at the same level as the current scope. @param options [Hash] options to control how this new scope behaves @option options :dependent_on [Symbol] if given, specifies that this
scope should only validate if this parameter from the above scope is present
@yield parameter scope
Source
# File lib/grape/validations/params_scope.rb, line 263 def new_scope(attrs, optional = false, &block) # if required params are grouped and no type or unsupported type is provided, raise an error type = attrs[1] ? attrs[1][:type] : nil if attrs.first && !optional raise Grape::Exceptions::MissingGroupType if type.nil? raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type) end self.class.new( api: @api, element: attrs.first, element_renamed: attrs[1][:as], parent: self, optional: optional, type: type || Array, group: @group, &block ) end
Returns a new parameter scope, subordinate to the current one and nested under the parameter corresponding to ‘attrs.first`. @param attrs [Array] the attributes passed to the `requires` or
`optional` invocation that opened this scope.
@param optional [Boolean] whether the parameter this are nested under
is optional or not (and hence, whether this block's params will be).
@yield parameter scope
Source
# File lib/grape/validations/params_scope.rb, line 511 def options_key?(type, key, validations) validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil? end
Source
# File lib/grape/validations/params_scope.rb, line 212 def push_renamed_param(path, new_name) base = @api.route_setting(:renamed_params) || {} base[Array(path).map(&:to_s)] = new_name.to_s @api.route_setting(:renamed_params, base) end
Add a new parameter which should be renamed when using the declared method.
@param path [Array<String, Symbol>] the full path of the parameter
(including the parameter name as last array element)
@param new_name [String, Symbol] the new name of the parameter (the
renamed name, with the +as: ...+ semantic)
Source
# File lib/grape/validations/params_scope.rb, line 238 def require_optional_fields(context, opts) optional_fields = opts[:using].keys unless context == :all except_fields = Array.wrap(opts[:except]) optional_fields.delete_if { |f| except_fields.include?(f) } end optional_fields.each do |field| field_opts = opts[:using][field] optional(field, field_opts) if field_opts end end
Source
# File lib/grape/validations/params_scope.rb, line 218 def require_required_and_optional_fields(context, opts) if context == :all optional_fields = Array.wrap(opts[:except]) required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) } else # context == :none required_fields = Array.wrap(opts[:except]) optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) } end required_fields.each do |field| field_opts = opts[:using][field] raise ArgumentError, "required field not exist: #{field}" unless field_opts requires(field, field_opts) end optional_fields.each do |field| field_opts = opts[:using][field] optional(field, field_opts) if field_opts end end
Source
# File lib/grape/validations/params_scope.rb, line 479 def validate(type, options, attrs, doc, opts) validator_options = { attributes: attrs, options: options, required: doc.required, params_scope: self, opts: opts, validator_class: Validations.require_validator(type) } @api.namespace_stackable(:validations, validator_options) end
Source
# File lib/grape/validations/params_scope.rb, line 250 def validate_attributes(attrs, opts, &block) validations = opts.clone validations[:type] ||= Array if block validates(attrs, validations) end
Source
# File lib/grape/validations/params_scope.rb, line 491 def validate_value_coercion(coerce_type, *values_list) return unless coerce_type coerce_type = coerce_type.first if coerce_type.is_a?(Enumerable) values_list.each do |values| next if !values || values.is_a?(Proc) value_types = values.is_a?(Range) ? [values.begin, values.end].compact : values value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type) end end
Source
# File lib/grape/validations/params_scope.rb, line 325 def validates(attrs, validations) doc = AttributesDoc.new @api, self doc.extract_details validations coerce_type = infer_coercion(validations) doc.type = coerce_type default = validations[:default] if (values_hash = validations[:values]).is_a? Hash values = values_hash[:value] # NB: excepts is deprecated excepts = values_hash[:except] else values = validations[:values] end doc.values = values except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values] # NB. values and excepts should be nil, Proc, Array, or Range. # Specifically, values should NOT be a Hash # use values or excepts to guess coerce type when stated type is Array coerce_type = guess_coerce_type(coerce_type, values, except_values, excepts) # default value should be present in values array, if both exist and are not procs check_incompatible_option_values(default, values, except_values, excepts) # type should be compatible with values array, if both exist validate_value_coercion(coerce_type, values, except_values, excepts) doc.document attrs opts = derive_validator_options(validations) # Validate for presence before any other validators validates_presence(validations, attrs, doc, opts) # Before we run the rest of the validators, let's handle # whatever coercion so that we are working with correctly # type casted values coerce_type validations, attrs, doc, opts validations.each do |type, options| # Don't try to look up validators for documentation params that don't have one. next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type) validate(type, options, attrs, doc, opts) end end
Source
# File lib/grape/validations/params_scope.rb, line 530 def validates_presence(validations, attrs, doc, opts) return unless validations.key?(:presence) && validations[:presence] validate('presence', validations.delete(:presence), attrs, doc, opts) validations.delete(:message) if validations.key?(:message) end