grape 0.12.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 +265 -215
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +166 -0
- data/README.md +426 -161
- data/RELEASING.md +14 -6
- data/Rakefile +30 -33
- data/UPGRADING.md +54 -23
- 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 +6 -7
- data/lib/grape/api.rb +24 -4
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/configuration.rb +59 -2
- data/lib/grape/dsl/helpers.rb +8 -3
- data/lib/grape/dsl/inside_route.rb +100 -45
- data/lib/grape/dsl/parameters.rb +96 -7
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +17 -4
- data/lib/grape/dsl/settings.rb +36 -1
- data/lib/grape/dsl/validations.rb +7 -5
- data/lib/grape/endpoint.rb +102 -57
- 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/exceptions/unknown_parameter.rb +10 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -3
- data/lib/grape/formatter/serializable_hash.rb +3 -2
- data/lib/grape/http/headers.rb +0 -1
- data/lib/grape/locale/en.yml +5 -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 +8 -4
- data/lib/grape/middleware/error.rb +3 -2
- data/lib/grape/middleware/filter.rb +1 -1
- data/lib/grape/middleware/formatter.rb +64 -45
- 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/namespace.rb +13 -2
- data/lib/grape/path.rb +4 -3
- data/lib/grape/request.rb +40 -0
- data/lib/grape/route.rb +5 -0
- data/lib/grape/util/content_types.rb +9 -9
- data/lib/grape/util/env.rb +22 -0
- data/lib/grape/util/file_response.rb +21 -0
- data/lib/grape/util/inheritable_setting.rb +23 -2
- data/lib/grape/util/inheritable_values.rb +1 -1
- data/lib/grape/util/stackable_values.rb +5 -2
- 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 +164 -22
- 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/types.rb +144 -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 +32 -34
- 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/validations.rb +5 -0
- data/lib/grape/version.rb +2 -1
- data/lib/grape.rb +15 -12
- 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 +151 -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 +40 -4
- data/spec/grape/dsl/parameters_spec.rb +0 -6
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/dsl/validations_spec.rb +18 -0
- data/spec/grape/endpoint_spec.rb +130 -6
- data/spec/grape/entity_spec.rb +10 -8
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
- data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
- data/spec/grape/integration/rack_spec.rb +3 -2
- data/spec/grape/middleware/base_spec.rb +40 -16
- 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 -5
- 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/util/inheritable_values_spec.rb +14 -0
- data/spec/grape/util/stackable_values_spec.rb +10 -0
- data/spec/grape/validations/params_scope_spec.rb +86 -0
- data/spec/grape/validations/types_spec.rb +95 -0
- data/spec/grape/validations/validators/coerce_spec.rb +364 -10
- data/spec/grape/validations/validators/values_spec.rb +27 -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 +55 -14
- 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/backports/active_support/deep_dup.rb +0 -49
- data/lib/backports/active_support/duplicable.rb +0 -88
- data/lib/grape/http/request.rb +0 -27
@@ -0,0 +1,22 @@
|
|
1
|
+
module Grape
|
2
|
+
module Env
|
3
|
+
API_VERSION = 'api.version'.freeze
|
4
|
+
API_ENDPOINT = 'api.endpoint'.freeze
|
5
|
+
API_REQUEST_INPUT = 'api.request.input'.freeze
|
6
|
+
API_REQUEST_BODY = 'api.request.body'.freeze
|
7
|
+
API_TYPE = 'api.type'.freeze
|
8
|
+
API_SUBTYPE = 'api.subtype'.freeze
|
9
|
+
API_VENDOR = 'api.vendor'.freeze
|
10
|
+
API_FORMAT = 'api.format'.freeze
|
11
|
+
|
12
|
+
RACK_INPUT = 'rack.input'.freeze
|
13
|
+
RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'.freeze
|
14
|
+
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'.freeze
|
15
|
+
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'.freeze
|
16
|
+
RACK_ROUTING_ARGS = 'rack.routing_args'.freeze
|
17
|
+
|
18
|
+
GRAPE_REQUEST = 'grape.request'.freeze
|
19
|
+
GRAPE_REQUEST_HEADERS = 'grape.request.headers'.freeze
|
20
|
+
GRAPE_REQUEST_PARAMS = 'grape.request.params'.freeze
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Grape
|
2
|
+
module Util
|
3
|
+
# A simple class used to identify responses which represent files and do not
|
4
|
+
# need to be formatted or pre-read by Rack::Response
|
5
|
+
class FileResponse
|
6
|
+
attr_reader :file
|
7
|
+
|
8
|
+
# @param file [Object]
|
9
|
+
def initialize(file)
|
10
|
+
@file = file
|
11
|
+
end
|
12
|
+
|
13
|
+
# Equality provided mostly for tests.
|
14
|
+
#
|
15
|
+
# @return [Boolean]
|
16
|
+
def ==(other)
|
17
|
+
file == other.file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,22 +1,31 @@
|
|
1
1
|
module Grape
|
2
2
|
module Util
|
3
|
+
# A branchable, inheritable settings object which can store both stackable
|
4
|
+
# and inheritable values (see InheritableValues and StackableValues).
|
3
5
|
class InheritableSetting
|
4
6
|
attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable
|
5
7
|
attr_accessor :parent, :point_in_time_copies
|
6
8
|
|
9
|
+
# Retrieve global settings.
|
7
10
|
def self.global
|
8
11
|
@global ||= {}
|
9
12
|
end
|
10
13
|
|
11
|
-
|
14
|
+
# Clear all global settings.
|
15
|
+
# @api private
|
16
|
+
# @note only for testing
|
17
|
+
def self.reset_global!
|
12
18
|
@global = {}
|
13
19
|
end
|
14
20
|
|
21
|
+
# Instantiate a new settings instance, with blank values. The fresh
|
22
|
+
# instance can then be set to inherit from an existing instance (see
|
23
|
+
# #inherit_from).
|
15
24
|
def initialize
|
16
25
|
self.route = {}
|
17
26
|
self.api_class = {}
|
18
27
|
self.namespace = InheritableValues.new # only inheritable from a parent when
|
19
|
-
# used with a mount, or should every API::Class be a
|
28
|
+
# used with a mount, or should every API::Class be a separate namespace by default?
|
20
29
|
self.namespace_inheritable = InheritableValues.new
|
21
30
|
self.namespace_stackable = StackableValues.new
|
22
31
|
|
@@ -25,10 +34,15 @@ module Grape
|
|
25
34
|
self.parent = nil
|
26
35
|
end
|
27
36
|
|
37
|
+
# Return the class-level global properties.
|
28
38
|
def global
|
29
39
|
self.class.global
|
30
40
|
end
|
31
41
|
|
42
|
+
# Set our inherited values to the given parent's current values. Also,
|
43
|
+
# update the inherited values on any settings instances which were forked
|
44
|
+
# from us.
|
45
|
+
# @param parent [InheritableSetting]
|
32
46
|
def inherit_from(parent)
|
33
47
|
return if parent.nil?
|
34
48
|
|
@@ -41,6 +55,10 @@ module Grape
|
|
41
55
|
point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }
|
42
56
|
end
|
43
57
|
|
58
|
+
# Create a point-in-time copy of this settings instance, with clones of
|
59
|
+
# all our values. Note that, should this instance's parent be set or
|
60
|
+
# changed via #inherit_from, it will copy that inheritence to any copies
|
61
|
+
# which were made.
|
44
62
|
def point_in_time_copy
|
45
63
|
self.class.new.tap do |new_setting|
|
46
64
|
point_in_time_copies << new_setting
|
@@ -56,10 +74,13 @@ module Grape
|
|
56
74
|
end
|
57
75
|
end
|
58
76
|
|
77
|
+
# Resets the instance store of per-route settings.
|
78
|
+
# @api private
|
59
79
|
def route_end
|
60
80
|
@route = {}
|
61
81
|
end
|
62
82
|
|
83
|
+
# Return a serializable hash of our values.
|
63
84
|
def to_hash
|
64
85
|
{
|
65
86
|
global: global.clone,
|
@@ -13,7 +13,10 @@ module Grape
|
|
13
13
|
|
14
14
|
def [](name)
|
15
15
|
return @froozen_values[name] if @froozen_values.key? name
|
16
|
-
[@inherited_values[name], @new_values[name]]
|
16
|
+
value = [@inherited_values[name], @new_values[name]]
|
17
|
+
value.compact!
|
18
|
+
value.flatten!(1)
|
19
|
+
value
|
17
20
|
end
|
18
21
|
|
19
22
|
def []=(name, value)
|
@@ -45,7 +48,7 @@ module Grape
|
|
45
48
|
def initialize_copy(other)
|
46
49
|
super
|
47
50
|
self.inherited_values = other.inherited_values
|
48
|
-
self.new_values = other.new_values.
|
51
|
+
self.new_values = other.new_values.dup
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
@@ -64,7 +64,8 @@ module Grape
|
|
64
64
|
end
|
65
65
|
|
66
66
|
define_method 'to_hash' do
|
67
|
-
merge_hash =
|
67
|
+
merge_hash = {}
|
68
|
+
setting_name.each_key { |k| merge_hash[k] = send("#{k}_context").to_hash }
|
68
69
|
|
69
70
|
@settings.to_hash.merge(
|
70
71
|
merge_hash
|
@@ -3,15 +3,20 @@ module Grape
|
|
3
3
|
class AttributesIterator
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
+
attr_reader :scope
|
7
|
+
|
6
8
|
def initialize(validator, scope, params)
|
9
|
+
@scope = scope
|
7
10
|
@attrs = validator.attrs
|
8
|
-
@params = scope.params(params)
|
9
|
-
@params = (@params.is_a?(Array) ? @params : [@params])
|
11
|
+
@params = Array.wrap(scope.params(params))
|
10
12
|
end
|
11
13
|
|
12
14
|
def each
|
13
15
|
@params.each do |resource_params|
|
14
|
-
@attrs.
|
16
|
+
@attrs.each_with_index do |attr_name, index|
|
17
|
+
if resource_params.is_a?(Hash) && resource_params[attr_name].is_a?(Array)
|
18
|
+
scope.index = index
|
19
|
+
end
|
15
20
|
yield resource_params, attr_name
|
16
21
|
end
|
17
22
|
end
|
@@ -1,16 +1,30 @@
|
|
1
1
|
module Grape
|
2
2
|
module Validations
|
3
3
|
class ParamsScope
|
4
|
-
attr_accessor :element, :parent
|
4
|
+
attr_accessor :element, :parent, :index
|
5
5
|
|
6
6
|
include Grape::DSL::Parameters
|
7
7
|
|
8
|
+
# Open up a new ParamsScope, allowing parameter definitions per
|
9
|
+
# Grape::DSL::Params.
|
10
|
+
# @param opts [Hash] options for this scope
|
11
|
+
# @option opts :element [Symbol] the element that contains this scope; for
|
12
|
+
# this to be relevant, @parent must be set
|
13
|
+
# @option opts :parent [ParamsScope] the scope containing this scope
|
14
|
+
# @option opts :api [API] the API endpoint to modify
|
15
|
+
# @option opts :optional [Boolean] whether or not this scope needs to have
|
16
|
+
# any parameters set or not
|
17
|
+
# @option opts :type [Class] a type meant to govern this scope (deprecated)
|
18
|
+
# @option opts :dependent_on [Symbol] if present, this scope should only
|
19
|
+
# validate if this param is present in the parent scope
|
20
|
+
# @yield the instance context, open for parameter definitions
|
8
21
|
def initialize(opts, &block)
|
9
|
-
@element
|
10
|
-
@parent
|
11
|
-
@api
|
12
|
-
@optional
|
13
|
-
@type
|
22
|
+
@element = opts[:element]
|
23
|
+
@parent = opts[:parent]
|
24
|
+
@api = opts[:api]
|
25
|
+
@optional = opts[:optional] || false
|
26
|
+
@type = opts[:type]
|
27
|
+
@dependent_on = opts[:dependent_on]
|
14
28
|
@declared_params = []
|
15
29
|
|
16
30
|
instance_eval(&block) if block_given?
|
@@ -18,27 +32,63 @@ module Grape
|
|
18
32
|
configure_declared_params
|
19
33
|
end
|
20
34
|
|
35
|
+
# @return [Boolean] whether or not this entire scope needs to be
|
36
|
+
# validated
|
21
37
|
def should_validate?(parameters)
|
22
38
|
return false if @optional && params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?)
|
39
|
+
return false if @dependent_on && params(parameters).try(:[], @dependent_on).blank?
|
23
40
|
return true if parent.nil?
|
24
41
|
parent.should_validate?(parameters)
|
25
42
|
end
|
26
43
|
|
44
|
+
# @return [String] the proper attribute name, with nesting considered.
|
27
45
|
def full_name(name)
|
28
|
-
|
29
|
-
|
46
|
+
case
|
47
|
+
when nested?
|
48
|
+
# Find our containing element's name, and append ours.
|
49
|
+
"#{@parent.full_name(@element)}#{parent_index}[#{name}]"
|
50
|
+
when lateral?
|
51
|
+
# Find the name of the element as if it was at the
|
52
|
+
# same nesting level as our parent.
|
53
|
+
@parent.full_name(name)
|
54
|
+
else
|
55
|
+
# We must be the root scope, so no prefix needed.
|
56
|
+
name.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parent_index
|
61
|
+
"[#{@parent.index}]" if @parent.present? && @parent.index.present?
|
30
62
|
end
|
31
63
|
|
64
|
+
# @return [Boolean] whether or not this scope is the root-level scope
|
32
65
|
def root?
|
33
66
|
!@parent
|
34
67
|
end
|
35
68
|
|
69
|
+
# A nested scope is contained in one of its parent's elements.
|
70
|
+
# @return [Boolean] whether or not this scope is nested
|
71
|
+
def nested?
|
72
|
+
@parent && @element
|
73
|
+
end
|
74
|
+
|
75
|
+
# A lateral scope is subordinate to its parent, but its keys are at the
|
76
|
+
# same level as its parent and thus is not contained within an element.
|
77
|
+
# @return [Boolean] whether or not this scope is lateral
|
78
|
+
def lateral?
|
79
|
+
@parent && !@element
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] whether or not this scope needs to be present, or can
|
83
|
+
# be blank
|
36
84
|
def required?
|
37
85
|
!@optional
|
38
86
|
end
|
39
87
|
|
40
88
|
protected
|
41
89
|
|
90
|
+
# Adds a parameter declaration to our list of validations.
|
91
|
+
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
|
42
92
|
def push_declared_params(attrs)
|
43
93
|
@declared_params.concat attrs
|
44
94
|
end
|
@@ -79,21 +129,46 @@ module Grape
|
|
79
129
|
validates(attrs, validations)
|
80
130
|
end
|
81
131
|
|
132
|
+
# Returns a new parameter scope, subordinate to the current one and nested
|
133
|
+
# under the parameter corresponding to `attrs.first`.
|
134
|
+
# @param attrs [Array] the attributes passed to the `requires` or
|
135
|
+
# `optional` invocation that opened this scope.
|
136
|
+
# @param optional [Boolean] whether the parameter this are nested under
|
137
|
+
# is optional or not (and hence, whether this block's params will be).
|
138
|
+
# @yield parameter scope
|
82
139
|
def new_scope(attrs, optional = false, &block)
|
83
140
|
# if required params are grouped and no type or unsupported type is provided, raise an error
|
84
141
|
type = attrs[1] ? attrs[1][:type] : nil
|
85
142
|
if attrs.first && !optional
|
86
143
|
fail Grape::Exceptions::MissingGroupTypeError.new if type.nil?
|
87
|
-
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash].include?(type)
|
144
|
+
fail Grape::Exceptions::UnsupportedGroupTypeError.new unless [Array, Hash, JSON, Array[JSON]].include?(type)
|
88
145
|
end
|
89
146
|
|
90
147
|
opts = attrs[1] || { type: Array }
|
91
|
-
|
148
|
+
self.class.new(api: @api, element: attrs.first, parent: self, optional: optional, type: opts[:type], &block)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns a new parameter scope, not nested under any current-level param
|
152
|
+
# but instead at the same level as the current scope.
|
153
|
+
# @param options [Hash] options to control how this new scope behaves
|
154
|
+
# @option options :dependent_on [Symbol] if given, specifies that this
|
155
|
+
# scope should only validate if this parameter from the above scope is
|
156
|
+
# present
|
157
|
+
# @yield parameter scope
|
158
|
+
def new_lateral_scope(options, &block)
|
159
|
+
self.class.new(
|
160
|
+
api: @api,
|
161
|
+
element: nil,
|
162
|
+
parent: self,
|
163
|
+
options: @optional,
|
164
|
+
type: Hash,
|
165
|
+
dependent_on: options[:dependent_on],
|
166
|
+
&block)
|
92
167
|
end
|
93
168
|
|
94
169
|
# Pushes declared params to parent or settings
|
95
170
|
def configure_declared_params
|
96
|
-
if
|
171
|
+
if nested?
|
97
172
|
@parent.push_declared_params [element => @declared_params]
|
98
173
|
else
|
99
174
|
@api.namespace_stackable(:declared_params, @declared_params)
|
@@ -106,10 +181,7 @@ module Grape
|
|
106
181
|
def validates(attrs, validations)
|
107
182
|
doc_attrs = { required: validations.keys.include?(:presence) }
|
108
183
|
|
109
|
-
|
110
|
-
validations[:coerce] = validations.delete(:type) if validations.key?(:type)
|
111
|
-
|
112
|
-
coerce_type = validations[:coerce]
|
184
|
+
coerce_type = infer_coercion(validations)
|
113
185
|
|
114
186
|
doc_attrs[:type] = coerce_type.to_s if coerce_type
|
115
187
|
|
@@ -141,19 +213,87 @@ module Grape
|
|
141
213
|
validations.delete(:presence)
|
142
214
|
end
|
143
215
|
|
144
|
-
# Before we run the rest of the validators,
|
216
|
+
# Before we run the rest of the validators, let's handle
|
145
217
|
# whatever coercion so that we are working with correctly
|
146
218
|
# type casted values
|
147
|
-
|
148
|
-
validate('coerce', validations[:coerce], attrs, doc_attrs)
|
149
|
-
validations.delete(:coerce)
|
150
|
-
end
|
219
|
+
coerce_type validations, attrs, doc_attrs
|
151
220
|
|
152
221
|
validations.each do |type, options|
|
153
222
|
validate(type, options, attrs, doc_attrs)
|
154
223
|
end
|
155
224
|
end
|
156
225
|
|
226
|
+
# Validate and comprehend the +:type+, +:types+, and +:coerce_with+
|
227
|
+
# options that have been supplied to the parameter declaration.
|
228
|
+
# The +:type+ and +:types+ options will be removed from the
|
229
|
+
# validations list, replaced appropriately with +:coerce+ and
|
230
|
+
# +:coerce_with+ options that will later be passed to
|
231
|
+
# {Validators::CoerceValidator}. The type that is returned may be
|
232
|
+
# used for documentation and further validation of parameter
|
233
|
+
# options.
|
234
|
+
#
|
235
|
+
# @param validations [Hash] list of validations supplied to the
|
236
|
+
# parameter declaration
|
237
|
+
# @return [class-like] type to which the parameter will be coerced
|
238
|
+
# @raise [ArgumentError] if the given type options are invalid
|
239
|
+
def infer_coercion(validations)
|
240
|
+
if validations.key?(:type) && validations.key?(:types)
|
241
|
+
fail ArgumentError, ':type may not be supplied with :types'
|
242
|
+
end
|
243
|
+
|
244
|
+
validations[:coerce] = validations[:type] if validations.key?(:type)
|
245
|
+
validations[:coerce] = validations.delete(:types) if validations.key?(:types)
|
246
|
+
|
247
|
+
coerce_type = validations[:coerce]
|
248
|
+
|
249
|
+
# Special case - when the argument is a single type that is a
|
250
|
+
# variant-type collection.
|
251
|
+
if Types.multiple?(coerce_type) && validations.key?(:type)
|
252
|
+
validations[:coerce] = Types::VariantCollectionCoercer.new(
|
253
|
+
coerce_type,
|
254
|
+
validations.delete(:coerce_with)
|
255
|
+
)
|
256
|
+
end
|
257
|
+
validations.delete(:type)
|
258
|
+
|
259
|
+
coerce_type
|
260
|
+
end
|
261
|
+
|
262
|
+
# Enforce correct usage of :coerce_with parameter.
|
263
|
+
# We do not allow coercion without a type, nor with
|
264
|
+
# +JSON+ as a type since this defines its own coercion
|
265
|
+
# method.
|
266
|
+
def check_coerce_with(validations)
|
267
|
+
return unless validations.key?(:coerce_with)
|
268
|
+
# type must be supplied for coerce_with..
|
269
|
+
fail ArgumentError, 'must supply type for coerce_with' unless validations.key?(:coerce)
|
270
|
+
|
271
|
+
# but not special JSON types, which
|
272
|
+
# already imply coercion method
|
273
|
+
return unless [JSON, Array[JSON]].include? validations[:coerce]
|
274
|
+
fail ArgumentError, 'coerce_with disallowed for type: JSON'
|
275
|
+
end
|
276
|
+
|
277
|
+
# Add type coercion validation to this scope,
|
278
|
+
# if any has been specified.
|
279
|
+
# This validation has special handling since it is
|
280
|
+
# composited from more than one +requires+/+optional+
|
281
|
+
# parameter, and needs to be run before most other
|
282
|
+
# validations.
|
283
|
+
def coerce_type(validations, attrs, doc_attrs)
|
284
|
+
check_coerce_with(validations)
|
285
|
+
|
286
|
+
return unless validations.key?(:coerce)
|
287
|
+
|
288
|
+
coerce_options = {
|
289
|
+
type: validations[:coerce],
|
290
|
+
method: validations[:coerce_with]
|
291
|
+
}
|
292
|
+
validate('coerce', coerce_options, attrs, doc_attrs)
|
293
|
+
validations.delete(:coerce_with)
|
294
|
+
validations.delete(:coerce)
|
295
|
+
end
|
296
|
+
|
157
297
|
def guess_coerce_type(coerce_type, values)
|
158
298
|
return coerce_type if !values || values.is_a?(Proc)
|
159
299
|
return values.first.class if coerce_type == Array && (values.is_a?(Range) || !values.empty?)
|
@@ -183,9 +323,11 @@ module Grape
|
|
183
323
|
return if values.is_a?(Proc)
|
184
324
|
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
|
185
325
|
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
186
|
-
if
|
187
|
-
|
326
|
+
if coerce_type == Virtus::Attribute::Boolean
|
327
|
+
value_types = value_types.map { |type| Virtus::Attribute.build(type) }
|
188
328
|
end
|
329
|
+
return unless value_types.any? { |v| !v.is_a?(coerce_type) }
|
330
|
+
fail Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
189
331
|
end
|
190
332
|
end
|
191
333
|
end
|
@@ -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
|