-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathtypes.rb
222 lines (195 loc) · 7.32 KB
/
types.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# frozen_string_literal: true
module Grape
module Validations
# 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 {Grape::Dsl::Parameters#requires}
# and {Grape::Dsl::Parameters#optional}. The main
# entry point for this process is {Types.build_coercer}.
module Types
# Types representing a single value, which are coerced.
PRIMITIVES = [
# Numerical
Integer,
Float,
BigDecimal,
Numeric,
# Date/time
Date,
DateTime,
Time,
# Misc
Grape::API::Boolean,
String,
Symbol,
TrueClass,
FalseClass
].freeze
# Types representing data structures.
STRUCTURES = [
Hash,
Array,
Set
].freeze
# Special custom types provided by Grape.
SPECIAL = {
JSON => Json,
Array[JSON] => JsonArray,
::File => File,
Rack::Multipart::UploadedFile => File
}.freeze
GROUPS = [
Array,
Hash,
JSON,
Array[JSON]
].freeze
# Is the given class a primitive type as recognized by Grape?
#
# @param type [Class] type to check
# @return [Boolean] whether or not the type is known by Grape as a valid
# type for a single value
def self.primitive?(type)
PRIMITIVES.include?(type)
end
# Is the given class a standard data structure (collection or map)
# as recognized by Grape?
#
# @param type [Class] type to check
# @return [Boolean] whether or not the type is known by Grape as a valid
# data structure type
def self.structure?(type)
STRUCTURES.include?(type)
end
# 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.
#
# @param type [Array<Class>,Set<Class>] type (or type list!) to check
# @return [Boolean] +true+ if the given value will be treated as
# a list of types.
def self.multiple?(type)
(type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
end
# 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 {Types::SPECIAL}.
#
# @param type [Class] type to check
# @return [Boolean] +true+ if special routines are available
def self.special?(type)
SPECIAL.key? type
end
# Is the declared type a supported group type?
# Currently supported group types are Array, Hash, JSON, and Array[JSON]
#
# @param type [Array<Class>,Class] type to check
# @return [Boolean] +true+ if the type is a supported group type
def self.group?(type)
GROUPS.include? type
end
# A valid custom type must implement a class-level `parse` method, taking
# one String argument and returning the parsed value in its correct type.
#
# @param type [Class] type to check
# @return [Boolean] whether or not the type can be used as a custom type
def self.custom?(type)
!primitive?(type) &&
!structure?(type) &&
!multiple?(type) &&
type.respond_to?(:parse) &&
type.method(:parse).arity == 1
end
# Is the declared type an +Array+ or +Set+ of a {#custom?} type?
#
# @param type [Array<Class>,Class] type to check
# @return [Boolean] true if +type+ is a collection of a type that implements
# its own +#parse+ method.
def self.collection_of_custom?(type)
(type.is_a?(Array) || type.is_a?(Set)) &&
type.length == 1 &&
(custom?(type.first) || special?(type.first))
end
def self.map_special(type)
SPECIAL.fetch(type, type)
end
# Chooses the best coercer for the given type. For example, if the type
# is Integer, it will return a coercer which will be able to coerce a value
# to the integer.
#
# There are a few very special coercers which might be returned.
#
# +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
# the given type implies values in an array with different types.
# For example, +[Integer, String]+ allows integer and string values in
# an array.
#
# +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
# a method is specified by a user with +coerce_with+ option or the user
# specifies a custom type which implements requirments of
# +Grape::Types::CustomTypeCoercer+.
#
# +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
# previous one, but it expects an array or set of values having a custom
# type implemented by the user.
#
# There is also a group of custom types implemented by Grape, check
# +Grape::Validations::Types::SPECIAL+ to get the full list.
#
# @param type [Class] the type to which input strings
# should be coerced
# @param method [Class,#call] the coercion method to use
# @return [Object] object to be used
# for coercion and type validation
def self.build_coercer(type, method: nil, strict: false)
cache_instance(type, method, strict) do
create_coercer_instance(type, method, strict)
end
end
def self.create_coercer_instance(type, method, strict)
# Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
type = Types.map_special(type)
# Use a special coercer for multiply-typed parameters.
if Types.multiple?(type)
MultipleTypeCoercer.new(type, method)
# Use a special coercer for custom types and coercion methods.
elsif method || Types.custom?(type)
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)
Types::CustomTypeCollectionCoercer.new(
Types.map_special(type.first), type.is_a?(Set)
)
else
DryTypeCoercer.coercer_instance_for(type, strict)
end
end
def self.cache_instance(type, method, strict, &_block)
key = cache_key(type, method, strict)
return @__cache[key] if @__cache.key?(key)
instance = yield
@__cache_write_lock.synchronize do
@__cache[key] = instance
end
instance
end
def self.cache_key(type, method, strict)
[type, method, strict].each_with_object(+'_') do |val, memo|
next if val.nil?
memo << '_' << val.to_s
end
end
instance_variable_set(:@__cache, {})
instance_variable_set(:@__cache_write_lock, Mutex.new)
end
end
end