Module: Grape::Validations::Types

Defined in:
lib/grape/validations/types.rb,
lib/grape/validations/types/file.rb,
lib/grape/validations/types/json.rb,
lib/grape/validations/types/build_coercer.rb,
lib/grape/validations/types/custom_type_coercer.rb,
lib/grape/validations/types/multiple_type_coercer.rb,
lib/grape/validations/types/variant_collection_coercer.rb,
lib/grape/validations/types/custom_type_collection_coercer.rb

Overview

Module for code related to grape’s system for coercion and type validation of incoming request parameters.

Grape uses a number of tests and assertions to work out exactly how a parameter should be handled, based on the type and coerce_with options that may be supplied to Dsl::Parameters#requires and Dsl::Parameters#optional. The main entry point for this process is Types.build_coercer.

Defined Under Namespace

Classes: CustomTypeCoercer, CustomTypeCollectionCoercer, File, InvalidValue, Json, JsonArray, MultipleTypeCoercer, VariantCollectionCoercer

Constant Summary collapse

PRIMITIVES =

Types representing a single value, which are coerced through Virtus or special logic in Grape.

[
  # Numerical
  Integer,
  Float,
  BigDecimal,
  Numeric,

  # Date/time
  Date,
  DateTime,
  Time,

  # Misc
  Virtus::Attribute::Boolean,
  String,
  Symbol,
  Rack::Multipart::UploadedFile
].freeze
STRUCTURES =

Types representing data structures.

[
  Hash,
  Array,
  Set
].freeze
SPECIAL =

Types for which Grape provides special coercion and type-checking logic.

{
  JSON => Json,
  Array[JSON] => JsonArray,
  ::File => File,
  Rack::Multipart::UploadedFile => File
}.freeze
GROUPS =
[
  Array,
  Hash,
  JSON,
  Array[JSON]
].freeze

Class Method Summary collapse

Class Method Details

.build_coercer(type, method = nil) ⇒ Virtus::Attribute

Work out the Virtus::Attribute object to use for coercing strings to the given type. Coercion method will be inferred if none is supplied.

If a Virtus::Attribute object already built with Virtus::Attribute.build is supplied as the type it will be returned and method will be ignored.

See CustomTypeCoercer for further details about coercion and type-checking inference.

Parameters:

  • type (Class)

    the type to which input strings should be coerced

  • method (Class, #call) (defaults to: nil)

    the coercion method to use

Returns:

  • (Virtus::Attribute)

    object to be used for coercion and type validation



22
23
24
25
26
# File 'lib/grape/validations/types/build_coercer.rb', line 22

def self.build_coercer(type, method = nil)
  cache_instance(type, method) do
    create_coercer_instance(type, method)
  end
end

.cache_instance(type, method, &_block) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/grape/validations/types/build_coercer.rb', line 74

def self.cache_instance(type, method, &_block)
  key = cache_key(type, method)

  return @__cache[key] if @__cache.key?(key)

  instance = yield

  @__cache_write_lock.synchronize do
    @__cache[key] = instance
  end

  instance
end

.cache_key(type, method) ⇒ Object



88
89
90
# File 'lib/grape/validations/types/build_coercer.rb', line 88

def self.cache_key(type, method)
  [type, method].compact.map(&:to_s).join('_')
end

.collection_of_custom?(type) ⇒ Boolean

Is the declared type an Array or Set of a #custom? type?

Parameters:

  • type (Array<Class>, Class)

    type to check

Returns:

  • (Boolean)

    true if type is a collection of a type that implements its own #parse method.



166
167
168
169
170
# File 'lib/grape/validations/types.rb', line 166

def self.collection_of_custom?(type)
  (type.is_a?(Array) || type.is_a?(Set)) &&
    type.length == 1 &&
    custom?(type.first)
end

.create_coercer_instance(type, method = nil) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/grape/validations/types/build_coercer.rb', line 28

def self.create_coercer_instance(type, method = nil)
  # Accept pre-rolled virtus attributes without interference
  return type if type.is_a? Virtus::Attribute

  converter_options = {
    nullify_blank: true
  }
  conversion_type = if method == JSON
                      Object
                      # because we want just parsed JSON content:
                      # if type is Array and data is `"{}"`
                      # result will be [] because Virtus converts hashes
                      # to arrays
                    else
                      type
                    end

  # Use a special coercer for multiply-typed parameters.
  if Types.multiple?(type)
    converter_options[:coercer] = Types::MultipleTypeCoercer.new(type, method)
    conversion_type = Object

  # Use a special coercer for custom types and coercion methods.
  elsif method || Types.custom?(type)
    converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method)

  # Special coercer for collections of types that implement a parse method.
  # CustomTypeCoercer (above) already handles such types when an explicit coercion
  # method is supplied.
  elsif Types.collection_of_custom?(type)
    converter_options[:coercer] = Types::CustomTypeCollectionCoercer.new(
      type.first, type.is_a?(Set)
    )

  # Grape swaps in its own Virtus::Attribute implementations
  # for certain special types that merit first-class support
  # (but not if a custom coercion method has been supplied).
  elsif Types.special?(type)
    conversion_type = Types::SPECIAL[type]
  end

  # Virtus will infer coercion and validation rules
  # for many common ruby types.
  Virtus::Attribute.build(conversion_type, converter_options)
end

.custom?(type) ⇒ Boolean

A valid custom type must implement a class-level ‘parse` method, taking one String argument and returning the parsed value in its correct type.

Parameters:

  • type (Class)

    type to check

Returns:

  • (Boolean)

    whether or not the type can be used as a custom type



151
152
153
154
155
156
157
158
159
# File 'lib/grape/validations/types.rb', line 151

def self.custom?(type)
  !primitive?(type) &&
    !structure?(type) &&
    !multiple?(type) &&
    !recognized?(type) &&
    !special?(type) &&
    type.respond_to?(:parse) &&
    type.method(:parse).arity == 1
end

.group?(type) ⇒ Boolean

Is the declared type a supported group type? Currently supported group types are Array, Hash, JSON, and Array

Parameters:

  • type (Array<Class>, Class)

    type to check

Returns:

  • (Boolean)

    true if the type is a supported group type



142
143
144
# File 'lib/grape/validations/types.rb', line 142

def self.group?(type)
  GROUPS.include? type
end

.multiple?(type) ⇒ Boolean

Is the declared type in fact an array of multiple allowed types? For example the declaration types: [Integer,String] will attempt first to coerce given values to integer, but will also accept any other string.

Parameters:

  • type (Array<Class>, Set<Class>)

    type (or type list!) to check

Returns:

  • (Boolean)

    true if the given value will be treated as a list of types.



103
104
105
# File 'lib/grape/validations/types.rb', line 103

def self.multiple?(type)
  (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
end

.primitive?(type) ⇒ Boolean

Is the given class a primitive type as recognized by Grape?

Parameters:

  • type (Class)

    type to check

Returns:

  • (Boolean)

    whether or not the type is known by Grape as a valid type for a single value



79
80
81
# File 'lib/grape/validations/types.rb', line 79

def self.primitive?(type)
  PRIMITIVES.include?(type)
end

.recognized?(type) ⇒ Boolean

Does the given class implement a type system that Grape (i.e. the underlying virtus attribute system) supports out-of-the-box? Currently supported are axiom-types and virtus.

The type will be passed to Virtus::Attribute.build, and the resulting attribute object will be expected to respond correctly to coerce and value_coerced?.

Parameters:

  • type (Class)

    type to check

Returns:

  • (Boolean)

    true where the type is recognized



118
119
120
121
122
123
124
# File 'lib/grape/validations/types.rb', line 118

def self.recognized?(type)
  return false if type.is_a?(Array) || type.is_a?(Set)

  type.is_a?(Virtus::Attribute) ||
    type.ancestors.include?(Axiom::Types::Type) ||
    type.include?(Virtus::Model::Core)
end

.special?(type) ⇒ Boolean

Does Grape provide special coercion and validation routines for the given class? This does not include automatic handling for primitives, structures and otherwise recognized types. See SPECIAL.

Parameters:

  • type (Class)

    type to check

Returns:

  • (Boolean)

    true if special routines are available



133
134
135
# File 'lib/grape/validations/types.rb', line 133

def self.special?(type)
  SPECIAL.key? type
end

.structure?(type) ⇒ Boolean

Note:

This method does not yet consider ‘complex types’, which inherit Virtus.model.

Is the given class a standard data structure (collection or map) as recognized by Grape?

Parameters:

  • type (Class)

    type to check

Returns:

  • (Boolean)

    whether or not the type is known by Grape as a valid data structure type



91
92
93
# File 'lib/grape/validations/types.rb', line 91

def self.structure?(type)
  STRUCTURES.include?(type)
end