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
@@ -0,0 +1,53 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
module Types
|
4
|
+
# Work out the +Virtus::Attribute+ object to
|
5
|
+
# use for coercing strings to the given +type+.
|
6
|
+
# Coercion +method+ will be inferred if none is
|
7
|
+
# supplied.
|
8
|
+
#
|
9
|
+
# If a +Virtus::Attribute+ object already built
|
10
|
+
# with +Virtus::Attribute.build+ is supplied as
|
11
|
+
# the +type+ it will be returned and +method+
|
12
|
+
# will be ignored.
|
13
|
+
#
|
14
|
+
# See {CustomTypeCoercer} for further details
|
15
|
+
# about coercion and type-checking inference.
|
16
|
+
#
|
17
|
+
# @param type [Class] the type to which input strings
|
18
|
+
# should be coerced
|
19
|
+
# @param method [Class,#call] the coercion method to use
|
20
|
+
# @return [Virtus::Attribute] object to be used
|
21
|
+
# for coercion and type validation
|
22
|
+
def self.build_coercer(type, method = nil)
|
23
|
+
# Accept pre-rolled virtus attributes without interference
|
24
|
+
return type if type.is_a? Virtus::Attribute
|
25
|
+
|
26
|
+
converter_options = {
|
27
|
+
nullify_blank: true
|
28
|
+
}
|
29
|
+
conversion_type = type
|
30
|
+
|
31
|
+
# Use a special coercer for multiply-typed parameters.
|
32
|
+
if Types.multiple?(type)
|
33
|
+
converter_options[:coercer] = Types::MultipleTypeCoercer.new(type, method)
|
34
|
+
conversion_type = Object
|
35
|
+
|
36
|
+
# Use a special coercer for custom types and coercion methods.
|
37
|
+
elsif method || Types.custom?(type)
|
38
|
+
converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method)
|
39
|
+
|
40
|
+
# Grape swaps in its own Virtus::Attribute implementations
|
41
|
+
# for certain special types that merit first-class support
|
42
|
+
# (but not if a custom coercion method has been supplied).
|
43
|
+
elsif Types.special?(type)
|
44
|
+
conversion_type = Types::SPECIAL[type]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Virtus will infer coercion and validation rules
|
48
|
+
# for many common ruby types.
|
49
|
+
Virtus::Attribute.build(conversion_type, converter_options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
module Types
|
4
|
+
# Instances of this class may be passed to
|
5
|
+
# +Virtus::Attribute.build+ as the +:coercer+
|
6
|
+
# option for custom types that do not otherwise
|
7
|
+
# satisfy the requirements for +Virtus::Attribute::coerce+
|
8
|
+
# and +Virtus::Attribute::value_coerced?+ to work
|
9
|
+
# as expected.
|
10
|
+
#
|
11
|
+
# Subclasses of +Virtus::Attribute+ or +Axiom::Types::Type+
|
12
|
+
# (or for which an axiom type can be inferred, i.e. the
|
13
|
+
# primitives, +Date+, +Time+, etc.) do not need any such
|
14
|
+
# coercer to be passed with them.
|
15
|
+
#
|
16
|
+
# Coercion
|
17
|
+
# --------
|
18
|
+
#
|
19
|
+
# This class will detect type classes that implement
|
20
|
+
# a class-level +parse+ method. The method should accept one
|
21
|
+
# +String+ argument and should return the value coerced to
|
22
|
+
# the appropriate type. The method may raise an exception if
|
23
|
+
# there are any problems parsing the string.
|
24
|
+
#
|
25
|
+
# Alternately an optional +method+ may be supplied (see the
|
26
|
+
# +coerce_with+ option of {Grape::Dsl::Parameters#requires}).
|
27
|
+
# This may be any class or object implementing +parse+ or +call+,
|
28
|
+
# with the same contract as described above.
|
29
|
+
#
|
30
|
+
# Type Checking
|
31
|
+
# -------------
|
32
|
+
#
|
33
|
+
# Calls to +value_coerced?+ will consult this class to check
|
34
|
+
# that the coerced value produced above is in fact of the
|
35
|
+
# expected type. By default this class performs a basic check
|
36
|
+
# against the type supplied, but this behaviour will be
|
37
|
+
# overridden if the class implements a class-level
|
38
|
+
# +coerced?+ or +parsed?+ method. This method
|
39
|
+
# will receive a single parameter that is the coerced value
|
40
|
+
# and should return +true+ iff the value meets type expectations.
|
41
|
+
# Arbitrary assertions may be made here but the grape validation
|
42
|
+
# system should be preferred.
|
43
|
+
#
|
44
|
+
# Alternately a proc or other object responding to +call+ may be
|
45
|
+
# supplied in place of a type. This should implement the same
|
46
|
+
# contract as +coerced?+, and must be supplied with a coercion
|
47
|
+
# +method+.
|
48
|
+
class CustomTypeCoercer
|
49
|
+
# Uses +Virtus::Attribute.build+ to build a new
|
50
|
+
# attribute that makes use of this class for
|
51
|
+
# coercion and type validation logic.
|
52
|
+
#
|
53
|
+
# @return [Virtus::Attribute]
|
54
|
+
def self.build(type, method = nil)
|
55
|
+
Virtus::Attribute.build(type, coercer: new(type, method))
|
56
|
+
end
|
57
|
+
|
58
|
+
# A new coercer for the given type specification
|
59
|
+
# and coercion method.
|
60
|
+
#
|
61
|
+
# @param type [Class,#coerced?,#parsed?,#call?]
|
62
|
+
# specifier for the target type. See class docs.
|
63
|
+
# @param method [#parse,#call]
|
64
|
+
# optional coercion method. See class docs.
|
65
|
+
def initialize(type, method = nil)
|
66
|
+
coercion_method = infer_coercion_method type, method
|
67
|
+
|
68
|
+
@method = enforce_symbolized_keys type, coercion_method
|
69
|
+
|
70
|
+
@type_check = infer_type_check(type)
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method is called from somewhere within
|
74
|
+
# +Virtus::Attribute::coerce+ in order to coerce
|
75
|
+
# the given value.
|
76
|
+
#
|
77
|
+
# @param value [String] value to be coerced, in grape
|
78
|
+
# this should always be a string.
|
79
|
+
# @return [Object] the coerced result
|
80
|
+
def call(value)
|
81
|
+
@method.call value
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method is called from somewhere within
|
85
|
+
# +Virtus::Attribute::value_coerced?+ in order to
|
86
|
+
# assert that the value has been coerced successfully.
|
87
|
+
#
|
88
|
+
# @param _primitive [Axiom::Types::Type] primitive type
|
89
|
+
# for the coercion as detected by axiom-types' inference
|
90
|
+
# system. For custom types this is typically not much use
|
91
|
+
# (i.e. it is +Axiom::Types::Object+) unless special
|
92
|
+
# inference rules have been declared for the type.
|
93
|
+
# @param value [Object] a coerced result returned from {#call}
|
94
|
+
# @return [true,false] whether or not the coerced value
|
95
|
+
# satisfies type requirements.
|
96
|
+
def success?(_primitive, value)
|
97
|
+
@type_check.call value
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Determine the coercion method we're expected to use
|
103
|
+
# based on the parameters given.
|
104
|
+
#
|
105
|
+
# @param type see #new
|
106
|
+
# @param method see #new
|
107
|
+
# @return [#call] coercion method
|
108
|
+
def infer_coercion_method(type, method)
|
109
|
+
if method
|
110
|
+
if method.respond_to? :parse
|
111
|
+
method.method :parse
|
112
|
+
else
|
113
|
+
method
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# Try to use parse() declared on the target type.
|
117
|
+
# This may raise an exception, but we are out of ideas anyway.
|
118
|
+
type.method :parse
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Determine how the type validity of a coerced
|
123
|
+
# value should be decided.
|
124
|
+
#
|
125
|
+
# @param type see #new
|
126
|
+
# @return [#call] a procedure which accepts a single parameter
|
127
|
+
# and returns +true+ if the passed object is of the correct type.
|
128
|
+
def infer_type_check(type)
|
129
|
+
# First check for special class methods
|
130
|
+
if type.respond_to? :coerced?
|
131
|
+
type.method :coerced?
|
132
|
+
elsif type.respond_to? :parsed?
|
133
|
+
type.method :parsed?
|
134
|
+
elsif type.respond_to? :call
|
135
|
+
# Arbitrary proc passed for type validation.
|
136
|
+
# Note that this will fail unless a method is also
|
137
|
+
# passed, or if the type also implements a parse() method.
|
138
|
+
type
|
139
|
+
else
|
140
|
+
# By default, do a simple type check
|
141
|
+
->(value) { value.is_a? type }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Enforce symbolized keys for complex types
|
146
|
+
# by wrapping the coercion method such that
|
147
|
+
# any Hash objects in the immediate heirarchy
|
148
|
+
# are passed through +Hashie.symbolize_keys!+.
|
149
|
+
# This helps common libs such as JSON to work easily.
|
150
|
+
#
|
151
|
+
# @param type see #new
|
152
|
+
# @param method see #infer_coercion_method
|
153
|
+
# @return [#call] +method+ wrapped in an additional
|
154
|
+
# key-conversion step, or just returns +method+
|
155
|
+
# itself if no conversion is deemed to be
|
156
|
+
# necessary.
|
157
|
+
def enforce_symbolized_keys(type, method)
|
158
|
+
# Collections have all values processed individually
|
159
|
+
if type == Array || type == Set
|
160
|
+
lambda do |val|
|
161
|
+
method.call(val).tap do |new_value|
|
162
|
+
new_value.each do |item|
|
163
|
+
Hashie.symbolize_keys!(item) if item.is_a? Hash
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Hash objects are processed directly
|
169
|
+
elsif type == Hash
|
170
|
+
lambda do |val|
|
171
|
+
Hashie.symbolize_keys! method.call(val)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Simple types are not processed.
|
175
|
+
# This includes Array<primitive> types.
|
176
|
+
else
|
177
|
+
method
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
module Types
|
4
|
+
# +Virtus::Attribute+ implementation for parameters
|
5
|
+
# that are multipart file objects. Actual handling
|
6
|
+
# of these objects is provided by +Rack::Request+;
|
7
|
+
# this class is here only to assert that rack's
|
8
|
+
# handling has succeeded, and to prevent virtus
|
9
|
+
# from interfering.
|
10
|
+
class File < Virtus::Attribute
|
11
|
+
def coerce(input)
|
12
|
+
# Processing of multipart file objects
|
13
|
+
# is already taken care of by Rack::Request.
|
14
|
+
# Nothing to do here.
|
15
|
+
input
|
16
|
+
end
|
17
|
+
|
18
|
+
def value_coerced?(value)
|
19
|
+
# Rack::Request creates a Hash with filename,
|
20
|
+
# content type and an IO object. Grape wraps that
|
21
|
+
# using hashie for convenience. Do a bit of basic
|
22
|
+
# duck-typing.
|
23
|
+
value.is_a?(Hashie::Mash) && value.key?(:tempfile)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
module Types
|
6
|
+
# +Virtus::Attribute+ implementation that handles coercion
|
7
|
+
# and type checking for parameters that are complex types
|
8
|
+
# given as JSON-encoded strings. It accepts both JSON objects
|
9
|
+
# and arrays of objects, and will coerce the input to a +Hash+
|
10
|
+
# or +Array+ object respectively. In either case the Grape
|
11
|
+
# validation system will apply nested validation rules to
|
12
|
+
# all returned objects.
|
13
|
+
class Json < Virtus::Attribute
|
14
|
+
# Coerce the input into a JSON-like data structure.
|
15
|
+
#
|
16
|
+
# @param input [String] a JSON-encoded parameter value
|
17
|
+
# @return [Hash,Array<Hash>,nil]
|
18
|
+
def coerce(input)
|
19
|
+
# Allow nulls and blank strings
|
20
|
+
return if input.nil? || input =~ /^\s*$/
|
21
|
+
JSON.parse(input, symbolize_names: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks that the input was parsed successfully
|
25
|
+
# and isn't something odd such as an array of primitives.
|
26
|
+
#
|
27
|
+
# @param value [Object] result of {#coerce}
|
28
|
+
# @return [true,false]
|
29
|
+
def value_coerced?(value)
|
30
|
+
value.is_a?(::Hash) || coerced_collection?(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Is the value an array of JSON-like objects?
|
36
|
+
#
|
37
|
+
# @param value [Object] result of {#coerce}
|
38
|
+
# @return [true,false]
|
39
|
+
def coerced_collection?(value)
|
40
|
+
value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Specialization of the {Json} attribute that is guaranteed
|
45
|
+
# to return an array of objects. Accepts both JSON-encoded
|
46
|
+
# objects and arrays of objects, but wraps single objects
|
47
|
+
# in an Array.
|
48
|
+
class JsonArray < Json
|
49
|
+
# See {Json#coerce}. Wraps single objects in an array.
|
50
|
+
#
|
51
|
+
# @param input [String] JSON-encoded parameter value
|
52
|
+
# @return [Array<Hash>]
|
53
|
+
def coerce(input)
|
54
|
+
json = super
|
55
|
+
Array.wrap(json) unless json.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
# See {Json#coerced_collection?}
|
59
|
+
def value_coerced?(value)
|
60
|
+
coerced_collection? value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
module Types
|
4
|
+
# This class is intended for use with Grape endpoint parameters that
|
5
|
+
# have been declared to be of variant-type using the +:types+ option.
|
6
|
+
# +MultipleTypeCoercer+ will build a coercer for each type declared
|
7
|
+
# in the array passed to +:types+ using {Types.build_coercer}. It will
|
8
|
+
# apply these coercers to parameter values in the order given to
|
9
|
+
# +:types+, and will return the value returned by the first coercer
|
10
|
+
# to successfully coerce the parameter value. Therefore if +String+ is
|
11
|
+
# an allowed type it should be declared last, since it will always
|
12
|
+
# successfully "coerce" the value.
|
13
|
+
class MultipleTypeCoercer
|
14
|
+
# Construct a new coercer that will attempt to coerce
|
15
|
+
# values to the given list of types in the given order.
|
16
|
+
#
|
17
|
+
# @param types [Array<Class>] list of allowed types
|
18
|
+
# @param method [#call,#parse] method by which values should be
|
19
|
+
# coerced. See class docs for default behaviour.
|
20
|
+
def initialize(types, method = nil)
|
21
|
+
@method = method.respond_to?(:parse) ? method.method(:parse) : method
|
22
|
+
|
23
|
+
@type_coercers = types.map do |type|
|
24
|
+
if Types.multiple? type
|
25
|
+
VariantCollectionCoercer.new type
|
26
|
+
else
|
27
|
+
Types.build_coercer type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# This method is called from somewhere within
|
33
|
+
# +Virtus::Attribute::coerce+ in order to coerce
|
34
|
+
# the given value.
|
35
|
+
#
|
36
|
+
# @param value [String] value to be coerced, in grape
|
37
|
+
# this should always be a string.
|
38
|
+
# @return [Object,InvalidValue] the coerced result, or an instance
|
39
|
+
# of {InvalidValue} if the value could not be coerced.
|
40
|
+
def call(value)
|
41
|
+
return @method.call(value) if @method
|
42
|
+
|
43
|
+
@type_coercers.each do |coercer|
|
44
|
+
coerced = coercer.coerce(value)
|
45
|
+
|
46
|
+
return coerced if coercer.value_coerced? coerced
|
47
|
+
end
|
48
|
+
|
49
|
+
# Declare that we couldn't coerce the value in such a way
|
50
|
+
# that Grape won't ask us again if the value is valid
|
51
|
+
InvalidValue.new
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method is called from somewhere within
|
55
|
+
# +Virtus::Attribute::value_coerced?+ in order to
|
56
|
+
# assert that the value has been coerced successfully.
|
57
|
+
# Due to Grape's design this will in fact only be called
|
58
|
+
# if a custom coercion method is being used, since {#call}
|
59
|
+
# returns an {InvalidValue} object if the value could not
|
60
|
+
# be coerced.
|
61
|
+
#
|
62
|
+
# @param _primitive [Axiom::Types::Type] primitive type
|
63
|
+
# for the coercion as detected by axiom-types' inference
|
64
|
+
# system. For custom types this is typically not much use
|
65
|
+
# (i.e. it is +Axiom::Types::Object+) unless special
|
66
|
+
# inference rules have been declared for the type.
|
67
|
+
# @param value [Object] a coerced result returned from {#call}
|
68
|
+
# @return [true,false] whether or not the coerced value
|
69
|
+
# satisfies type requirements.
|
70
|
+
def success?(_primitive, value)
|
71
|
+
@type_coercers.any? { |coercer| coercer.value_coerced? value }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Grape
|
2
|
+
module Validations
|
3
|
+
module Types
|
4
|
+
# This class wraps {MultipleTypeCoercer}, for use with collections
|
5
|
+
# that allow members of more than one type.
|
6
|
+
class VariantCollectionCoercer < Virtus::Attribute
|
7
|
+
# Construct a new coercer that will attempt to coerce
|
8
|
+
# a list of values such that all members are of one of
|
9
|
+
# the given types. The container may also optionally be
|
10
|
+
# coerced to a +Set+. An arbitrary coercion +method+ may
|
11
|
+
# be supplied, which will be passed the entire collection
|
12
|
+
# as a parameter and should return a new collection, or
|
13
|
+
# may return the same one if no coercion was required.
|
14
|
+
#
|
15
|
+
# @param types [Array<Class>,Set<Class>] list of allowed types,
|
16
|
+
# also specifying the container type
|
17
|
+
# @param method [#call,#parse] method by which values should be coerced
|
18
|
+
def initialize(types, method = nil)
|
19
|
+
@types = types
|
20
|
+
@method = method.respond_to?(:parse) ? method.method(:parse) : method
|
21
|
+
|
22
|
+
# If we have a coercion method, pass it in here to save
|
23
|
+
# building another one, even though we call it directly.
|
24
|
+
@member_coercer = MultipleTypeCoercer.new types, method
|
25
|
+
end
|
26
|
+
|
27
|
+
# Coerce the given value.
|
28
|
+
#
|
29
|
+
# @param value [Array<String>] collection of values to be coerced
|
30
|
+
# @return [Array<Object>,Set<Object>,InvalidValue]
|
31
|
+
# the coerced result, or an instance
|
32
|
+
# of {InvalidValue} if the value could not be coerced.
|
33
|
+
def coerce(value)
|
34
|
+
return InvalidValue.new unless value.is_a? Array
|
35
|
+
|
36
|
+
value =
|
37
|
+
if @method
|
38
|
+
@method.call(value)
|
39
|
+
else
|
40
|
+
value.map { |v| @member_coercer.call(v) }
|
41
|
+
end
|
42
|
+
return Set.new value if @types.is_a? Set
|
43
|
+
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
# Assert that the value has been coerced successfully.
|
48
|
+
#
|
49
|
+
# @param value [Object] a coerced result returned from {#coerce}
|
50
|
+
# @return [true,false] whether or not the coerced value
|
51
|
+
# satisfies type requirements.
|
52
|
+
def value_coerced?(value)
|
53
|
+
value.is_a?(@types.class) &&
|
54
|
+
value.all? { |v| @member_coercer.success?(@types, v) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|