grape 0.1.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1167 -0
- data/CONTRIBUTING.md +156 -0
- data/LICENSE +1 -1
- data/README.md +4192 -0
- data/UPGRADING.md +1697 -0
- data/grape.gemspec +28 -105
- data/grape.png +0 -0
- data/lib/grape/api/helpers.rb +9 -0
- data/lib/grape/api/instance.rb +247 -0
- data/lib/grape/api.rb +158 -194
- data/lib/grape/content_types.rb +31 -0
- data/lib/grape/cookies.rb +50 -0
- data/lib/grape/dry_types.rb +10 -0
- data/lib/grape/dsl/api.rb +17 -0
- data/lib/grape/dsl/callbacks.rb +69 -0
- data/lib/grape/dsl/configuration.rb +15 -0
- data/lib/grape/dsl/desc.rb +124 -0
- data/lib/grape/dsl/headers.rb +21 -0
- data/lib/grape/dsl/helpers.rb +108 -0
- data/lib/grape/dsl/inside_route.rb +454 -0
- data/lib/grape/dsl/logger.rb +22 -0
- data/lib/grape/dsl/middleware.rb +54 -0
- data/lib/grape/dsl/parameters.rb +266 -0
- data/lib/grape/dsl/request_response.rb +171 -0
- data/lib/grape/dsl/routing.rb +248 -0
- data/lib/grape/dsl/settings.rb +179 -0
- data/lib/grape/dsl/validations.rb +57 -0
- data/lib/grape/endpoint.rb +398 -68
- data/lib/grape/env.rb +20 -0
- data/lib/grape/error_formatter/base.rb +68 -0
- data/lib/grape/error_formatter/json.rb +28 -0
- data/lib/grape/error_formatter/serializable_hash.rb +7 -0
- data/lib/grape/error_formatter/txt.rb +21 -0
- data/lib/grape/error_formatter/xml.rb +11 -0
- data/lib/grape/error_formatter.rb +15 -0
- data/lib/grape/exceptions/base.rb +76 -0
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/incompatible_option_values.rb +11 -0
- data/lib/grape/exceptions/invalid_accept_header.rb +11 -0
- data/lib/grape/exceptions/invalid_formatter.rb +11 -0
- data/lib/grape/exceptions/invalid_message_body.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/invalid_response.rb +11 -0
- data/lib/grape/exceptions/invalid_version_header.rb +11 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +11 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +11 -0
- data/lib/grape/exceptions/method_not_allowed.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +13 -0
- data/lib/grape/exceptions/missing_mime_type.rb +11 -0
- data/lib/grape/exceptions/missing_option.rb +11 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +11 -0
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_options.rb +11 -0
- data/lib/grape/exceptions/unknown_parameter.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unknown_validator.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +13 -0
- data/lib/grape/exceptions/validation.rb +25 -0
- data/lib/grape/exceptions/validation_array_errors.rb +14 -0
- data/lib/grape/exceptions/validation_errors.rb +53 -0
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +24 -0
- data/lib/grape/extensions/hash.rb +27 -0
- data/lib/grape/extensions/hashie/mash.rb +24 -0
- data/lib/grape/formatter/base.rb +16 -0
- data/lib/grape/formatter/json.rb +13 -0
- data/lib/grape/formatter/serializable_hash.rb +39 -0
- data/lib/grape/formatter/txt.rb +11 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/formatter.rb +17 -0
- data/lib/grape/json.rb +10 -0
- data/lib/grape/locale/en.yml +59 -0
- data/lib/grape/middleware/auth/base.rb +23 -0
- data/lib/grape/middleware/auth/dsl.rb +39 -0
- data/lib/grape/middleware/auth/strategies.rb +25 -0
- data/lib/grape/middleware/auth/strategy_info.rb +15 -0
- data/lib/grape/middleware/base.rb +89 -18
- data/lib/grape/middleware/error.rb +129 -10
- data/lib/grape/middleware/filter.rb +19 -0
- data/lib/grape/middleware/formatter.rb +124 -82
- data/lib/grape/middleware/globals.rb +14 -0
- data/lib/grape/middleware/stack.rb +109 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +38 -0
- data/lib/grape/middleware/versioner/base.rb +74 -0
- data/lib/grape/middleware/versioner/header.rb +127 -0
- data/lib/grape/middleware/versioner/param.rb +32 -0
- data/lib/grape/middleware/versioner/path.rb +40 -0
- data/lib/grape/middleware/versioner.rb +21 -21
- data/lib/grape/namespace.rb +46 -0
- data/lib/grape/params_builder/base.rb +18 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/parser/base.rb +16 -0
- data/lib/grape/parser/json.rb +14 -0
- data/lib/grape/parser/xml.rb +14 -0
- data/lib/grape/parser.rb +15 -0
- data/lib/grape/path.rb +76 -0
- data/lib/grape/presenters/presenter.rb +11 -0
- data/lib/grape/railtie.rb +9 -0
- data/lib/grape/request.rb +190 -0
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +81 -0
- data/lib/grape/router/route.rb +62 -0
- data/lib/grape/router.rb +190 -0
- data/lib/grape/serve_stream/file_body.rb +36 -0
- data/lib/grape/serve_stream/sendfile_response.rb +21 -0
- data/lib/grape/serve_stream/stream_response.rb +23 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/base_inheritable.rb +43 -0
- data/lib/grape/util/cache.rb +17 -0
- data/lib/grape/util/endpoint_configuration.rb +8 -0
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_setting.rb +100 -0
- data/lib/grape/util/inheritable_values.rb +29 -0
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/registry.rb +27 -0
- data/lib/grape/util/reverse_stackable_values.rb +15 -0
- data/lib/grape/util/stackable_values.rb +36 -0
- data/lib/grape/util/strict_hash_configuration.rb +108 -0
- data/lib/grape/validations/attributes_doc.rb +60 -0
- data/lib/grape/validations/attributes_iterator.rb +62 -0
- data/lib/grape/validations/contract_scope.rb +34 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +13 -0
- data/lib/grape/validations/params_scope.rb +538 -0
- data/lib/grape/validations/single_attribute_iterator.rb +26 -0
- data/lib/grape/validations/types/array_coercer.rb +61 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +164 -0
- data/lib/grape/validations/types/custom_type_collection_coercer.rb +56 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +66 -0
- data/lib/grape/validations/types/file.rb +31 -0
- data/lib/grape/validations/types/invalid_value.rb +17 -0
- data/lib/grape/validations/types/json.rb +71 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +57 -0
- data/lib/grape/validations/types/primitive_coercer.rb +73 -0
- data/lib/grape/validations/types/set_coercer.rb +35 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +51 -0
- data/lib/grape/validations/types.rb +213 -0
- data/lib/grape/validations/validator_factory.rb +15 -0
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +88 -0
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
- data/lib/grape/validations/validators/default_validator.rb +37 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +34 -0
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +60 -0
- data/lib/grape/validations.rb +21 -0
- data/lib/grape/version.rb +6 -0
- data/lib/grape/xml.rb +10 -0
- data/lib/grape.rb +81 -17
- metadata +250 -192
- data/.document +0 -5
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/Gemfile +0 -21
- data/Gemfile.lock +0 -58
- data/README.markdown +0 -75
- data/Rakefile +0 -70
- data/VERSION +0 -1
- data/autotest/discover.rb +0 -1
- data/lib/grape/middleware/auth/basic.rb +0 -30
- data/lib/grape/middleware/auth/oauth2.rb +0 -55
- data/lib/grape/middleware/prefixer.rb +0 -21
- data/lib/grape/middleware_stack.rb +0 -35
- data/spec/grape/api_spec.rb +0 -343
- data/spec/grape/endpoint_spec.rb +0 -104
- data/spec/grape/middleware/auth/basic_spec.rb +0 -31
- data/spec/grape/middleware/auth/oauth2_spec.rb +0 -88
- data/spec/grape/middleware/base_spec.rb +0 -63
- data/spec/grape/middleware/error_spec.rb +0 -49
- data/spec/grape/middleware/formatter_spec.rb +0 -87
- data/spec/grape/middleware/prefixer_spec.rb +0 -30
- data/spec/grape/middleware/versioner_spec.rb +0 -40
- data/spec/grape/middleware_stack_spec.rb +0 -47
- data/spec/grape_spec.rb +0 -1
- data/spec/spec_helper.rb +0 -20
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# This class will detect type classes that implement
|
|
7
|
+
# a class-level +parse+ method. The method should accept one
|
|
8
|
+
# +String+ argument and should return the value coerced to
|
|
9
|
+
# the appropriate type. The method may raise an exception if
|
|
10
|
+
# there are any problems parsing the string.
|
|
11
|
+
#
|
|
12
|
+
# Alternately an optional +method+ may be supplied (see the
|
|
13
|
+
# +coerce_with+ option of {Grape::Dsl::Parameters#requires}).
|
|
14
|
+
# This may be any class or object implementing +parse+ or +call+,
|
|
15
|
+
# with the same contract as described above.
|
|
16
|
+
#
|
|
17
|
+
# Type Checking
|
|
18
|
+
# -------------
|
|
19
|
+
#
|
|
20
|
+
# Calls to +coerced?+ will consult this class to check
|
|
21
|
+
# that the coerced value produced above is in fact of the
|
|
22
|
+
# expected type. By default this class performs a basic check
|
|
23
|
+
# against the type supplied, but this behaviour will be
|
|
24
|
+
# overridden if the class implements a class-level
|
|
25
|
+
# +coerced?+ or +parsed?+ method. This method
|
|
26
|
+
# will receive a single parameter that is the coerced value
|
|
27
|
+
# and should return +true+ if the value meets type expectations.
|
|
28
|
+
# Arbitrary assertions may be made here but the grape validation
|
|
29
|
+
# system should be preferred.
|
|
30
|
+
#
|
|
31
|
+
# Alternately a proc or other object responding to +call+ may be
|
|
32
|
+
# supplied in place of a type. This should implement the same
|
|
33
|
+
# contract as +coerced?+, and must be supplied with a coercion
|
|
34
|
+
# +method+.
|
|
35
|
+
class CustomTypeCoercer
|
|
36
|
+
# A new coercer for the given type specification
|
|
37
|
+
# and coercion method.
|
|
38
|
+
#
|
|
39
|
+
# @param type [Class,#coerced?,#parsed?,#call?]
|
|
40
|
+
# specifier for the target type. See class docs.
|
|
41
|
+
# @param method [#parse,#call]
|
|
42
|
+
# optional coercion method. See class docs.
|
|
43
|
+
def initialize(type, method = nil)
|
|
44
|
+
coercion_method = infer_coercion_method type, method
|
|
45
|
+
@method = enforce_symbolized_keys type, coercion_method
|
|
46
|
+
@type_check = infer_type_check(type)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Coerces the given value.
|
|
50
|
+
#
|
|
51
|
+
# @param value [String] value to be coerced, in grape
|
|
52
|
+
# this should always be a string.
|
|
53
|
+
# @return [Object] the coerced result
|
|
54
|
+
def call(val)
|
|
55
|
+
coerced_val = @method.call(val)
|
|
56
|
+
|
|
57
|
+
return coerced_val if coerced_val.is_a?(InvalidValue)
|
|
58
|
+
return InvalidValue.new unless coerced?(coerced_val)
|
|
59
|
+
|
|
60
|
+
coerced_val
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def coerced?(val)
|
|
64
|
+
val.nil? || @type_check.call(val)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Determine the coercion method we're expected to use
|
|
70
|
+
# based on the parameters given.
|
|
71
|
+
#
|
|
72
|
+
# @param type see #new
|
|
73
|
+
# @param method see #new
|
|
74
|
+
# @return [#call] coercion method
|
|
75
|
+
def infer_coercion_method(type, method)
|
|
76
|
+
if method
|
|
77
|
+
if method.respond_to? :parse
|
|
78
|
+
method.method :parse
|
|
79
|
+
else
|
|
80
|
+
method
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
# Try to use parse() declared on the target type.
|
|
84
|
+
# This may raise an exception, but we are out of ideas anyway.
|
|
85
|
+
type.method :parse
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Determine how the type validity of a coerced
|
|
90
|
+
# value should be decided.
|
|
91
|
+
#
|
|
92
|
+
# @param type see #new
|
|
93
|
+
# @return [#call] a procedure which accepts a single parameter
|
|
94
|
+
# and returns +true+ if the passed object is of the correct type.
|
|
95
|
+
def infer_type_check(type)
|
|
96
|
+
# First check for special class methods
|
|
97
|
+
if type.respond_to? :coerced?
|
|
98
|
+
type.method :coerced?
|
|
99
|
+
elsif type.respond_to? :parsed?
|
|
100
|
+
type.method :parsed?
|
|
101
|
+
elsif type.respond_to? :call
|
|
102
|
+
# Arbitrary proc passed for type validation.
|
|
103
|
+
# Note that this will fail unless a method is also
|
|
104
|
+
# passed, or if the type also implements a parse() method.
|
|
105
|
+
type
|
|
106
|
+
elsif type.is_a?(Enumerable)
|
|
107
|
+
lambda do |value|
|
|
108
|
+
value.is_a?(Enumerable) && value.all? do |val|
|
|
109
|
+
recursive_type_check(type.first, val)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
else
|
|
113
|
+
# By default, do a simple type check
|
|
114
|
+
->(value) { value.is_a? type }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def recursive_type_check(type, value)
|
|
119
|
+
if type.is_a?(Enumerable) && value.is_a?(Enumerable)
|
|
120
|
+
value.all? { |val| recursive_type_check(type.first, val) }
|
|
121
|
+
else
|
|
122
|
+
!type.is_a?(Enumerable) && value.is_a?(type)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Enforce symbolized keys for complex types
|
|
127
|
+
# by wrapping the coercion method such that
|
|
128
|
+
# any Hash objects in the immediate heirarchy
|
|
129
|
+
# have their keys recursively symbolized.
|
|
130
|
+
# This helps common libs such as JSON to work easily.
|
|
131
|
+
#
|
|
132
|
+
# @param type see #new
|
|
133
|
+
# @param method see #infer_coercion_method
|
|
134
|
+
# @return [#call] +method+ wrapped in an additional
|
|
135
|
+
# key-conversion step, or just returns +method+
|
|
136
|
+
# itself if no conversion is deemed to be
|
|
137
|
+
# necessary.
|
|
138
|
+
def enforce_symbolized_keys(type, method)
|
|
139
|
+
# Collections have all values processed individually
|
|
140
|
+
if [Array, Set].include?(type)
|
|
141
|
+
lambda do |val|
|
|
142
|
+
method.call(val).tap do |new_val|
|
|
143
|
+
new_val.map do |item|
|
|
144
|
+
item.is_a?(Hash) ? item.deep_symbolize_keys : item
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Hash objects are processed directly
|
|
150
|
+
elsif type == Hash
|
|
151
|
+
lambda do |val|
|
|
152
|
+
method.call(val).deep_symbolize_keys
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Simple types are not processed.
|
|
156
|
+
# This includes Array<primitive> types.
|
|
157
|
+
else
|
|
158
|
+
method
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# See {CustomTypeCoercer} for details on types
|
|
7
|
+
# that will be supported by this by this coercer.
|
|
8
|
+
# This coercer works in the same way as +CustomTypeCoercer+
|
|
9
|
+
# except that it expects to receive an array of strings to
|
|
10
|
+
# coerce and will return an array (or optionally, a set)
|
|
11
|
+
# of coerced values.
|
|
12
|
+
#
|
|
13
|
+
# +CustomTypeCoercer+ is already capable of providing type
|
|
14
|
+
# checking for arrays where an independent coercion method
|
|
15
|
+
# is supplied. As such, +CustomTypeCollectionCoercer+ does
|
|
16
|
+
# not allow for such a method to be supplied independently
|
|
17
|
+
# of the type.
|
|
18
|
+
class CustomTypeCollectionCoercer < CustomTypeCoercer
|
|
19
|
+
# A new coercer for collections of the given type.
|
|
20
|
+
#
|
|
21
|
+
# @param type [Class,#parse]
|
|
22
|
+
# type to which items in the array should be coerced.
|
|
23
|
+
# Must implement a +parse+ method which accepts a string,
|
|
24
|
+
# and for the purposes of type-checking it may either be
|
|
25
|
+
# a class, or it may implement a +coerced?+, +parsed?+ or
|
|
26
|
+
# +call+ method (in that order of precedence) which
|
|
27
|
+
# accepts a single argument and returns true if the given
|
|
28
|
+
# array item has been coerced correctly.
|
|
29
|
+
# @param set [Boolean]
|
|
30
|
+
# when true, a +Set+ will be returned by {#call} instead
|
|
31
|
+
# of an +Array+ and duplicate items will be discarded.
|
|
32
|
+
def initialize(type, set = false)
|
|
33
|
+
super(type)
|
|
34
|
+
@set = set
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Coerces the given value.
|
|
38
|
+
#
|
|
39
|
+
# @param value [Array<String>] an array of values to be coerced
|
|
40
|
+
# @return [Array,Set] the coerced result. May be an +Array+ or a
|
|
41
|
+
# +Set+ depending on the setting given to the constructor
|
|
42
|
+
def call(value)
|
|
43
|
+
coerced = value.map do |item|
|
|
44
|
+
coerced_item = super(item)
|
|
45
|
+
|
|
46
|
+
return coerced_item if coerced_item.is_a?(InvalidValue)
|
|
47
|
+
|
|
48
|
+
coerced_item
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@set ? Set.new(coerced) : coerced
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DryTypes
|
|
4
|
+
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
|
5
|
+
# a container in this case. Check documentation for more information
|
|
6
|
+
# https://dry-rb.org/gems/dry-types/1.2/getting-started/
|
|
7
|
+
include Dry.Types()
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Grape
|
|
11
|
+
module Validations
|
|
12
|
+
module Types
|
|
13
|
+
# A base class for classes which must identify a coercer to be used.
|
|
14
|
+
# If the +strict+ argument is true, it won't coerce the given value
|
|
15
|
+
# but check its type. More information there
|
|
16
|
+
# https://dry-rb.org/gems/dry-types/1.2/built-in-types/
|
|
17
|
+
class DryTypeCoercer
|
|
18
|
+
class << self
|
|
19
|
+
# Returns a collection coercer which corresponds to a given type.
|
|
20
|
+
# Example:
|
|
21
|
+
#
|
|
22
|
+
# collection_coercer_for(Array)
|
|
23
|
+
# #=> Grape::Validations::Types::ArrayCoercer
|
|
24
|
+
def collection_coercer_for(type)
|
|
25
|
+
case type
|
|
26
|
+
when Array
|
|
27
|
+
ArrayCoercer
|
|
28
|
+
when Set
|
|
29
|
+
SetCoercer
|
|
30
|
+
else
|
|
31
|
+
raise ArgumentError, "Unknown type: #{type}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns an instance of a coercer for a given type
|
|
36
|
+
def coercer_instance_for(type, strict = false)
|
|
37
|
+
klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)
|
|
38
|
+
klass.new(type, strict)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(type, strict = false)
|
|
43
|
+
@type = type
|
|
44
|
+
@strict = strict
|
|
45
|
+
@scope = strict ? DryTypes::Strict : DryTypes::Params
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Coerces the given value to a type which was specified during
|
|
49
|
+
# initialization as a type argument.
|
|
50
|
+
#
|
|
51
|
+
# @param val [Object]
|
|
52
|
+
def call(val)
|
|
53
|
+
return if val.nil?
|
|
54
|
+
|
|
55
|
+
@coercer[val]
|
|
56
|
+
rescue Dry::Types::CoercionError => _e
|
|
57
|
+
InvalidValue.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
protected
|
|
61
|
+
|
|
62
|
+
attr_reader :scope, :type, :strict
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# Implementation for parameters that are multipart file objects.
|
|
7
|
+
# Actual handling of these objects is provided by +Rack::Request+;
|
|
8
|
+
# this class is here only to assert that rack's handling has succeeded.
|
|
9
|
+
class File
|
|
10
|
+
class << self
|
|
11
|
+
def parse(input)
|
|
12
|
+
return if input.nil?
|
|
13
|
+
return InvalidValue.new unless parsed?(input)
|
|
14
|
+
|
|
15
|
+
# Processing of multipart file objects
|
|
16
|
+
# is already taken care of by Rack::Request.
|
|
17
|
+
# Nothing to do here.
|
|
18
|
+
input
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def parsed?(value)
|
|
22
|
+
# Rack::Request creates a Hash with filename,
|
|
23
|
+
# content type and an IO object. Do a bit of basic
|
|
24
|
+
# duck-typing.
|
|
25
|
+
value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# Instances of this class may be used as tokens to denote that a parameter value could not be
|
|
7
|
+
# coerced. The given message will be used as a validation error.
|
|
8
|
+
class InvalidValue
|
|
9
|
+
attr_reader :message
|
|
10
|
+
|
|
11
|
+
def initialize(message = nil)
|
|
12
|
+
@message = message
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# Handles coercion and type checking for parameters that are complex
|
|
7
|
+
# types given as JSON-encoded strings. It accepts both JSON objects
|
|
8
|
+
# and arrays of objects, and will coerce the input to a +Hash+
|
|
9
|
+
# or +Array+ object respectively. In either case the Grape
|
|
10
|
+
# validation system will apply nested validation rules to
|
|
11
|
+
# all returned objects.
|
|
12
|
+
class Json
|
|
13
|
+
class << self
|
|
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 parse(input)
|
|
19
|
+
return input if parsed?(input)
|
|
20
|
+
|
|
21
|
+
# Allow nulls and blank strings
|
|
22
|
+
return if input.nil? || input.match?(/^\s*$/)
|
|
23
|
+
|
|
24
|
+
JSON.parse(input, symbolize_names: true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Checks that the input was parsed successfully
|
|
28
|
+
# and isn't something odd such as an array of primitives.
|
|
29
|
+
#
|
|
30
|
+
# @param value [Object] result of {#parse}
|
|
31
|
+
# @return [true,false]
|
|
32
|
+
def parsed?(value)
|
|
33
|
+
value.is_a?(::Hash) || coerced_collection?(value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
protected
|
|
37
|
+
|
|
38
|
+
# Is the value an array of JSON-like objects?
|
|
39
|
+
#
|
|
40
|
+
# @param value [Object] result of {#parse}
|
|
41
|
+
# @return [true,false]
|
|
42
|
+
def coerced_collection?(value)
|
|
43
|
+
value.is_a?(::Array) && value.all?(::Hash)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Specialization of the {Json} attribute that is guaranteed
|
|
49
|
+
# to return an array of objects. Accepts both JSON-encoded
|
|
50
|
+
# objects and arrays of objects, but wraps single objects
|
|
51
|
+
# in an Array.
|
|
52
|
+
class JsonArray < Json
|
|
53
|
+
class << self
|
|
54
|
+
# See {Json#parse}. Wraps single objects in an array.
|
|
55
|
+
#
|
|
56
|
+
# @param input [String] JSON-encoded parameter value
|
|
57
|
+
# @return [Array<Hash>]
|
|
58
|
+
def parse(input)
|
|
59
|
+
json = super
|
|
60
|
+
Array.wrap(json) unless json.nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# See {Json#coerced_collection?}
|
|
64
|
+
def parsed?(value)
|
|
65
|
+
coerced_collection? value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# This class is intended for use with Grape endpoint parameters that
|
|
7
|
+
# have been declared to be of variant-type using the +:types+ option.
|
|
8
|
+
# +MultipleTypeCoercer+ will build a coercer for each type declared
|
|
9
|
+
# in the array passed to +:types+ using {Types.build_coercer}. It will
|
|
10
|
+
# apply these coercers to parameter values in the order given to
|
|
11
|
+
# +:types+, and will return the value returned by the first coercer
|
|
12
|
+
# to successfully coerce the parameter value. Therefore if +String+ is
|
|
13
|
+
# an allowed type it should be declared last, since it will always
|
|
14
|
+
# successfully "coerce" the value.
|
|
15
|
+
class MultipleTypeCoercer
|
|
16
|
+
# Construct a new coercer that will attempt to coerce
|
|
17
|
+
# values to the given list of types in the given order.
|
|
18
|
+
#
|
|
19
|
+
# @param types [Array<Class>] list of allowed types
|
|
20
|
+
# @param method [#call,#parse] method by which values should be
|
|
21
|
+
# coerced. See class docs for default behaviour.
|
|
22
|
+
def initialize(types, method = nil)
|
|
23
|
+
@method = method.respond_to?(:parse) ? method.method(:parse) : method
|
|
24
|
+
|
|
25
|
+
@type_coercers = types.map do |type|
|
|
26
|
+
if Types.multiple? type
|
|
27
|
+
VariantCollectionCoercer.new type, @method
|
|
28
|
+
else
|
|
29
|
+
Types.build_coercer type, strict: !@method.nil?
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Coerces the given value.
|
|
35
|
+
#
|
|
36
|
+
# @param val [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(val)
|
|
41
|
+
# once the value is coerced by the custom method, its type should be checked
|
|
42
|
+
val = @method.call(val) if @method
|
|
43
|
+
|
|
44
|
+
coerced_val = InvalidValue.new
|
|
45
|
+
|
|
46
|
+
@type_coercers.each do |coercer|
|
|
47
|
+
coerced_val = coercer.call(val)
|
|
48
|
+
|
|
49
|
+
return coerced_val unless coerced_val.is_a?(InvalidValue)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
coerced_val
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# Coerces the given value to a type defined via a +type+ argument during
|
|
7
|
+
# initialization. When +strict+ is true, it doesn't coerce a value but check
|
|
8
|
+
# that it has the proper type.
|
|
9
|
+
class PrimitiveCoercer < DryTypeCoercer
|
|
10
|
+
MAPPING = {
|
|
11
|
+
Grape::API::Boolean => DryTypes::Params::Bool,
|
|
12
|
+
BigDecimal => DryTypes::Params::Decimal,
|
|
13
|
+
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
|
14
|
+
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
|
15
|
+
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
|
16
|
+
|
|
17
|
+
# unfortunately, a +Params+ scope doesn't contain String
|
|
18
|
+
String => DryTypes::Coercible::String
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
STRICT_MAPPING = {
|
|
22
|
+
Grape::API::Boolean => DryTypes::Strict::Bool,
|
|
23
|
+
BigDecimal => DryTypes::Strict::Decimal,
|
|
24
|
+
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
|
25
|
+
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
|
26
|
+
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
def initialize(type, strict = false)
|
|
30
|
+
super
|
|
31
|
+
|
|
32
|
+
@type = type
|
|
33
|
+
|
|
34
|
+
@coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
|
|
35
|
+
scope.const_get(type.name, false)
|
|
36
|
+
rescue NameError
|
|
37
|
+
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
|
|
38
|
+
|
|
39
|
+
type
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call(val)
|
|
44
|
+
return InvalidValue.new if reject?(val)
|
|
45
|
+
return nil if val.nil? || treat_as_nil?(val)
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
attr_reader :type
|
|
53
|
+
|
|
54
|
+
# This method maintains logic which was defined by Virtus. For example,
|
|
55
|
+
# dry-types is ok to convert an array or a hash to a string, it is supported,
|
|
56
|
+
# but Virtus wouldn't accept it. So, this method only exists to not introduce
|
|
57
|
+
# breaking changes.
|
|
58
|
+
def reject?(val)
|
|
59
|
+
(val.is_a?(Array) && type == String) ||
|
|
60
|
+
(val.is_a?(String) && type == Hash) ||
|
|
61
|
+
(val.is_a?(Hash) && type == String)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Dry-Types treats an empty string as invalid. However, Grape considers an empty string as
|
|
65
|
+
# absence of a value and coerces it into nil. See a discussion there
|
|
66
|
+
# https://github.com/ruby-grape/grape/pull/2045
|
|
67
|
+
def treat_as_nil?(val)
|
|
68
|
+
val == '' && type != String
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# Takes the given array and converts it to a set. Every element of the set
|
|
7
|
+
# is also coerced.
|
|
8
|
+
class SetCoercer < ArrayCoercer
|
|
9
|
+
def initialize(type, strict = false)
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
@coercer = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(value)
|
|
16
|
+
return InvalidValue.new unless value.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
coerce_elements(value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
def coerce_elements(collection)
|
|
24
|
+
collection.each_with_object(Set.new) do |elem, memo|
|
|
25
|
+
coerced_elem = elem_coercer.call(elem)
|
|
26
|
+
|
|
27
|
+
return coerced_elem if coerced_elem.is_a?(InvalidValue)
|
|
28
|
+
|
|
29
|
+
memo.add(coerced_elem)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
module Types
|
|
6
|
+
# This class wraps {MultipleTypeCoercer}, for use with collections
|
|
7
|
+
# that allow members of more than one type.
|
|
8
|
+
class VariantCollectionCoercer
|
|
9
|
+
# Construct a new coercer that will attempt to coerce
|
|
10
|
+
# a list of values such that all members are of one of
|
|
11
|
+
# the given types. The container may also optionally be
|
|
12
|
+
# coerced to a +Set+. An arbitrary coercion +method+ may
|
|
13
|
+
# be supplied, which will be passed the entire collection
|
|
14
|
+
# as a parameter and should return a new collection, or
|
|
15
|
+
# may return the same one if no coercion was required.
|
|
16
|
+
#
|
|
17
|
+
# @param types [Array<Class>,Set<Class>] list of allowed types,
|
|
18
|
+
# also specifying the container type
|
|
19
|
+
# @param method [#call,#parse] method by which values should be coerced
|
|
20
|
+
def initialize(types, method = nil)
|
|
21
|
+
@types = types
|
|
22
|
+
@method = method.respond_to?(:parse) ? method.method(:parse) : method
|
|
23
|
+
|
|
24
|
+
# If we have a coercion method, pass it in here to save
|
|
25
|
+
# building another one, even though we call it directly.
|
|
26
|
+
@member_coercer = MultipleTypeCoercer.new types, method
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Coerce the given value.
|
|
30
|
+
#
|
|
31
|
+
# @param value [Array<String>] collection of values to be coerced
|
|
32
|
+
# @return [Array<Object>,Set<Object>,InvalidValue]
|
|
33
|
+
# the coerced result, or an instance
|
|
34
|
+
# of {InvalidValue} if the value could not be coerced.
|
|
35
|
+
def call(value)
|
|
36
|
+
return unless value.is_a? Array
|
|
37
|
+
|
|
38
|
+
value =
|
|
39
|
+
if @method
|
|
40
|
+
@method.call(value)
|
|
41
|
+
else
|
|
42
|
+
value.map { |v| @member_coercer.call(v) }
|
|
43
|
+
end
|
|
44
|
+
return Set.new value if @types.is_a? Set
|
|
45
|
+
|
|
46
|
+
value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|