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.
- checksums.yaml +4 -4
- data/Appraisals +8 -0
- data/CHANGELOG.md +40 -22
- data/Gemfile +1 -0
- data/Gemfile.lock +58 -59
- data/LICENSE +1 -1
- data/README.md +94 -49
- data/Rakefile +1 -0
- data/UPGRADING.md +89 -0
- data/benchmark/simple_with_type_coercer.rb +22 -0
- data/gemfiles/multi_json.gemfile +36 -0
- data/gemfiles/multi_xml.gemfile +36 -0
- data/gemfiles/rack_1.5.2.gemfile +1 -0
- data/gemfiles/rack_edge.gemfile +1 -0
- data/gemfiles/rails_3.gemfile +1 -0
- data/gemfiles/rails_4.gemfile +1 -0
- data/gemfiles/rails_5.gemfile +1 -0
- data/gemfiles/rails_edge.gemfile +1 -0
- data/grape.gemspec +0 -3
- data/lib/grape.rb +40 -17
- data/lib/grape/dsl/helpers.rb +32 -18
- data/lib/grape/dsl/inside_route.rb +2 -2
- data/lib/grape/dsl/parameters.rb +26 -0
- data/lib/grape/dsl/routing.rb +1 -1
- data/lib/grape/dsl/settings.rb +1 -1
- data/lib/grape/endpoint.rb +20 -16
- data/lib/grape/error_formatter/json.rb +1 -1
- data/lib/grape/error_formatter/txt.rb +1 -1
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +26 -0
- data/lib/grape/extensions/deep_hash_with_indifferent_access.rb +18 -0
- data/lib/grape/extensions/deep_mergeable_hash.rb +19 -0
- data/lib/grape/extensions/deep_symbolize_hash.rb +30 -0
- data/lib/grape/extensions/hash.rb +23 -0
- data/lib/grape/extensions/hashie/mash.rb +24 -0
- data/lib/grape/formatter/json.rb +1 -1
- data/lib/grape/formatter/serializable_hash.rb +2 -2
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/globals.rb +1 -1
- data/lib/grape/parser/json.rb +2 -2
- data/lib/grape/parser/xml.rb +2 -2
- data/lib/grape/request.rb +11 -10
- data/lib/grape/util/json.rb +8 -0
- data/lib/grape/util/xml.rb +8 -0
- data/lib/grape/validations.rb +4 -0
- data/lib/grape/validations/params_scope.rb +77 -39
- data/lib/grape/validations/types/build_coercer.rb +27 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +18 -4
- data/lib/grape/validations/types/file.rb +2 -3
- data/lib/grape/validations/validator_factory.rb +18 -0
- data/lib/grape/validations/validators/base.rb +4 -5
- data/lib/grape/validations/validators/coerce.rb +4 -0
- data/lib/grape/validations/validators/except_values.rb +20 -0
- data/lib/grape/validations/validators/values.rb +25 -5
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api/invalid_format_spec.rb +3 -3
- data/spec/grape/api_spec.rb +28 -16
- data/spec/grape/dsl/helpers_spec.rb +25 -6
- data/spec/grape/endpoint_spec.rb +117 -13
- data/spec/grape/extensions/param_builders/hash_spec.rb +83 -0
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +105 -0
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +79 -0
- data/spec/grape/middleware/formatter_spec.rb +6 -2
- data/spec/grape/request_spec.rb +13 -3
- data/spec/grape/validations/instance_behaivour_spec.rb +44 -0
- data/spec/grape/validations/params_scope_spec.rb +23 -0
- data/spec/grape/validations/types_spec.rb +19 -0
- data/spec/grape/validations/validators/coerce_spec.rb +117 -8
- data/spec/grape/validations/validators/except_values_spec.rb +191 -0
- data/spec/grape/validations/validators/values_spec.rb +78 -0
- data/spec/integration/multi_json/json_spec.rb +7 -0
- data/spec/integration/multi_xml/xml_spec.rb +7 -0
- metadata +30 -46
- 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(
|
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] ||
|
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
|
data/lib/grape/dsl/parameters.rb
CHANGED
@@ -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
|
#
|
data/lib/grape/dsl/routing.rb
CHANGED
data/lib/grape/dsl/settings.rb
CHANGED
@@ -9,7 +9,7 @@ module Grape
|
|
9
9
|
module Settings
|
10
10
|
extend ActiveSupport::Concern
|
11
11
|
|
12
|
-
|
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
|
data/lib/grape/endpoint.rb
CHANGED
@@ -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(
|
341
|
+
def run_validators(validator_factories, request)
|
342
342
|
validation_errors = []
|
343
343
|
|
344
|
-
validators
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
|
@@ -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) ?
|
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
|
data/lib/grape/formatter/json.rb
CHANGED
@@ -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
|
7
|
+
return ::Grape::Json.dump(serialize(object)) if serializable?(object)
|
8
8
|
return object.to_json if object.respond_to?(:to_json)
|
9
|
-
|
9
|
+
::Grape::Json.dump(object)
|
10
10
|
end
|
11
11
|
|
12
12
|
private
|
data/lib/grape/locale/en.yml
CHANGED
@@ -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
|
-
|
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]
|
data/lib/grape/parser/json.rb
CHANGED
@@ -3,8 +3,8 @@ module Grape
|
|
3
3
|
module Json
|
4
4
|
class << self
|
5
5
|
def call(object, _env)
|
6
|
-
|
7
|
-
rescue
|
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
|
data/lib/grape/parser/xml.rb
CHANGED
@@ -3,8 +3,8 @@ module Grape
|
|
3
3
|
module Xml
|
4
4
|
class << self
|
5
5
|
def call(object, _env)
|
6
|
-
|
7
|
-
rescue
|
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
|
data/lib/grape/request.rb
CHANGED
@@ -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
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
data/lib/grape/validations.rb
CHANGED
@@ -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
|
50
|
-
elsif params(
|
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
|
62
|
+
end
|
54
63
|
|
55
|
-
|
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
|
-
|
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
|
-
#
|
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
|
75
|
-
"[#{
|
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
|
-
|
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
|
-
|
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,
|
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,
|
350
|
-
return coerce_type
|
351
|
-
|
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,
|
356
|
-
return unless
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
368
|
-
|
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,
|
372
|
-
return unless coerce_type
|
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
|
-
|
380
|
-
|
381
|
-
value_types =
|
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)
|