-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow parameters with more than one type.
Adds `types` option for `requires` and `optional` endpoint parameter declarations, allowing for parameters that have more than one allowed type. See README.md for usage.
- Loading branch information
Showing
10 changed files
with
416 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
module Grape | ||
module Validations | ||
module Types | ||
# This class is intended for use with Grape endpoint parameters that | ||
# have been declared to be of variant-type using the +:types+ option. | ||
# +MultipleTypeCoercer+ will build a coercer for each type declared | ||
# in the array passed to +:types+ using {Types.build_coercer}. It will | ||
# apply these coercers to parameter values in the order given to | ||
# +:types+, and will return the value returned by the first coercer | ||
# to successfully coerce the parameter value. Therefore if +String+ is | ||
# an allowed type it should be declared last, since it will always | ||
# successfully "coerce" the value. | ||
class MultipleTypeCoercer | ||
# Construct a new coercer that will attempt to coerce | ||
# values to the given list of types in the given order. | ||
# | ||
# @param types [Array<Class>] list of allowed types | ||
# @param method [#call,#parse] method by which values should be | ||
# coerced. See class docs for default behaviour. | ||
def initialize(types, method = nil) | ||
@method = method.respond_to?(:parse) ? method.method(:parse) : method | ||
|
||
@type_coercers = types.map do |type| | ||
if Types.multiple? type | ||
VariantCollectionCoercer.new type | ||
else | ||
Types.build_coercer type | ||
end | ||
end | ||
end | ||
|
||
# This method is called from somewhere within | ||
# +Virtus::Attribute::coerce+ in order to coerce | ||
# the given value. | ||
# | ||
# @param value [String] value to be coerced, in grape | ||
# this should always be a string. | ||
# @return [Object,InvalidValue] the coerced result, or an instance | ||
# of {InvalidValue} if the value could not be coerced. | ||
def call(value) | ||
return @method.call(value) if @method | ||
|
||
@type_coercers.each do |coercer| | ||
coerced = coercer.coerce(value) | ||
|
||
return coerced if coercer.value_coerced? coerced | ||
end | ||
|
||
# Declare that we couldn't coerce the value in such a way | ||
# that Grape won't ask us again if the value is valid | ||
InvalidValue.new | ||
end | ||
|
||
# This method is called from somewhere within | ||
# +Virtus::Attribute::value_coerced?+ in order to | ||
# assert that the value has been coerced successfully. | ||
# Due to Grape's design this will in fact only be called | ||
# if a custom coercion method is being used, since {#call} | ||
# returns an {InvalidValue} object if the value could not | ||
# be coerced. | ||
# | ||
# @param _primitive [Axiom::Types::Type] primitive type | ||
# for the coercion as detected by axiom-types' inference | ||
# system. For custom types this is typically not much use | ||
# (i.e. it is +Axiom::Types::Object+) unless special | ||
# inference rules have been declared for the type. | ||
# @param value [Object] a coerced result returned from {#call} | ||
# @return [true,false] whether or not the coerced value | ||
# satisfies type requirements. | ||
def success?(_primitive, value) | ||
@type_coercers.any? { |coercer| coercer.value_coerced? value } | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
module Grape | ||
module Validations | ||
module Types | ||
# This class wraps {MultipleTypeCoercer}, for use with collections | ||
# that allow members of more than one type. | ||
class VariantCollectionCoercer < Virtus::Attribute | ||
# Construct a new coercer that will attempt to coerce | ||
# a list of values such that all members are of one of | ||
# the given types. The container may also optionally be | ||
# coerced to a +Set+. An arbitrary coercion +method+ may | ||
# be supplied, which will be passed the entire collection | ||
# as a parameter and should return a new collection, or | ||
# may return the same one if no coercion was required. | ||
# | ||
# @param types [Array<Class>,Set<Class>] list of allowed types, | ||
# also specifying the container type | ||
# @param method [#call,#parse] method by which values should be coerced | ||
def initialize(types, method = nil) | ||
@types = types | ||
@method = method.respond_to?(:parse) ? method.method(:parse) : method | ||
|
||
# If we have a coercion method, pass it in here to save | ||
# building another one, even though we call it directly. | ||
@member_coercer = MultipleTypeCoercer.new types, method | ||
end | ||
|
||
# Coerce the given value. | ||
# | ||
# @param value [Array<String>] collection of values to be coerced | ||
# @return [Array<Object>,Set<Object>,InvalidValue] | ||
# the coerced result, or an instance | ||
# of {InvalidValue} if the value could not be coerced. | ||
def coerce(value) | ||
return InvalidValue.new unless value.is_a? Array | ||
|
||
value = | ||
if @method | ||
@method.call(value) | ||
else | ||
value.map { |v| @member_coercer.call(v) } | ||
end | ||
return Set.new value if @types.is_a? Set | ||
|
||
value | ||
end | ||
|
||
# Assert that the value has been coerced successfully. | ||
# | ||
# @param value [Object] a coerced result returned from {#coerce} | ||
# @return [true,false] whether or not the coerced value | ||
# satisfies type requirements. | ||
def value_coerced?(value) | ||
value.is_a?(@types.class) && | ||
value.all? { |v| @member_coercer.success?(@types, v) } | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.