grape 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Appraisals +9 -4
- data/CHANGELOG.md +28 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +166 -0
- data/README.md +305 -163
- data/Rakefile +30 -33
- data/UPGRADING.md +31 -0
- data/benchmark/simple.rb +27 -0
- data/gemfiles/rack_1.5.2.gemfile +13 -0
- data/gemfiles/rails_3.gemfile +2 -2
- data/gemfiles/rails_4.gemfile +1 -2
- data/grape.gemspec +5 -4
- data/lib/grape.rb +9 -5
- data/lib/grape/dsl/configuration.rb +5 -2
- data/lib/grape/dsl/helpers.rb +8 -3
- data/lib/grape/dsl/inside_route.rb +67 -44
- data/lib/grape/dsl/parameters.rb +21 -12
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +3 -4
- data/lib/grape/endpoint.rb +63 -28
- data/lib/grape/error_formatter/base.rb +6 -6
- data/lib/grape/exceptions/base.rb +5 -5
- data/lib/grape/exceptions/invalid_version_header.rb +10 -0
- data/lib/grape/formatter/serializable_hash.rb +3 -2
- data/lib/grape/locale/en.yml +4 -1
- data/lib/grape/middleware/auth/base.rb +2 -2
- data/lib/grape/middleware/auth/dsl.rb +1 -1
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/base.rb +7 -4
- data/lib/grape/middleware/error.rb +3 -2
- data/lib/grape/middleware/filter.rb +1 -1
- data/lib/grape/middleware/formatter.rb +47 -44
- data/lib/grape/middleware/globals.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
- data/lib/grape/middleware/versioner/header.rb +113 -50
- data/lib/grape/middleware/versioner/param.rb +5 -8
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
- data/lib/grape/middleware/versioner/path.rb +3 -6
- data/lib/grape/path.rb +3 -3
- data/lib/grape/request.rb +40 -0
- data/lib/grape/util/content_types.rb +9 -9
- data/lib/grape/util/env.rb +22 -0
- data/lib/grape/util/strict_hash_configuration.rb +2 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -3
- data/lib/grape/validations/params_scope.rb +83 -15
- data/lib/grape/validations/types.rb +144 -0
- data/lib/grape/validations/types/build_coercer.rb +53 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
- data/lib/grape/validations/types/file.rb +28 -0
- data/lib/grape/validations/types/json.rb +65 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
- data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
- data/lib/grape/validations/validators/all_or_none.rb +1 -1
- data/lib/grape/validations/validators/allow_blank.rb +3 -3
- data/lib/grape/validations/validators/base.rb +7 -0
- data/lib/grape/validations/validators/coerce.rb +31 -42
- data/lib/grape/validations/validators/presence.rb +2 -3
- data/lib/grape/validations/validators/regexp.rb +2 -4
- data/lib/grape/validations/validators/values.rb +3 -3
- data/lib/grape/version.rb +1 -1
- data/pkg/grape-0.13.0.gem +0 -0
- data/spec/grape/api/custom_validations_spec.rb +5 -4
- data/spec/grape/api/deeply_included_options_spec.rb +7 -7
- data/spec/grape/api/nested_helpers_spec.rb +4 -2
- data/spec/grape/api/shared_helpers_spec.rb +8 -8
- data/spec/grape/api_spec.rb +88 -54
- data/spec/grape/dsl/configuration_spec.rb +13 -0
- data/spec/grape/dsl/helpers_spec.rb +16 -2
- data/spec/grape/dsl/inside_route_spec.rb +3 -2
- data/spec/grape/dsl/parameters_spec.rb +0 -6
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint_spec.rb +61 -20
- data/spec/grape/entity_spec.rb +10 -8
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
- data/spec/grape/integration/rack_spec.rb +3 -2
- data/spec/grape/middleware/base_spec.rb +7 -5
- data/spec/grape/middleware/error_spec.rb +16 -15
- data/spec/grape/middleware/exception_spec.rb +45 -43
- data/spec/grape/middleware/formatter_spec.rb +34 -0
- data/spec/grape/middleware/versioner/header_spec.rb +79 -47
- data/spec/grape/path_spec.rb +10 -10
- data/spec/grape/presenters/presenter_spec.rb +2 -2
- data/spec/grape/request_spec.rb +100 -0
- data/spec/grape/validations/params_scope_spec.rb +11 -9
- data/spec/grape/validations/types_spec.rb +95 -0
- data/spec/grape/validations/validators/coerce_spec.rb +335 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -15
- data/spec/grape/validations_spec.rb +53 -24
- data/spec/shared/versioning_examples.rb +2 -2
- data/spec/spec_helper.rb +0 -1
- data/spec/support/versioned_helpers.rb +2 -2
- metadata +51 -13
- data/.gitignore +0 -46
- data/.rspec +0 -2
- data/.rubocop.yml +0 -7
- data/.rubocop_todo.yml +0 -84
- data/.travis.yml +0 -20
- data/.yardopts +0 -2
- data/lib/grape/http/request.rb +0 -35
- data/lib/grape/util/parameter_types.rb +0 -58
- data/spec/grape/util/parameter_types_spec.rb +0 -54
@@ -21,20 +21,17 @@ module Grape
|
|
21
21
|
class Param < Base
|
22
22
|
def default_options
|
23
23
|
{
|
24
|
-
parameter: 'apiver'
|
24
|
+
parameter: 'apiver'.freeze
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
28
28
|
def before
|
29
29
|
paramkey = options[:parameter]
|
30
30
|
potential_version = Rack::Utils.parse_nested_query(env[Grape::Http::Headers::QUERY_STRING])[paramkey]
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
env['api.version'] = potential_version
|
36
|
-
env['rack.request.query_hash'].delete(paramkey) if env.key? 'rack.request.query_hash'
|
37
|
-
end
|
31
|
+
return if potential_version.nil?
|
32
|
+
throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' } if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
33
|
+
env[Grape::Env::API_VERSION] = potential_version
|
34
|
+
env[Grape::Env::RACK_REQUEST_QUERY_HASH].delete(paramkey) if env.key? Grape::Env::RACK_REQUEST_QUERY_HASH
|
38
35
|
end
|
39
36
|
end
|
40
37
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rack
|
2
|
+
module Accept
|
3
|
+
module Header
|
4
|
+
class << self
|
5
|
+
# Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44
|
6
|
+
def parse_media_type(media_type)
|
7
|
+
# see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names
|
8
|
+
m = media_type.to_s.match(%r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$})
|
9
|
+
m ? [m[1], m[2], m[3] || ''] : []
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MediaType
|
15
|
+
def parse_media_type(media_type)
|
16
|
+
Header.parse_media_type(media_type)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -33,12 +33,9 @@ module Grape
|
|
33
33
|
|
34
34
|
pieces = path.split('/')
|
35
35
|
potential_version = pieces[1]
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
env['api.version'] = potential_version
|
41
|
-
end
|
36
|
+
return unless potential_version =~ options[:pattern]
|
37
|
+
throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
|
38
|
+
env[Grape::Env::API_VERSION] = potential_version
|
42
39
|
end
|
43
40
|
|
44
41
|
private
|
data/lib/grape/path.rb
CHANGED
@@ -29,18 +29,18 @@ module Grape
|
|
29
29
|
!!(settings[:version] && settings[:version_options][:using] == :path)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def namespace?
|
33
33
|
namespace && namespace.to_s =~ /^\S/ && namespace != '/'
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def path?
|
37
37
|
raw_path && raw_path.to_s =~ /^\S/ && raw_path != '/'
|
38
38
|
end
|
39
39
|
|
40
40
|
def suffix
|
41
41
|
if uses_specific_format?
|
42
42
|
"(.#{settings[:format]})"
|
43
|
-
elsif !uses_path_versioning? || (
|
43
|
+
elsif !uses_path_versioning? || (namespace? || path?)
|
44
44
|
'(.:format)'
|
45
45
|
else
|
46
46
|
'(/.:format)'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Grape
|
2
|
+
class Request < Rack::Request
|
3
|
+
HTTP_PREFIX = 'HTTP_'.freeze
|
4
|
+
|
5
|
+
alias_method :rack_params, :params
|
6
|
+
|
7
|
+
def params
|
8
|
+
@params ||= build_params
|
9
|
+
end
|
10
|
+
|
11
|
+
def headers
|
12
|
+
@headers ||= build_headers
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def build_params
|
18
|
+
params = Hashie::Mash.new(rack_params)
|
19
|
+
if env[Grape::Env::RACK_ROUTING_ARGS]
|
20
|
+
args = env[Grape::Env::RACK_ROUTING_ARGS].dup
|
21
|
+
# preserve version from query string parameters
|
22
|
+
args.delete(:version)
|
23
|
+
args.delete(:route_info)
|
24
|
+
params.deep_merge!(args)
|
25
|
+
end
|
26
|
+
params
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_headers
|
30
|
+
headers = {}
|
31
|
+
env.each_pair do |k, v|
|
32
|
+
next unless k.to_s.start_with? HTTP_PREFIX
|
33
|
+
|
34
|
+
k = k[5..-1].split('_').each(&:capitalize!).join('-')
|
35
|
+
headers[k] = v
|
36
|
+
end
|
37
|
+
headers
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module Grape
|
2
2
|
module ContentTypes
|
3
3
|
# Content types are listed in order of preference.
|
4
|
-
CONTENT_TYPES =
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
CONTENT_TYPES = {
|
5
|
+
xml: 'application/xml',
|
6
|
+
serializable_hash: 'application/json',
|
7
|
+
json: 'application/json',
|
8
|
+
binary: 'application/octet-stream',
|
9
|
+
txt: 'text/plain'
|
10
|
+
}
|
11
11
|
|
12
12
|
def self.content_types_for_settings(settings)
|
13
|
-
return
|
13
|
+
return if settings.blank?
|
14
14
|
|
15
|
-
settings.each_with_object(
|
15
|
+
settings.each_with_object({}) { |value, result| result.merge!(value) }
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.content_types_for(from_settings)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Grape
|
2
|
+
module Env
|
3
|
+
API_VERSION = 'api.version'.freeze
|
4
|
+
API_ENDPOINT = 'api.endpoint'.freeze
|
5
|
+
API_REQUEST_INPUT = 'api.request.input'.freeze
|
6
|
+
API_REQUEST_BODY = 'api.request.body'.freeze
|
7
|
+
API_TYPE = 'api.type'.freeze
|
8
|
+
API_SUBTYPE = 'api.subtype'.freeze
|
9
|
+
API_VENDOR = 'api.vendor'.freeze
|
10
|
+
API_FORMAT = 'api.format'.freeze
|
11
|
+
|
12
|
+
RACK_INPUT = 'rack.input'.freeze
|
13
|
+
RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'.freeze
|
14
|
+
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'.freeze
|
15
|
+
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'.freeze
|
16
|
+
RACK_ROUTING_ARGS = 'rack.routing_args'.freeze
|
17
|
+
|
18
|
+
GRAPE_REQUEST = 'grape.request'.freeze
|
19
|
+
GRAPE_REQUEST_HEADERS = 'grape.request.headers'.freeze
|
20
|
+
GRAPE_REQUEST_PARAMS = 'grape.request.params'.freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -64,7 +64,8 @@ module Grape
|
|
64
64
|
end
|
65
65
|
|
66
66
|
define_method 'to_hash' do
|
67
|
-
merge_hash =
|
67
|
+
merge_hash = {}
|
68
|
+
setting_name.each_key { |k| merge_hash[k] = send("#{k}_context").to_hash }
|
68
69
|
|
69
70
|
@settings.to_hash.merge(
|
70
71
|
merge_hash
|
@@ -3,15 +3,20 @@ module Grape
|
|
3
3
|
class AttributesIterator
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
+
attr_reader :scope
|
7
|
+
|
6
8
|
def initialize(validator, scope, params)
|
9
|
+
@scope = scope
|
7
10
|
@attrs = validator.attrs
|
8
|
-
@params = scope.params(params)
|
9
|
-
@params = (@params.is_a?(Array) ? @params : [@params])
|
11
|
+
@params = Array.wrap(scope.params(params))
|
10
12
|
end
|
11
13
|
|
12
14
|
def each
|
13
15
|
@params.each do |resource_params|
|
14
|
-
@attrs.
|
16
|
+
@attrs.each_with_index do |attr_name, index|
|
17
|
+
if resource_params.is_a?(Hash) && resource_params[attr_name].is_a?(Array)
|
18
|
+
scope.index = index
|
19
|
+
end
|
15
20
|
yield resource_params, attr_name
|
16
21
|
end
|
17
22
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class ParamsScope
|
4
|
-
attr_accessor :element, :parent
|
4
|
+
attr_accessor :element, :parent, :index
|
5
5
|
|
6
6
|
include Grape::DSL::Parameters
|
7
7
|
|
@@ -46,7 +46,7 @@ module Grape
|
|
46
46
|
case
|
47
47
|
when nested?
|
48
48
|
# Find our containing element's name, and append ours.
|
49
|
-
"#{@parent.full_name(@element)}[#{name}]"
|
49
|
+
"#{@parent.full_name(@element)}#{parent_index}[#{name}]"
|
50
50
|
when lateral?
|
51
51
|
# Find the name of the element as if it was at the
|
52
52
|
# same nesting level as our parent.
|
@@ -57,6 +57,10 @@ module Grape
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
def parent_index
|
61
|
+
"[#{@parent.index}]" if @parent.present? && @parent.index.present?
|
62
|
+
end
|
63
|
+
|
60
64
|
# @return [Boolean] whether or not this scope is the root-level scope
|
61
65
|
def root?
|
62
66
|
!@parent
|
@@ -137,7 +141,7 @@ module Grape
|
|
137
141
|
type = attrs[1] ? attrs[1][:type] : nil
|
138
142
|
if attrs.first && !optional
|
139
143
|
fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
|
140
|
-
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
|
144
|
+
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash, JSON, Array[JSON]].include?(type)
|
141
145
|
end
|
142
146
|
|
143
147
|
opts = attrs[1] || { type: Array }
|
@@ -177,10 +181,7 @@ module Grape
|
|
177
181
|
def validates(attrs, validations)
|
178
182
|
doc_attrs = { required: validations.keys.include?(:presence) }
|
179
183
|
|
180
|
-
|
181
|
-
validations[:coerce] = validations.delete(:type) if validations.key?(:type)
|
182
|
-
|
183
|
-
coerce_type = validations[:coerce]
|
184
|
+
coerce_type = infer_coercion(validations)
|
184
185
|
|
185
186
|
doc_attrs[:type] = coerce_type.to_s if coerce_type
|
186
187
|
|
@@ -212,19 +213,87 @@ module Grape
|
|
212
213
|
validations.delete(:presence)
|
213
214
|
end
|
214
215
|
|
215
|
-
# Before we run the rest of the validators,
|
216
|
+
# Before we run the rest of the validators, let's handle
|
216
217
|
# whatever coercion so that we are working with correctly
|
217
218
|
# type casted values
|
218
|
-
|
219
|
-
validate('coerce', validations[:coerce], attrs, doc_attrs)
|
220
|
-
validations.delete(:coerce)
|
221
|
-
end
|
219
|
+
coerce_type validations, attrs, doc_attrs
|
222
220
|
|
223
221
|
validations.each do |type, options|
|
224
222
|
validate(type, options, attrs, doc_attrs)
|
225
223
|
end
|
226
224
|
end
|
227
225
|
|
226
|
+
# Validate and comprehend the +:type+, +:types+, and +:coerce_with+
|
227
|
+
# options that have been supplied to the parameter declaration.
|
228
|
+
# The +:type+ and +:types+ options will be removed from the
|
229
|
+
# validations list, replaced appropriately with +:coerce+ and
|
230
|
+
# +:coerce_with+ options that will later be passed to
|
231
|
+
# {Validators::CoerceValidator}. The type that is returned may be
|
232
|
+
# used for documentation and further validation of parameter
|
233
|
+
# options.
|
234
|
+
#
|
235
|
+
# @param validations [Hash] list of validations supplied to the
|
236
|
+
# parameter declaration
|
237
|
+
# @return [class-like] type to which the parameter will be coerced
|
238
|
+
# @raise [ArgumentError] if the given type options are invalid
|
239
|
+
def infer_coercion(validations)
|
240
|
+
if validations.key?(:type) && validations.key?(:types)
|
241
|
+
fail ArgumentError, ':type may not be supplied with :types'
|
242
|
+
end
|
243
|
+
|
244
|
+
validations[:coerce] = validations[:type] if validations.key?(:type)
|
245
|
+
validations[:coerce] = validations.delete(:types) if validations.key?(:types)
|
246
|
+
|
247
|
+
coerce_type = validations[:coerce]
|
248
|
+
|
249
|
+
# Special case - when the argument is a single type that is a
|
250
|
+
# variant-type collection.
|
251
|
+
if Types.multiple?(coerce_type) && validations.key?(:type)
|
252
|
+
validations[:coerce] = Types::VariantCollectionCoercer.new(
|
253
|
+
coerce_type,
|
254
|
+
validations.delete(:coerce_with)
|
255
|
+
)
|
256
|
+
end
|
257
|
+
validations.delete(:type)
|
258
|
+
|
259
|
+
coerce_type
|
260
|
+
end
|
261
|
+
|
262
|
+
# Enforce correct usage of :coerce_with parameter.
|
263
|
+
# We do not allow coercion without a type, nor with
|
264
|
+
# +JSON+ as a type since this defines its own coercion
|
265
|
+
# method.
|
266
|
+
def check_coerce_with(validations)
|
267
|
+
return unless validations.key?(:coerce_with)
|
268
|
+
# type must be supplied for coerce_with..
|
269
|
+
fail ArgumentError, 'must supply type for coerce_with' unless validations.key?(:coerce)
|
270
|
+
|
271
|
+
# but not special JSON types, which
|
272
|
+
# already imply coercion method
|
273
|
+
return unless [JSON, Array[JSON]].include? validations[:coerce]
|
274
|
+
fail ArgumentError, 'coerce_with disallowed for type: JSON'
|
275
|
+
end
|
276
|
+
|
277
|
+
# Add type coercion validation to this scope,
|
278
|
+
# if any has been specified.
|
279
|
+
# This validation has special handling since it is
|
280
|
+
# composited from more than one +requires+/+optional+
|
281
|
+
# parameter, and needs to be run before most other
|
282
|
+
# validations.
|
283
|
+
def coerce_type(validations, attrs, doc_attrs)
|
284
|
+
check_coerce_with(validations)
|
285
|
+
|
286
|
+
return unless validations.key?(:coerce)
|
287
|
+
|
288
|
+
coerce_options = {
|
289
|
+
type: validations[:coerce],
|
290
|
+
method: validations[:coerce_with]
|
291
|
+
}
|
292
|
+
validate('coerce', coerce_options, attrs, doc_attrs)
|
293
|
+
validations.delete(:coerce_with)
|
294
|
+
validations.delete(:coerce)
|
295
|
+
end
|
296
|
+
|
228
297
|
def guess_coerce_type(coerce_type, values)
|
229
298
|
return coerce_type if !values || values.is_a?(Proc)
|
230
299
|
return values.first.class if coerce_type == Array && (values.is_a?(Range) || !values.empty?)
|
@@ -257,9 +326,8 @@ module Grape
|
|
257
326
|
if coerce_type == Virtus::Attribute::Boolean
|
258
327
|
value_types = value_types.map { |type| Virtus::Attribute.build(type) }
|
259
328
|
end
|
260
|
-
|
261
|
-
|
262
|
-
end
|
329
|
+
return unless value_types.any? { |v| !v.is_a?(coerce_type) }
|
330
|
+
fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
263
331
|
end
|
264
332
|
end
|
265
333
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require_relative 'types/build_coercer'
|
2
|
+
require_relative 'types/custom_type_coercer'
|
3
|
+
require_relative 'types/multiple_type_coercer'
|
4
|
+
require_relative 'types/variant_collection_coercer'
|
5
|
+
require_relative 'types/json'
|
6
|
+
require_relative 'types/file'
|
7
|
+
|
8
|
+
# Patch for Virtus::Attribute::Collection
|
9
|
+
# See the file for more details
|
10
|
+
require_relative 'types/virtus_collection_patch'
|
11
|
+
|
12
|
+
module Grape
|
13
|
+
module Validations
|
14
|
+
# Module for code related to grape's system for
|
15
|
+
# coercion and type validation of incoming request
|
16
|
+
# parameters.
|
17
|
+
#
|
18
|
+
# Grape uses a number of tests and assertions to
|
19
|
+
# work out exactly how a parameter should be handled,
|
20
|
+
# based on the +type+ and +coerce_with+ options that
|
21
|
+
# may be supplied to {Grape::Dsl::Parameters#requires}
|
22
|
+
# and {Grape::Dsl::Parameters#optional}. The main
|
23
|
+
# entry point for this process is {Types.build_coercer}.
|
24
|
+
module Types
|
25
|
+
# Instances of this class may be used as tokens to denote that
|
26
|
+
# a parameter value could not be coerced.
|
27
|
+
class InvalidValue; end
|
28
|
+
|
29
|
+
# Types representing a single value, which are coerced through Virtus
|
30
|
+
# or special logic in Grape.
|
31
|
+
PRIMITIVES = [
|
32
|
+
# Numerical
|
33
|
+
Integer,
|
34
|
+
Float,
|
35
|
+
BigDecimal,
|
36
|
+
Numeric,
|
37
|
+
|
38
|
+
# Date/time
|
39
|
+
Date,
|
40
|
+
DateTime,
|
41
|
+
Time,
|
42
|
+
|
43
|
+
# Misc
|
44
|
+
Virtus::Attribute::Boolean,
|
45
|
+
String,
|
46
|
+
Symbol,
|
47
|
+
Rack::Multipart::UploadedFile
|
48
|
+
]
|
49
|
+
|
50
|
+
# Types representing data structures.
|
51
|
+
STRUCTURES = [
|
52
|
+
Hash,
|
53
|
+
Array,
|
54
|
+
Set
|
55
|
+
]
|
56
|
+
|
57
|
+
# Types for which Grape provides special coercion
|
58
|
+
# and type-checking logic.
|
59
|
+
SPECIAL = {
|
60
|
+
JSON => Json,
|
61
|
+
Array[JSON] => JsonArray,
|
62
|
+
::File => File,
|
63
|
+
Rack::Multipart::UploadedFile => File
|
64
|
+
}
|
65
|
+
|
66
|
+
# Is the given class a primitive type as recognized by Grape?
|
67
|
+
#
|
68
|
+
# @param type [Class] type to check
|
69
|
+
# @return [Boolean] whether or not the type is known by Grape as a valid
|
70
|
+
# type for a single value
|
71
|
+
def self.primitive?(type)
|
72
|
+
PRIMITIVES.include?(type)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Is the given class a standard data structure (collection or map)
|
76
|
+
# as recognized by Grape?
|
77
|
+
#
|
78
|
+
# @param type [Class] type to check
|
79
|
+
# @return [Boolean] whether or not the type is known by Grape as a valid
|
80
|
+
# data structure type
|
81
|
+
# @note This method does not yet consider 'complex types', which inherit
|
82
|
+
# Virtus.model.
|
83
|
+
def self.structure?(type)
|
84
|
+
STRUCTURES.include?(type)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Is the declared type in fact an array of multiple allowed types?
|
88
|
+
# For example the declaration +types: [Integer,String]+ will attempt
|
89
|
+
# first to coerce given values to integer, but will also accept any
|
90
|
+
# other string.
|
91
|
+
#
|
92
|
+
# @param type [Array<Class>,Set<Class>] type (or type list!) to check
|
93
|
+
# @return [Boolean] +true+ if the given value will be treated as
|
94
|
+
# a list of types.
|
95
|
+
def self.multiple?(type)
|
96
|
+
(type.is_a?(Array) || type.is_a?(Set)) && type.size > 1
|
97
|
+
end
|
98
|
+
|
99
|
+
# Does the given class implement a type system that Grape
|
100
|
+
# (i.e. the underlying virtus attribute system) supports
|
101
|
+
# out-of-the-box? Currently supported are +axiom-types+
|
102
|
+
# and +virtus+.
|
103
|
+
#
|
104
|
+
# The type will be passed to +Virtus::Attribute.build+,
|
105
|
+
# and the resulting attribute object will be expected to
|
106
|
+
# respond correctly to +coerce+ and +value_coerced?+.
|
107
|
+
#
|
108
|
+
# @param type [Class] type to check
|
109
|
+
# @return [Boolean] +true+ where the type is recognized
|
110
|
+
def self.recognized?(type)
|
111
|
+
return false if type.is_a?(Array) || type.is_a?(Set)
|
112
|
+
|
113
|
+
type.is_a?(Virtus::Attribute) ||
|
114
|
+
type.ancestors.include?(Axiom::Types::Type) ||
|
115
|
+
type.include?(Virtus::Model::Core)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Does Grape provide special coercion and validation
|
119
|
+
# routines for the given class? This does not include
|
120
|
+
# automatic handling for primitives, structures and
|
121
|
+
# otherwise recognized types. See {Types::SPECIAL}.
|
122
|
+
#
|
123
|
+
# @param type [Class] type to check
|
124
|
+
# @return [Boolean] +true+ if special routines are available
|
125
|
+
def self.special?(type)
|
126
|
+
SPECIAL.key? type
|
127
|
+
end
|
128
|
+
|
129
|
+
# A valid custom type must implement a class-level `parse` method, taking
|
130
|
+
# one String argument and returning the parsed value in its correct type.
|
131
|
+
# @param type [Class] type to check
|
132
|
+
# @return [Boolean] whether or not the type can be used as a custom type
|
133
|
+
def self.custom?(type)
|
134
|
+
!primitive?(type) &&
|
135
|
+
!structure?(type) &&
|
136
|
+
!multiple?(type) &&
|
137
|
+
!recognized?(type) &&
|
138
|
+
!special?(type) &&
|
139
|
+
type.respond_to?(:parse) &&
|
140
|
+
type.method(:parse).arity == 1
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|