class Grape::Validations::ParamsScope

def all_element_blank?(scoped_params)

def all_element_blank?(scoped_params)
  scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)
end

def attr_meets_dependency?(params)

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

def brackets(val)

def brackets(val)
  "[#{val}]" if val
end

def check_coerce_with(validations)

method.
+JSON+ as a type since this defines its own coercion
We do not allow coercion without a type, nor with
Enforce correct usage of :coerce_with parameter.
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

def check_incompatible_option_values(default, values, except_values, excepts)

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

def coerce_type(validations, attrs, doc, opts)

validations.
parameter, and needs to be run before most other
composited from more than one +requires+/+optional+
This validation has special handling since it is
if any has been specified.
Add type coercion validation to this scope,
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

def configuration

def configuration
  @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
end

def configure_declared_params

Pushes declared params to parent or settings
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

def derive_validator_options(validations)

some validators might influence others, so their options should be shared
Validators don't have access to each other and they don't need, however,
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

def extract_message_option(attrs)

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

def full_name(name, index: nil)

Returns:
  • (String) - the proper attribute name, with nesting considered.
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

def full_path

Returns:
  • (Array) - the nesting/path of the current parameter scope
def full_path
  if nested?
    (@parent.full_path + [@element])
  elsif lateral?
    @parent.full_path
  else
    []
  end
end

def guess_coerce_type(coerce_type, *values_list)

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

def infer_coercion(validations)

Raises:
  • (ArgumentError) - if the given type options are invalid

Returns:
  • (class-like) - type to which the parameter will be coerced

Parameters:
  • validations (Hash) -- list of validations supplied to the
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

def initialize(opts, &block)

Other tags:
    Yield: - the instance context, open for parameter definitions

Options Hash: (**opts)
  • :dependent_on (Symbol) -- if present, this scope should only
  • :type (Hash) -- group options for this scope
  • :type (Class) -- a type meant to govern this scope (deprecated)
  • :optional (Boolean) -- whether or not this scope needs to have
  • :api (API) -- the API endpoint to modify
  • :parent (ParamsScope) -- the scope containing this scope
  • :element_renamed (Symbol, nil) -- whenever this scope should
  • :element (Symbol) -- the element that contains this scope; for

Parameters:
  • opts (Hash) -- options for this scope
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]
  @declared_params = []
  @index = nil
  instance_eval(&block) if block
  configure_declared_params
end

def lateral?

Returns:
  • (Boolean) - whether or not this scope is lateral
def lateral?
  @parent && !@element
end

def meets_dependency?(params, request_params)

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)
  return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
  meets_hash_dependency?(params)
end

def meets_hash_dependency?(params)

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

def nested?

Returns:
  • (Boolean) - whether or not this scope is nested
def nested?
  @parent && @element
end

def new_group_scope(attrs, &block)

Other tags:
    Yield: - parameter scope

Parameters:
  • attrs (Array) -- the attributes passed to the `requires` or
def new_group_scope(attrs, &block)
  self.class.new(api: @api, parent: self, group: attrs.first, &block)
end

def new_lateral_scope(options, &block)

Other tags:
    Yield: - parameter scope

Options Hash: (**options)
  • :dependent_on (Symbol) -- if given, specifies that this

Parameters:
  • options (Hash) -- options to control how this new scope behaves
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

def new_scope(attrs, optional = false, &block)

Other tags:
    Yield: - parameter scope

Parameters:
  • optional (Boolean) -- whether the parameter this are nested under
  • attrs (Array) -- the attributes passed to the `requires` or
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

def options_key?(type, key, validations)

def options_key?(type, key, validations)
  validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?
end

def push_declared_params(attrs, opts = {})

Parameters:
  • attrs (Array) -- (see Grape::DSL::Parameters#requires)
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

def push_renamed_param(path, new_name)

Parameters:
  • new_name (String, Symbol) -- the new name of the parameter (the
  • path (Array) -- the full path of the parameter
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

def require_optional_fields(context, opts)

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

def require_required_and_optional_fields(context, opts)

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

def required?

Returns:
  • (Boolean) - whether or not this scope needs to be present, or can
def required?
  !@optional
end

def reset_index

def reset_index
  @index = nil
end

def root?

Returns:
  • (Boolean) - whether or not this scope is the root-level scope
def root?
  !@parent
end

def should_validate?(parameters)

Returns:
  • (Boolean) - whether or not this entire scope needs to be
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

def validate(type, options, attrs, doc, opts)

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

def validate_attributes(attrs, opts, &block)

def validate_attributes(attrs, opts, &block)
  validations = opts.clone
  validations[:type] ||= Array if block
  validates(attrs, validations)
end

def validate_value_coercion(coerce_type, *values_list)

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

def validates(attrs, validations)

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

def validates_presence(validations, attrs, doc, opts)

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