grape 0.19.2 → 1.0.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +8 -0
  3. data/CHANGELOG.md +40 -22
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +58 -59
  6. data/LICENSE +1 -1
  7. data/README.md +94 -49
  8. data/Rakefile +1 -0
  9. data/UPGRADING.md +89 -0
  10. data/benchmark/simple_with_type_coercer.rb +22 -0
  11. data/gemfiles/multi_json.gemfile +36 -0
  12. data/gemfiles/multi_xml.gemfile +36 -0
  13. data/gemfiles/rack_1.5.2.gemfile +1 -0
  14. data/gemfiles/rack_edge.gemfile +1 -0
  15. data/gemfiles/rails_3.gemfile +1 -0
  16. data/gemfiles/rails_4.gemfile +1 -0
  17. data/gemfiles/rails_5.gemfile +1 -0
  18. data/gemfiles/rails_edge.gemfile +1 -0
  19. data/grape.gemspec +0 -3
  20. data/lib/grape.rb +40 -17
  21. data/lib/grape/dsl/helpers.rb +32 -18
  22. data/lib/grape/dsl/inside_route.rb +2 -2
  23. data/lib/grape/dsl/parameters.rb +26 -0
  24. data/lib/grape/dsl/routing.rb +1 -1
  25. data/lib/grape/dsl/settings.rb +1 -1
  26. data/lib/grape/endpoint.rb +20 -16
  27. data/lib/grape/error_formatter/json.rb +1 -1
  28. data/lib/grape/error_formatter/txt.rb +1 -1
  29. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +26 -0
  30. data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +18 -0
  31. data/lib/grape/extensions/deep_mergeable_hash.rb +19 -0
  32. data/lib/grape/extensions/deep_symbolize_hash.rb +30 -0
  33. data/lib/grape/extensions/hash.rb +23 -0
  34. data/lib/grape/extensions/hashie/mash.rb +24 -0
  35. data/lib/grape/formatter/json.rb +1 -1
  36. data/lib/grape/formatter/serializable_hash.rb +2 -2
  37. data/lib/grape/locale/en.yml +1 -1
  38. data/lib/grape/middleware/globals.rb +1 -1
  39. data/lib/grape/parser/json.rb +2 -2
  40. data/lib/grape/parser/xml.rb +2 -2
  41. data/lib/grape/request.rb +11 -10
  42. data/lib/grape/util/json.rb +8 -0
  43. data/lib/grape/util/xml.rb +8 -0
  44. data/lib/grape/validations.rb +4 -0
  45. data/lib/grape/validations/params_scope.rb +77 -39
  46. data/lib/grape/validations/types/build_coercer.rb +27 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +18 -4
  48. data/lib/grape/validations/types/file.rb +2 -3
  49. data/lib/grape/validations/validator_factory.rb +18 -0
  50. data/lib/grape/validations/validators/base.rb +4 -5
  51. data/lib/grape/validations/validators/coerce.rb +4 -0
  52. data/lib/grape/validations/validators/except_values.rb +20 -0
  53. data/lib/grape/validations/validators/values.rb +25 -5
  54. data/lib/grape/version.rb +1 -1
  55. data/spec/grape/api/invalid_format_spec.rb +3 -3
  56. data/spec/grape/api_spec.rb +28 -16
  57. data/spec/grape/dsl/helpers_spec.rb +25 -6
  58. data/spec/grape/endpoint_spec.rb +117 -13
  59. data/spec/grape/extensions/param_builders/hash_spec.rb +83 -0
  60. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +105 -0
  61. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +79 -0
  62. data/spec/grape/middleware/formatter_spec.rb +6 -2
  63. data/spec/grape/request_spec.rb +13 -3
  64. data/spec/grape/validations/instance_behaivour_spec.rb +44 -0
  65. data/spec/grape/validations/params_scope_spec.rb +23 -0
  66. data/spec/grape/validations/types_spec.rb +19 -0
  67. data/spec/grape/validations/validators/coerce_spec.rb +117 -8
  68. data/spec/grape/validations/validators/except_values_spec.rb +191 -0
  69. data/spec/grape/validations/validators/values_spec.rb +78 -0
  70. data/spec/integration/multi_json/json_spec.rb +7 -0
  71. data/spec/integration/multi_xml/xml_spec.rb +7 -0
  72. metadata +30 -46
  73. data/pkg/grape-0.18.0.gem +0 -0
@@ -46,7 +46,7 @@ module Grape
46
46
  end
47
47
 
48
48
  def declared_hash(passed_params, options, declared_params)
49
- declared_params.each_with_object(Hashie::Mash.new) do |declared_param, memo|
49
+ declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
50
50
  # If it is not a Hash then it does not have children.
51
51
  # Find its value or set it to nil.
52
52
  if !declared_param.is_a?(Hash)
@@ -56,7 +56,7 @@ module Grape
56
56
  declared_param.each_pair do |declared_parent_param, declared_children_params|
57
57
  next unless options[:include_missing] || passed_params.key?(declared_parent_param)
58
58
 
59
- passed_children_params = passed_params[declared_parent_param] || Hashie::Mash.new
59
+ passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
60
60
  memo[optioned_param_key(declared_parent_param, options)] = declared(passed_children_params, options, declared_children_params)
61
61
  end
62
62
  end
@@ -8,6 +8,32 @@ module Grape
8
8
  module Parameters
9
9
  extend ActiveSupport::Concern
10
10
 
11
+ # Set the module used to build the request.params.
12
+ #
13
+ # @param build_with the ParamBuilder module to use when building request.params
14
+ # Available builders are:
15
+ #
16
+ # * Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder (default)
17
+ # * Grape::Extensions::Hash::ParamBuilder
18
+ # * Grape::Extensions::Hashie::Mash::ParamBuilder
19
+ #
20
+ # @example
21
+ #
22
+ # require 'grape/extenstions/hashie_mash'
23
+ # class API < Grape::API
24
+ # desc "Get collection"
25
+ # params do
26
+ # build_with Grape::Extensions::Hashie::Mash::ParamBuilder
27
+ # requires :user_id, type: Integer
28
+ # end
29
+ # get do
30
+ # params['user_id']
31
+ # end
32
+ # end
33
+ def build_with(build_with = nil)
34
+ @api.namespace_inheritable(:build_params_with, build_with)
35
+ end
36
+
11
37
  # Include reusable params rules among current.
12
38
  # You can define reusable params with helpers method.
13
39
  #
@@ -7,7 +7,7 @@ module Grape
7
7
  include Grape::DSL::Configuration
8
8
 
9
9
  module ClassMethods
10
- attr_reader :endpoints, :routes
10
+ attr_reader :endpoints
11
11
 
12
12
  # Specify an API version.
13
13
  #
@@ -9,7 +9,7 @@ module Grape
9
9
  module Settings
10
10
  extend ActiveSupport::Concern
11
11
 
12
- attr_accessor :inheritable_setting, :top_level_setting
12
+ attr_writer :inheritable_setting, :top_level_setting
13
13
 
14
14
  # Fetch our top-level settings, which apply to all endpoints in the API.
15
15
  def top_level_setting
@@ -1,5 +1,3 @@
1
- require 'grape/middleware/stack'
2
-
3
1
  module Grape
4
2
  # An Endpoint is the proxy scope in which all routing
5
3
  # blocks are executed. In other words, any methods
@@ -98,6 +96,11 @@ module Grape
98
96
  @lazy_initialized = nil
99
97
  @block = nil
100
98
 
99
+ @status = nil
100
+ @file = nil
101
+ @body = nil
102
+ @proc = nil
103
+
101
104
  return unless block_given?
102
105
 
103
106
  @source = block
@@ -239,15 +242,12 @@ module Grape
239
242
  def run
240
243
  ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do
241
244
  @header = {}
242
-
243
- @request = Grape::Request.new(env)
245
+ @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with))
244
246
  @params = @request.params
245
247
  @headers = @request.headers
246
248
 
247
249
  cookies.read(@request)
248
-
249
250
  self.class.run_before_each(self)
250
-
251
251
  run_filters befores, :before
252
252
 
253
253
  if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS])
@@ -338,18 +338,22 @@ module Grape
338
338
  end
339
339
  end
340
340
 
341
- def run_validators(validators, request)
341
+ def run_validators(validator_factories, request)
342
342
  validation_errors = []
343
343
 
344
- validators.each do |validator|
345
- begin
346
- validator.validate(request)
347
- rescue Grape::Exceptions::Validation => e
348
- validation_errors << e
349
- break if validator.fail_fast?
350
- rescue Grape::Exceptions::ValidationArrayErrors => e
351
- validation_errors += e.errors
352
- break if validator.fail_fast?
344
+ validators = validator_factories.map(&:create_validator)
345
+
346
+ ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do
347
+ validators.each do |validator|
348
+ begin
349
+ validator.validate(request)
350
+ rescue Grape::Exceptions::Validation => e
351
+ validation_errors << e
352
+ break if validator.fail_fast?
353
+ rescue Grape::Exceptions::ValidationArrayErrors => e
354
+ validation_errors += e.errors
355
+ break if validator.fail_fast?
356
+ end
353
357
  end
354
358
  end
355
359
 
@@ -10,7 +10,7 @@ module Grape
10
10
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
11
11
  result = result.merge(backtrace: backtrace)
12
12
  end
13
- MultiJson.dump(result)
13
+ ::Grape::Json.dump(result)
14
14
  end
15
15
 
16
16
  private
@@ -7,7 +7,7 @@ module Grape
7
7
  def call(message, backtrace, options = {}, env = nil)
8
8
  message = present(message, env)
9
9
 
10
- result = message.is_a?(Hash) ? MultiJson.dump(message) : message
10
+ result = message.is_a?(Hash) ? ::Grape::Json.dump(message) : message
11
11
  if (options[:rescue_options] || {})[:backtrace] && backtrace && !backtrace.empty?
12
12
  result += "\r\n "
13
13
  result += backtrace.join("\r\n ")
@@ -0,0 +1,26 @@
1
+ module Grape
2
+ module Extensions
3
+ module ActiveSupport
4
+ module HashWithIndifferentAccess
5
+ module ParamBuilder
6
+ extend ::ActiveSupport::Concern
7
+
8
+ included do
9
+ namespace_inheritable(:build_params_with, Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder)
10
+ end
11
+
12
+ def params_builder
13
+ Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
14
+ end
15
+
16
+ def build_params
17
+ params = ::ActiveSupport::HashWithIndifferentAccess[rack_params]
18
+ params.deep_merge!(grape_routing_args) if env[Grape::Env::GRAPE_ROUTING_ARGS]
19
+ # TODO: remove, in Rails 4 or later ::ActiveSupport::HashWithIndifferentAccess converts nested Hashes into indifferent access ones
20
+ DeepHashWithIndifferentAccess.deep_hash_with_indifferent_access(params)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module Grape
2
+ module Extensions
3
+ module DeepHashWithIndifferentAccess
4
+ def self.deep_hash_with_indifferent_access(object)
5
+ case object
6
+ when ::Hash
7
+ object.inject(::ActiveSupport::HashWithIndifferentAccess.new) do |new_hash, (key, value)|
8
+ new_hash.merge!(key => deep_hash_with_indifferent_access(value))
9
+ end
10
+ when ::Array
11
+ object.map { |element| deep_hash_with_indifferent_access(element) }
12
+ else
13
+ object
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Grape
2
+ module Extensions
3
+ class DeepMergeableHash < ::Hash
4
+ def deep_merge!(other_hash)
5
+ other_hash.each_pair do |current_key, other_value|
6
+ this_value = self[current_key]
7
+
8
+ self[current_key] = if this_value.is_a?(::Hash) && other_value.is_a?(::Hash)
9
+ this_value.deep_merge(other_value)
10
+ else
11
+ other_value
12
+ end
13
+ end
14
+
15
+ self
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Grape
2
+ module Extensions
3
+ module DeepSymbolizeHash
4
+ def self.deep_symbolize_keys_in(object)
5
+ case object
6
+ when ::Hash
7
+ object.each_with_object({}) do |(key, value), new_hash|
8
+ new_hash[symbolize_key(key)] = deep_symbolize_keys_in(value)
9
+ end
10
+ when ::Array
11
+ object.map { |element| deep_symbolize_keys_in(element) }
12
+ else
13
+ object
14
+ end
15
+ end
16
+
17
+ def self.symbolize_key(key)
18
+ if key.is_a?(Symbol)
19
+ key
20
+ elsif key.is_a?(String)
21
+ key.to_sym
22
+ elsif key.respond_to?(:to_sym)
23
+ key.to_sym
24
+ else
25
+ key
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ module Grape
2
+ module Extensions
3
+ module Hash
4
+ module ParamBuilder
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ namespace_inheritable(:build_params_with, Grape::Extensions::Hash::ParamBuilder)
9
+ end
10
+
11
+ def build_params
12
+ params = Grape::Extensions::DeepMergeableHash[rack_params]
13
+ params.deep_merge!(grape_routing_args) if env[Grape::Env::GRAPE_ROUTING_ARGS]
14
+ post_process_params(params)
15
+ end
16
+
17
+ def post_process_params(params)
18
+ Grape::Extensions::DeepSymbolizeHash.deep_symbolize_keys_in(params)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Grape
2
+ module Extensions
3
+ module Hashie
4
+ module Mash
5
+ module ParamBuilder
6
+ extend ::ActiveSupport::Concern
7
+ included do
8
+ namespace_inheritable(:build_params_with, Grape::Extensions::Hashie::Mash::ParamBuilder)
9
+ end
10
+
11
+ def params_builder
12
+ Grape::Extensions::Hashie::Mash::ParamBuilder
13
+ end
14
+
15
+ def build_params
16
+ params = ::Hashie::Mash.new(rack_params)
17
+ params.deep_merge!(grape_routing_args) if env[Grape::Env::GRAPE_ROUTING_ARGS]
18
+ params
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,7 +4,7 @@ module Grape
4
4
  class << self
5
5
  def call(object, _env)
6
6
  return object.to_json if object.respond_to?(:to_json)
7
- MultiJson.dump(object)
7
+ ::Grape::Json.dump(object)
8
8
  end
9
9
  end
10
10
  end
@@ -4,9 +4,9 @@ module Grape
4
4
  class << self
5
5
  def call(object, _env)
6
6
  return object if object.is_a?(String)
7
- return MultiJson.dump(serialize(object)) if serializable?(object)
7
+ return ::Grape::Json.dump(serialize(object)) if serializable?(object)
8
8
  return object.to_json if object.respond_to?(:to_json)
9
- MultiJson.dump(object)
9
+ ::Grape::Json.dump(object)
10
10
  end
11
11
 
12
12
  private
@@ -8,7 +8,7 @@ en:
8
8
  regexp: 'is invalid'
9
9
  blank: 'is empty'
10
10
  values: 'does not have a valid value'
11
- except: 'has a value not allowed'
11
+ except_values: 'has a value not allowed'
12
12
  missing_vendor_option:
13
13
  problem: 'missing :vendor option.'
14
14
  summary: 'when version using header, you must specify :vendor option. '
@@ -4,7 +4,7 @@ module Grape
4
4
  module Middleware
5
5
  class Globals < Base
6
6
  def before
7
- request = Grape::Request.new(@env)
7
+ request = Grape::Request.new(@env, build_params_with: @options[:build_params_with])
8
8
  @env[Grape::Env::GRAPE_REQUEST] = request
9
9
  @env[Grape::Env::GRAPE_REQUEST_HEADERS] = request.headers
10
10
  @env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Grape::Env::RACK_INPUT]
@@ -3,8 +3,8 @@ module Grape
3
3
  module Json
4
4
  class << self
5
5
  def call(object, _env)
6
- MultiJson.load(object)
7
- rescue MultiJson::ParseError
6
+ ::Grape::Json.load(object)
7
+ rescue ::Grape::Json::ParseError
8
8
  # handle JSON parsing errors via the rescue handlers or provide error message
9
9
  raise Grape::Exceptions::InvalidMessageBody, 'application/json'
10
10
  end
@@ -3,8 +3,8 @@ module Grape
3
3
  module Xml
4
4
  class << self
5
5
  def call(object, _env)
6
- MultiXml.parse(object)
7
- rescue MultiXml::ParseError
6
+ ::Grape::Xml.parse(object)
7
+ rescue ::Grape::Xml::ParseError
8
8
  # handle XML parsing errors via the rescue handlers or provide error message
9
9
  raise Grape::Exceptions::InvalidMessageBody, 'application/xml'
10
10
  end
@@ -4,6 +4,11 @@ module Grape
4
4
 
5
5
  alias rack_params params
6
6
 
7
+ def initialize(env, options = {})
8
+ extend options[:build_params_with] || Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
9
+ super(env)
10
+ end
11
+
7
12
  def params
8
13
  @params ||= build_params
9
14
  end
@@ -14,16 +19,12 @@ module Grape
14
19
 
15
20
  private
16
21
 
17
- def build_params
18
- params = Hashie::Mash.new(rack_params)
19
- if env[Grape::Env::GRAPE_ROUTING_ARGS]
20
- args = env[Grape::Env::GRAPE_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
22
+ def grape_routing_args
23
+ args = env[Grape::Env::GRAPE_ROUTING_ARGS].dup
24
+ # preserve version from query string parameters
25
+ args.delete(:version)
26
+ args.delete(:route_info)
27
+ args
27
28
  end
28
29
 
29
30
  def build_headers
@@ -0,0 +1,8 @@
1
+ module Grape
2
+ if Object.const_defined? :MultiJson
3
+ Json = ::MultiJson
4
+ else
5
+ Json = ::JSON
6
+ Json::ParseError = Json::ParserError
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Grape
2
+ if Object.const_defined? :MultiXml
3
+ Xml = ::MultiXml
4
+ else
5
+ Xml = ::ActiveSupport::XmlMini
6
+ Xml::ParseError = StandardError
7
+ end
8
+ end
@@ -14,5 +14,9 @@ module Grape
14
14
  def self.register_validator(short_name, klass)
15
15
  validators[short_name] = klass
16
16
  end
17
+
18
+ def self.deregister_validator(short_name)
19
+ validators.delete(short_name)
20
+ end
17
21
  end
18
22
  end
@@ -42,37 +42,45 @@ module Grape
42
42
  return false if @optional && (params(parameters).blank? ||
43
43
  any_element_blank?(parameters))
44
44
 
45
+ return true if parent.nil?
46
+ parent.should_validate?(parameters)
47
+ end
48
+
49
+ def meets_dependency?(params)
50
+ return true unless @dependent_on
51
+
52
+ params = params.with_indifferent_access
53
+
45
54
  @dependent_on.each do |dependency|
46
55
  if dependency.is_a?(Hash)
47
56
  dependency_key = dependency.keys[0]
48
57
  proc = dependency.values[0]
49
- return false unless proc.call(params(parameters).try(:[], dependency_key))
50
- elsif params(parameters).try(:[], dependency).blank?
58
+ return false unless proc.call(params.try(:[], dependency_key))
59
+ elsif params.respond_to?(:key?) && params.try(:[], dependency).blank?
51
60
  return false
52
61
  end
53
- end if @dependent_on
62
+ end
54
63
 
55
- return true if parent.nil?
56
- parent.should_validate?(parameters)
64
+ true
57
65
  end
58
66
 
59
67
  # @return [String] the proper attribute name, with nesting considered.
60
- def full_name(name)
68
+ def full_name(name, index: nil)
61
69
  if nested?
62
70
  # Find our containing element's name, and append ours.
63
- "#{@parent.full_name(@element)}#{array_index}[#{name}]"
71
+ [@parent.full_name(@element), [@index || index, name].map(&method(:brackets))].compact.join
64
72
  elsif lateral?
65
- # Find the name of the element as if it was at the
66
- # same nesting level as our parent.
67
- @parent.full_name(name)
73
+ # Find the name of the element as if it was at the same nesting level
74
+ # as our parent. We need to forward our index upward to achieve this.
75
+ @parent.full_name(name, index: @index)
68
76
  else
69
77
  # We must be the root scope, so no prefix needed.
70
78
  name.to_s
71
79
  end
72
80
  end
73
81
 
74
- def array_index
75
- "[#{@index}]" if @index.present?
82
+ def brackets(val)
83
+ "[#{val}]" if val
76
84
  end
77
85
 
78
86
  # @return [Boolean] whether or not this scope is the root-level scope
@@ -187,7 +195,7 @@ module Grape
187
195
  element: nil,
188
196
  parent: self,
189
197
  options: @optional,
190
- type: Hash,
198
+ type: type == Array ? Array : Hash,
191
199
  dependent_on: options[:dependent_on],
192
200
  &block
193
201
  )
@@ -232,16 +240,28 @@ module Grape
232
240
  default = validations[:default]
233
241
  doc_attrs[:default] = default if validations.key?(:default)
234
242
 
235
- values = options_key?(:values, :value, validations) ? validations[:values][:value] : validations[:values]
243
+ if (values_hash = validations[:values]).is_a? Hash
244
+ values = values_hash[:value]
245
+ # NB: excepts is deprecated
246
+ excepts = values_hash[:except]
247
+ else
248
+ values = validations[:values]
249
+ end
236
250
  doc_attrs[:values] = values if values
237
251
 
238
- coerce_type = guess_coerce_type(coerce_type, values)
252
+ except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
253
+
254
+ # NB. values and excepts should be nil, Proc, Array, or Range.
255
+ # Specifically, values should NOT be a Hash
256
+
257
+ # use values or excepts to guess coerce type when stated type is Array
258
+ coerce_type = guess_coerce_type(coerce_type, values, except_values, excepts)
239
259
 
240
260
  # default value should be present in values array, if both exist and are not procs
241
- check_incompatible_option_values(values, default)
261
+ check_incompatible_option_values(default, values, except_values, excepts)
242
262
 
243
263
  # type should be compatible with values array, if both exist
244
- validate_value_coercion(coerce_type, values)
264
+ validate_value_coercion(coerce_type, values, except_values, excepts)
245
265
 
246
266
  doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
247
267
 
@@ -346,17 +366,31 @@ module Grape
346
366
  validations.delete(:coerce_message)
347
367
  end
348
368
 
349
- def guess_coerce_type(coerce_type, values)
350
- return coerce_type if !values || values.is_a?(Proc)
351
- return values.first.class if coerce_type == Array && (values.is_a?(Range) || !values.empty?)
369
+ def guess_coerce_type(coerce_type, *values_list)
370
+ return coerce_type unless coerce_type == Array
371
+ values_list.each do |values|
372
+ next if !values || values.is_a?(Proc)
373
+ return values.first.class if values.is_a?(Range) || !values.empty?
374
+ end
352
375
  coerce_type
353
376
  end
354
377
 
355
- def check_incompatible_option_values(values, default)
356
- return unless values && default
357
- return if values.is_a?(Proc) || default.is_a?(Proc)
358
- return if values.include?(default) || (Array(default) - Array(values)).empty?
359
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values)
378
+ def check_incompatible_option_values(default, values, except_values, excepts)
379
+ return unless default && !default.is_a?(Proc)
380
+
381
+ if values && !values.is_a?(Proc)
382
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
383
+ unless Array(default).all? { |def_val| values.include?(def_val) }
384
+ end
385
+
386
+ if except_values && !except_values.is_a?(Proc)
387
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
388
+ unless Array(default).none? { |def_val| except_values.include?(def_val) }
389
+ end
390
+
391
+ return unless excepts && !excepts.is_a?(Proc)
392
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, excepts) \
393
+ unless Array(default).none? { |def_val| excepts.include?(def_val) }
360
394
  end
361
395
 
362
396
  def validate(type, options, attrs, doc_attrs, opts)
@@ -364,24 +398,28 @@ module Grape
364
398
 
365
399
  raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
366
400
 
367
- value = validator_class.new(attrs, options, doc_attrs[:required], self, opts)
368
- @api.namespace_stackable(:validations, value)
401
+ factory = Grape::Validations::ValidatorFactory.new(attributes: attrs,
402
+ options: options,
403
+ required: doc_attrs[:required],
404
+ params_scope: self,
405
+ opts: opts,
406
+ validator_class: validator_class)
407
+ @api.namespace_stackable(:validations, factory)
369
408
  end
370
409
 
371
- def validate_value_coercion(coerce_type, values)
372
- return unless coerce_type && values
373
- return if values.is_a?(Proc)
374
- if values.is_a?(Hash)
375
- return if values[:value] && values[:value].is_a?(Proc)
376
- return if values[:except] && values[:except].is_a?(Proc)
377
- end
410
+ def validate_value_coercion(coerce_type, *values_list)
411
+ return unless coerce_type
378
412
  coerce_type = coerce_type.first if coerce_type.is_a?(Array)
379
- value_types = values.is_a?(Range) ? [values.begin, values.end] : values
380
- if coerce_type == Virtus::Attribute::Boolean
381
- value_types = value_types.map { |type| Virtus::Attribute.build(type) }
413
+ values_list.each do |values|
414
+ next if !values || values.is_a?(Proc)
415
+ value_types = values.is_a?(Range) ? [values.begin, values.end] : values
416
+ if coerce_type == Virtus::Attribute::Boolean
417
+ value_types = value_types.map { |type| Virtus::Attribute.build(type) }
418
+ end
419
+ unless value_types.all? { |v| v.is_a? coerce_type }
420
+ raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
421
+ end
382
422
  end
383
- return unless value_types.any? { |v| !v.is_a?(coerce_type) }
384
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
385
423
  end
386
424
 
387
425
  def extract_message_option(attrs)