-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Refactor and extend coercion and type validation #1167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 was deleted.
Oops, something went wrong.
This file contains hidden or 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,125 @@ | ||
require_relative 'types/build_coercer' | ||
require_relative 'types/custom_type_coercer' | ||
require_relative 'types/json' | ||
require_relative 'types/file' | ||
|
||
# Patch for Virtus::Attribute::Collection | ||
# See the file for more details | ||
require_relative 'types/virtus_collection_patch' | ||
|
||
module Grape | ||
module Validations | ||
# Module for code related to grape's system for | ||
# coercion and type validation of incoming request | ||
# parameters. | ||
# | ||
# Grape uses a number of tests and assertions to | ||
# work out exactly how a parameter should be handled, | ||
# based on the +type+ and +coerce_with+ options that | ||
# may be supplied to {Grape::Dsl::Parameters#requires} | ||
# and {Grape::Dsl::Parameters#optional}. The main | ||
# entry point for this process is {Types.build_coercer}. | ||
module Types | ||
# Types representing a single value, which are coerced through Virtus | ||
# or special logic in Grape. | ||
PRIMITIVES = [ | ||
# Numerical | ||
Integer, | ||
Float, | ||
BigDecimal, | ||
Numeric, | ||
|
||
# Date/time | ||
Date, | ||
DateTime, | ||
Time, | ||
|
||
# Misc | ||
Virtus::Attribute::Boolean, | ||
String, | ||
Symbol, | ||
Rack::Multipart::UploadedFile | ||
] | ||
|
||
# Types representing data structures. | ||
STRUCTURES = [ | ||
Hash, | ||
Array, | ||
Set | ||
] | ||
|
||
# Types for which Grape provides special coercion | ||
# and type-checking logic. | ||
SPECIAL = { | ||
JSON => Json, | ||
Array[JSON] => JsonArray, | ||
::File => File, | ||
Rack::Multipart::UploadedFile => File | ||
} | ||
|
||
# Is the given class a primitive type as recognized by Grape? | ||
# | ||
# @param type [Class] type to check | ||
# @return [Boolean] whether or not the type is known by Grape as a valid | ||
# type for a single value | ||
def self.primitive?(type) | ||
PRIMITIVES.include?(type) | ||
end | ||
|
||
# Is the given class a standard data structure (collection or map) | ||
# as recognized by Grape? | ||
# | ||
# @param type [Class] type to check | ||
# @return [Boolean] whether or not the type is known by Grape as a valid | ||
# data structure type | ||
# @note This method does not yet consider 'complex types', which inherit | ||
# Virtus.model. | ||
def self.structure?(type) | ||
STRUCTURES.include?(type) | ||
end | ||
|
||
# Does the given class implement a type system that Grape | ||
# (i.e. the underlying virtus attribute system) supports | ||
# out-of-the-box? Currently supported are +axiom-types+ | ||
# and +virtus+. | ||
# | ||
# The type will be passed to +Virtus::Attribute.build+, | ||
# and the resulting attribute object will be expected to | ||
# respond correctly to +coerce+ and +value_coerced?+. | ||
# | ||
# @param type [Class] type to check | ||
# @return [Boolean] +true+ where the type is recognized | ||
def self.recognized?(type) | ||
return false if type.is_a?(Array) || type.is_a?(Set) | ||
|
||
type.is_a?(Virtus::Attribute) || | ||
type.ancestors.include?(Axiom::Types::Type) || | ||
type.include?(Virtus::Model::Core) | ||
end | ||
|
||
# Does Grape provide special coercion and validation | ||
# routines for the given class? This does not include | ||
# automatic handling for primitives, structures and | ||
# otherwise recognized types. See {Types::SPECIAL}. | ||
# | ||
# @param type [Class] type to check | ||
# @return [Boolean] +true+ if special routines are available | ||
def self.special?(type) | ||
SPECIAL.key? type | ||
end | ||
|
||
# A valid custom type must implement a class-level `parse` method, taking | ||
# one String argument and returning the parsed value in its correct type. | ||
# @param type [Class] type to check | ||
# @return [Boolean] whether or not the type can be used as a custom type | ||
def self.custom?(type) | ||
!primitive?(type) && | ||
!structure?(type) && | ||
!recognized?(type) && | ||
!special?(type) && | ||
type.respond_to?(:parse) && | ||
type.method(:parse).arity == 1 | ||
end | ||
end | ||
end | ||
end |
This file contains hidden or 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,50 @@ | ||
module Grape | ||
module Validations | ||
module Types | ||
# Work out the +Virtus::Attribute+ object to | ||
# use for coercing strings to the given +type+. | ||
# Coercion +method+ will be inferred if none is | ||
# supplied. | ||
# | ||
# If a +Virtus::Attribute+ object already built | ||
# with +Virtus::Attribute.build+ is supplied as | ||
# the +type+ it will be returned and +method+ | ||
# will be ignored. | ||
# | ||
# See {CustomTypeCoercer} for further details | ||
# about coercion and type-checking inference. | ||
# | ||
# @param type [Class] the type to which input strings | ||
# should be coerced | ||
# @param method [Class,#call] the coercion method to use | ||
# @return [Virtus::Attribute] object to be used | ||
# for coercion and type validation | ||
def self.build_coercer(type, method = nil) | ||
if type.is_a? Virtus::Attribute | ||
# Accept pre-rolled virtus attributes without interference | ||
type | ||
else | ||
converter_options = { | ||
nullify_blank: true | ||
} | ||
conversion_type = type | ||
|
||
# Use a special coercer for custom types and coercion methods. | ||
if method || Types.custom?(type) | ||
converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method) | ||
|
||
# Grape swaps in its own Virtus::Attribute implementations | ||
# for certain special types that merit first-class support | ||
# (but not if a custom coercion method has been supplied). | ||
elsif Types.special?(type) | ||
conversion_type = Types::SPECIAL[type] | ||
end | ||
|
||
# Virtus will infer coercion and validation rules | ||
# for many common ruby types. | ||
Virtus::Attribute.build(conversion_type, converter_options) | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added
type: File
, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, doc'd and spec'd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe should be added to changelog as a line, like that we support
File
now out of the box.