ruby-grape/grape

View on GitHub
lib/grape/validations/types/primitive_coercer.rb

Summary

Maintainability
A
55 mins
Test Coverage
# frozen_string_literal: true

require_relative 'dry_type_coercer'

module Grape
  module Validations
    module Types
      # Coerces the given value to a type defined via a +type+ argument during
      # initialization. When +strict+ is true, it doesn't coerce a value but check
      # that it has the proper type.
      class PrimitiveCoercer < DryTypeCoercer
        MAPPING = {
          Grape::API::Boolean => DryTypes::Params::Bool,
          BigDecimal => DryTypes::Params::Decimal,
          Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
          TrueClass => DryTypes::Params::Bool.constrained(eql: true),
          FalseClass => DryTypes::Params::Bool.constrained(eql: false),

          # unfortunately, a +Params+ scope doesn't contain String
          String => DryTypes::Coercible::String
        }.freeze

        STRICT_MAPPING = {
          Grape::API::Boolean => DryTypes::Strict::Bool,
          BigDecimal => DryTypes::Strict::Decimal,
          Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
          TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
          FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
        }.freeze

        def initialize(type, strict = false)
          super

          @type = type

          @coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
            scope.const_get(type.name, false)
          rescue NameError
            raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])

            type
          end
        end

        def call(val)
          return InvalidValue.new if reject?(val)
          return nil if val.nil? || treat_as_nil?(val)

          super
        end

        protected

        attr_reader :type

        # This method maintains logic which was defined by Virtus. For example,
        # dry-types is ok to convert an array or a hash to a string, it is supported,
        # but Virtus wouldn't accept it. So, this method only exists to not introduce
        # breaking changes.
        def reject?(val)
          (val.is_a?(Array) && type == String) ||
            (val.is_a?(String) && type == Hash) ||
            (val.is_a?(Hash) && type == String)
        end

        # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
        # absence of a value and coerces it into nil. See a discussion there
        # https://github.com/ruby-grape/grape/pull/2045
        def treat_as_nil?(val)
          val == '' && type != String
        end
      end
    end
  end
end