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.

Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +28 -0
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +166 -0
  6. data/README.md +305 -163
  7. data/Rakefile +30 -33
  8. data/UPGRADING.md +31 -0
  9. data/benchmark/simple.rb +27 -0
  10. data/gemfiles/rack_1.5.2.gemfile +13 -0
  11. data/gemfiles/rails_3.gemfile +2 -2
  12. data/gemfiles/rails_4.gemfile +1 -2
  13. data/grape.gemspec +5 -4
  14. data/lib/grape.rb +9 -5
  15. data/lib/grape/dsl/configuration.rb +5 -2
  16. data/lib/grape/dsl/helpers.rb +8 -3
  17. data/lib/grape/dsl/inside_route.rb +67 -44
  18. data/lib/grape/dsl/parameters.rb +21 -12
  19. data/lib/grape/dsl/request_response.rb +1 -1
  20. data/lib/grape/dsl/routing.rb +3 -4
  21. data/lib/grape/endpoint.rb +63 -28
  22. data/lib/grape/error_formatter/base.rb +6 -6
  23. data/lib/grape/exceptions/base.rb +5 -5
  24. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  25. data/lib/grape/formatter/serializable_hash.rb +3 -2
  26. data/lib/grape/locale/en.yml +4 -1
  27. data/lib/grape/middleware/auth/base.rb +2 -2
  28. data/lib/grape/middleware/auth/dsl.rb +1 -1
  29. data/lib/grape/middleware/auth/strategies.rb +1 -1
  30. data/lib/grape/middleware/base.rb +7 -4
  31. data/lib/grape/middleware/error.rb +3 -2
  32. data/lib/grape/middleware/filter.rb +1 -1
  33. data/lib/grape/middleware/formatter.rb +47 -44
  34. data/lib/grape/middleware/globals.rb +3 -3
  35. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  36. data/lib/grape/middleware/versioner/header.rb +113 -50
  37. data/lib/grape/middleware/versioner/param.rb +5 -8
  38. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  39. data/lib/grape/middleware/versioner/path.rb +3 -6
  40. data/lib/grape/path.rb +3 -3
  41. data/lib/grape/request.rb +40 -0
  42. data/lib/grape/util/content_types.rb +9 -9
  43. data/lib/grape/util/env.rb +22 -0
  44. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  45. data/lib/grape/validations/attributes_iterator.rb +8 -3
  46. data/lib/grape/validations/params_scope.rb +83 -15
  47. data/lib/grape/validations/types.rb +144 -0
  48. data/lib/grape/validations/types/build_coercer.rb +53 -0
  49. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  50. data/lib/grape/validations/types/file.rb +28 -0
  51. data/lib/grape/validations/types/json.rb +65 -0
  52. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  53. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  54. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  55. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  56. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  57. data/lib/grape/validations/validators/base.rb +7 -0
  58. data/lib/grape/validations/validators/coerce.rb +31 -42
  59. data/lib/grape/validations/validators/presence.rb +2 -3
  60. data/lib/grape/validations/validators/regexp.rb +2 -4
  61. data/lib/grape/validations/validators/values.rb +3 -3
  62. data/lib/grape/version.rb +1 -1
  63. data/pkg/grape-0.13.0.gem +0 -0
  64. data/spec/grape/api/custom_validations_spec.rb +5 -4
  65. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  66. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  67. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  68. data/spec/grape/api_spec.rb +88 -54
  69. data/spec/grape/dsl/configuration_spec.rb +13 -0
  70. data/spec/grape/dsl/helpers_spec.rb +16 -2
  71. data/spec/grape/dsl/inside_route_spec.rb +3 -2
  72. data/spec/grape/dsl/parameters_spec.rb +0 -6
  73. data/spec/grape/dsl/routing_spec.rb +1 -1
  74. data/spec/grape/endpoint_spec.rb +61 -20
  75. data/spec/grape/entity_spec.rb +10 -8
  76. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  77. data/spec/grape/integration/rack_spec.rb +3 -2
  78. data/spec/grape/middleware/base_spec.rb +7 -5
  79. data/spec/grape/middleware/error_spec.rb +16 -15
  80. data/spec/grape/middleware/exception_spec.rb +45 -43
  81. data/spec/grape/middleware/formatter_spec.rb +34 -0
  82. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  83. data/spec/grape/path_spec.rb +10 -10
  84. data/spec/grape/presenters/presenter_spec.rb +2 -2
  85. data/spec/grape/request_spec.rb +100 -0
  86. data/spec/grape/validations/params_scope_spec.rb +11 -9
  87. data/spec/grape/validations/types_spec.rb +95 -0
  88. data/spec/grape/validations/validators/coerce_spec.rb +335 -2
  89. data/spec/grape/validations/validators/values_spec.rb +15 -15
  90. data/spec/grape/validations_spec.rb +53 -24
  91. data/spec/shared/versioning_examples.rb +2 -2
  92. data/spec/spec_helper.rb +0 -1
  93. data/spec/support/versioned_helpers.rb +2 -2
  94. metadata +51 -13
  95. data/.gitignore +0 -46
  96. data/.rspec +0 -2
  97. data/.rubocop.yml +0 -7
  98. data/.rubocop_todo.yml +0 -84
  99. data/.travis.yml +0 -20
  100. data/.yardopts +0 -2
  101. data/lib/grape/http/request.rb +0 -35
  102. data/lib/grape/util/parameter_types.rb +0 -58
  103. 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
- unless potential_version.nil?
32
- if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
33
- throw :error, status: 404, message: '404 API Version Not Found', headers: { Grape::Http::Headers::X_CASCADE => 'pass' }
34
- end
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
- if potential_version =~ options[:pattern]
37
- if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version }
38
- throw :error, status: 404, message: '404 API Version Not Found'
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
@@ -29,18 +29,18 @@ module Grape
29
29
  !!(settings[:version] && settings[:version_options][:using] == :path)
30
30
  end
31
31
 
32
- def has_namespace?
32
+ def namespace?
33
33
  namespace && namespace.to_s =~ /^\S/ && namespace != '/'
34
34
  end
35
35
 
36
- def has_path?
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? || (has_namespace? || has_path?)
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 = ActiveSupport::OrderedHash[
5
- :xml, 'application/xml',
6
- :serializable_hash, 'application/json',
7
- :json, 'application/json',
8
- :binary, 'application/octet-stream',
9
- :txt, 'text/plain'
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 nil if settings.nil? || settings.blank?
13
+ return if settings.blank?
14
14
 
15
- settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.merge!(value) }
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 = setting_name.keys.each_with_object({}) { |k, hash| hash[k] = send("#{k}_context").to_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.each do |attr_name|
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
- # special case (type = coerce)
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, lets handle
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
- if validations.key? :coerce
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
- if value_types.any? { |v| !v.is_a?(coerce_type) }
261
- fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
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