grape 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -2
- data/README.md +43 -10
- data/UPGRADING.md +91 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +7 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +7 -6
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +21 -36
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/base.rb +4 -2
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +10 -16
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +3 -0
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +92 -58
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +1 -0
- data/lib/grape/validations/validators/as.rb +4 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
- data/lib/grape/validations/validators/base.rb +12 -7
- data/lib/grape/validations/validators/coerce.rb +8 -9
- data/lib/grape/validations/validators/default.rb +1 -0
- data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
- data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
- data/lib/grape/validations/validators/presence.rb +1 -0
- data/lib/grape/validations/validators/regexp.rb +1 -0
- data/lib/grape/validations/validators/same_as.rb +1 -0
- data/lib/grape/validations/validators/values.rb +3 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +3 -1
- data/spec/grape/api/custom_validations_spec.rb +1 -0
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api_remount_spec.rb +9 -4
- data/spec/grape/api_spec.rb +203 -37
- data/spec/grape/dsl/callbacks_spec.rb +1 -1
- data/spec/grape/dsl/middleware_spec.rb +1 -1
- data/spec/grape/dsl/parameters_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/endpoint/declared_spec.rb +259 -1
- data/spec/grape/endpoint_spec.rb +18 -5
- data/spec/grape/entity_spec.rb +10 -10
- data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
- data/spec/grape/middleware/error_spec.rb +1 -2
- data/spec/grape/middleware/formatter_spec.rb +2 -2
- data/spec/grape/middleware/stack_spec.rb +4 -3
- data/spec/grape/request_spec.rb +1 -1
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +129 -22
- data/spec/grape/validations/validators/except_values_spec.rb +2 -2
- data/spec/grape/validations/validators/values_spec.rb +15 -11
- data/spec/grape/validations_spec.rb +280 -0
- data/spec/shared/versioning_examples.rb +22 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +8 -6
@@ -13,6 +13,8 @@ module Grape
|
|
13
13
|
# @param opts [Hash] options for this scope
|
14
14
|
# @option opts :element [Symbol] the element that contains this scope; for
|
15
15
|
# this to be relevant, @parent must be set
|
16
|
+
# @option opts :element_renamed [Symbol, nil] whenever this scope should
|
17
|
+
# be renamed and to what, given +nil+ no renaming is done
|
16
18
|
# @option opts :parent [ParamsScope] the scope containing this scope
|
17
19
|
# @option opts :api [API] the API endpoint to modify
|
18
20
|
# @option opts :optional [Boolean] whether or not this scope needs to have
|
@@ -23,23 +25,24 @@ module Grape
|
|
23
25
|
# validate if this param is present in the parent scope
|
24
26
|
# @yield the instance context, open for parameter definitions
|
25
27
|
def initialize(opts, &block)
|
26
|
-
@element
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@
|
28
|
+
@element = opts[:element]
|
29
|
+
@element_renamed = opts[:element_renamed]
|
30
|
+
@parent = opts[:parent]
|
31
|
+
@api = opts[:api]
|
32
|
+
@optional = opts[:optional] || false
|
33
|
+
@type = opts[:type]
|
34
|
+
@group = opts[:group] || {}
|
35
|
+
@dependent_on = opts[:dependent_on]
|
33
36
|
@declared_params = []
|
34
37
|
@index = nil
|
35
38
|
|
36
|
-
instance_eval(&block) if
|
39
|
+
instance_eval(&block) if block
|
37
40
|
|
38
41
|
configure_declared_params
|
39
42
|
end
|
40
43
|
|
41
44
|
def configuration
|
42
|
-
@api.configuration.evaluate
|
45
|
+
@api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
|
43
46
|
end
|
44
47
|
|
45
48
|
# @return [Boolean] whether or not this entire scope needs to be
|
@@ -50,19 +53,19 @@ module Grape
|
|
50
53
|
return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
|
51
54
|
return false unless meets_dependency?(scoped_params, parameters)
|
52
55
|
return true if parent.nil?
|
56
|
+
|
53
57
|
parent.should_validate?(parameters)
|
54
58
|
end
|
55
59
|
|
56
60
|
def meets_dependency?(params, request_params)
|
57
61
|
return true unless @dependent_on
|
58
62
|
|
59
|
-
if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
60
|
-
return false
|
61
|
-
end
|
63
|
+
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
62
64
|
|
63
65
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
64
|
-
|
65
|
-
params
|
66
|
+
|
67
|
+
# params might be anything what looks like a hash, so it must implement a `key?` method
|
68
|
+
return false unless params.respond_to?(:key?)
|
66
69
|
|
67
70
|
@dependent_on.each do |dependency|
|
68
71
|
if dependency.is_a?(Hash)
|
@@ -128,18 +131,35 @@ module Grape
|
|
128
131
|
if lateral?
|
129
132
|
@parent.push_declared_params(attrs, **opts)
|
130
133
|
else
|
131
|
-
|
132
|
-
|
133
|
-
@api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
|
134
|
-
attrs = [opts[:as]]
|
135
|
-
end
|
134
|
+
push_renamed_param(full_path + [attrs.first], opts[:as]) \
|
135
|
+
if opts && opts[:as]
|
136
136
|
|
137
137
|
@declared_params.concat attrs
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
+
# Get the full path of the parameter scope in the hierarchy.
|
142
|
+
#
|
143
|
+
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
144
|
+
def full_path
|
145
|
+
nested? ? @parent.full_path + [@element] : []
|
146
|
+
end
|
147
|
+
|
141
148
|
private
|
142
149
|
|
150
|
+
# Add a new parameter which should be renamed when using the +#declared+
|
151
|
+
# method.
|
152
|
+
#
|
153
|
+
# @param path [Array<String, Symbol>] the full path of the parameter
|
154
|
+
# (including the parameter name as last array element)
|
155
|
+
# @param new_name [String, Symbol] the new name of the parameter (the
|
156
|
+
# renamed name, with the +as: ...+ semantic)
|
157
|
+
def push_renamed_param(path, new_name)
|
158
|
+
base = @api.route_setting(:renamed_params) || {}
|
159
|
+
base[Array(path).map(&:to_s)] = new_name.to_s
|
160
|
+
@api.route_setting(:renamed_params, base)
|
161
|
+
end
|
162
|
+
|
143
163
|
def require_required_and_optional_fields(context, opts)
|
144
164
|
if context == :all
|
145
165
|
optional_fields = Array(opts[:except])
|
@@ -151,6 +171,7 @@ module Grape
|
|
151
171
|
required_fields.each do |field|
|
152
172
|
field_opts = opts[:using][field]
|
153
173
|
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
174
|
+
|
154
175
|
requires(field, field_opts)
|
155
176
|
end
|
156
177
|
optional_fields.each do |field|
|
@@ -190,11 +211,12 @@ module Grape
|
|
190
211
|
end
|
191
212
|
|
192
213
|
self.class.new(
|
193
|
-
api:
|
194
|
-
element:
|
195
|
-
|
214
|
+
api: @api,
|
215
|
+
element: attrs.first,
|
216
|
+
element_renamed: attrs[1][:as],
|
217
|
+
parent: self,
|
196
218
|
optional: optional,
|
197
|
-
type:
|
219
|
+
type: type || Array,
|
198
220
|
&block
|
199
221
|
)
|
200
222
|
end
|
@@ -208,11 +230,11 @@ module Grape
|
|
208
230
|
# @yield parameter scope
|
209
231
|
def new_lateral_scope(options, &block)
|
210
232
|
self.class.new(
|
211
|
-
api:
|
212
|
-
element:
|
213
|
-
parent:
|
214
|
-
options:
|
215
|
-
type:
|
233
|
+
api: @api,
|
234
|
+
element: nil,
|
235
|
+
parent: self,
|
236
|
+
options: @optional,
|
237
|
+
type: type == Array ? Array : Hash,
|
216
238
|
dependent_on: options[:dependent_on],
|
217
239
|
&block
|
218
240
|
)
|
@@ -225,15 +247,17 @@ module Grape
|
|
225
247
|
# @yield parameter scope
|
226
248
|
def new_group_scope(attrs, &block)
|
227
249
|
self.class.new(
|
228
|
-
api:
|
229
|
-
parent:
|
230
|
-
group:
|
250
|
+
api: @api,
|
251
|
+
parent: self,
|
252
|
+
group: attrs.first,
|
231
253
|
&block
|
232
254
|
)
|
233
255
|
end
|
234
256
|
|
235
257
|
# Pushes declared params to parent or settings
|
236
258
|
def configure_declared_params
|
259
|
+
push_renamed_param(full_path, @element_renamed) if @element_renamed
|
260
|
+
|
237
261
|
if nested?
|
238
262
|
@parent.push_declared_params [element => @declared_params]
|
239
263
|
else
|
@@ -282,16 +306,15 @@ module Grape
|
|
282
306
|
|
283
307
|
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
|
284
308
|
|
285
|
-
|
286
|
-
@api.document_attribute(full_attrs, doc_attrs)
|
309
|
+
document_attribute(attrs, doc_attrs)
|
287
310
|
|
288
311
|
opts = derive_validator_options(validations)
|
289
312
|
|
313
|
+
order_specific_validations = Set[:as]
|
314
|
+
|
290
315
|
# Validate for presence before any other validators
|
291
|
-
|
292
|
-
|
293
|
-
validations.delete(:presence)
|
294
|
-
validations.delete(:message) if validations.key?(:message)
|
316
|
+
validates_presence(validations, attrs, doc_attrs, opts) do |validation_type|
|
317
|
+
order_specific_validations << validation_type
|
295
318
|
end
|
296
319
|
|
297
320
|
# Before we run the rest of the validators, let's handle
|
@@ -300,6 +323,8 @@ module Grape
|
|
300
323
|
coerce_type validations, attrs, doc_attrs, opts
|
301
324
|
|
302
325
|
validations.each do |type, options|
|
326
|
+
next if order_specific_validations.include?(type)
|
327
|
+
|
303
328
|
validate(type, options, attrs, doc_attrs, opts)
|
304
329
|
end
|
305
330
|
end
|
@@ -318,9 +343,7 @@ module Grape
|
|
318
343
|
# @return [class-like] type to which the parameter will be coerced
|
319
344
|
# @raise [ArgumentError] if the given type options are invalid
|
320
345
|
def infer_coercion(validations)
|
321
|
-
if validations.key?(:type) && validations.key?(:types)
|
322
|
-
raise ArgumentError, ':type may not be supplied with :types'
|
323
|
-
end
|
346
|
+
raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
|
324
347
|
|
325
348
|
validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
|
326
349
|
validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
|
@@ -356,6 +379,7 @@ module Grape
|
|
356
379
|
# but not special JSON types, which
|
357
380
|
# already imply coercion method
|
358
381
|
return unless [JSON, Array[JSON]].include? validations[:coerce]
|
382
|
+
|
359
383
|
raise ArgumentError, 'coerce_with disallowed for type: JSON'
|
360
384
|
end
|
361
385
|
|
@@ -383,6 +407,7 @@ module Grape
|
|
383
407
|
|
384
408
|
def guess_coerce_type(coerce_type, *values_list)
|
385
409
|
return coerce_type unless coerce_type == Array
|
410
|
+
|
386
411
|
values_list.each do |values|
|
387
412
|
next if !values || values.is_a?(Proc)
|
388
413
|
return values.first.class if values.is_a?(Range) || !values.empty?
|
@@ -393,14 +418,11 @@ module Grape
|
|
393
418
|
def check_incompatible_option_values(default, values, except_values, excepts)
|
394
419
|
return unless default && !default.is_a?(Proc)
|
395
420
|
|
396
|
-
if values && !values.is_a?(Proc)
|
397
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
|
398
|
-
unless Array(default).all? { |def_val| values.include?(def_val) }
|
399
|
-
end
|
421
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
|
400
422
|
|
401
|
-
if except_values && !except_values.is_a?(Proc)
|
423
|
+
if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
402
424
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
|
403
|
-
|
425
|
+
|
404
426
|
end
|
405
427
|
|
406
428
|
return unless excepts && !excepts.is_a?(Proc)
|
@@ -414,11 +436,11 @@ module Grape
|
|
414
436
|
raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
|
415
437
|
|
416
438
|
validator_options = {
|
417
|
-
attributes:
|
418
|
-
options:
|
419
|
-
required:
|
420
|
-
params_scope:
|
421
|
-
opts:
|
439
|
+
attributes: attrs,
|
440
|
+
options: options,
|
441
|
+
required: doc_attrs[:required],
|
442
|
+
params_scope: self,
|
443
|
+
opts: opts,
|
422
444
|
validator_class: validator_class
|
423
445
|
}
|
424
446
|
@api.namespace_stackable(:validations, validator_options)
|
@@ -426,21 +448,20 @@ module Grape
|
|
426
448
|
|
427
449
|
def validate_value_coercion(coerce_type, *values_list)
|
428
450
|
return unless coerce_type
|
451
|
+
|
429
452
|
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
|
430
453
|
values_list.each do |values|
|
431
454
|
next if !values || values.is_a?(Proc)
|
455
|
+
|
432
456
|
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
433
|
-
if coerce_type == Grape::API::Boolean
|
434
|
-
|
435
|
-
end
|
436
|
-
unless value_types.all? { |v| v.is_a? coerce_type }
|
437
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
438
|
-
end
|
457
|
+
value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
|
458
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
|
439
459
|
end
|
440
460
|
end
|
441
461
|
|
442
462
|
def extract_message_option(attrs)
|
443
463
|
return nil unless attrs.is_a?(Array)
|
464
|
+
|
444
465
|
opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
|
445
466
|
opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
|
446
467
|
end
|
@@ -460,9 +481,22 @@ module Grape
|
|
460
481
|
|
461
482
|
{
|
462
483
|
allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
|
463
|
-
fail_fast:
|
484
|
+
fail_fast: validations.delete(:fail_fast) || false
|
464
485
|
}
|
465
486
|
end
|
487
|
+
|
488
|
+
def validates_presence(validations, attrs, doc_attrs, opts)
|
489
|
+
return unless validations.key?(:presence) && validations[:presence]
|
490
|
+
|
491
|
+
validate(:presence, validations[:presence], attrs, doc_attrs, opts)
|
492
|
+
yield :presence
|
493
|
+
yield :message if validations.key?(:message)
|
494
|
+
end
|
495
|
+
|
496
|
+
def document_attribute(attrs, doc_attrs)
|
497
|
+
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
498
|
+
@api.document_attribute(full_attrs, doc_attrs)
|
499
|
+
end
|
466
500
|
end
|
467
501
|
end
|
468
502
|
end
|
@@ -52,10 +52,11 @@ module Grape
|
|
52
52
|
# this should always be a string.
|
53
53
|
# @return [Object] the coerced result
|
54
54
|
def call(val)
|
55
|
-
return if val.nil?
|
56
|
-
|
57
55
|
coerced_val = @method.call(val)
|
56
|
+
|
57
|
+
return coerced_val if coerced_val.is_a?(InvalidValue)
|
58
58
|
return InvalidValue.new unless coerced?(coerced_val)
|
59
|
+
|
59
60
|
coerced_val
|
60
61
|
end
|
61
62
|
|
@@ -35,7 +35,7 @@ module Grape
|
|
35
35
|
|
36
36
|
# Returns an instance of a coercer for a given type
|
37
37
|
def coercer_instance_for(type, strict = false)
|
38
|
-
return PrimitiveCoercer.new(type, strict) if type.
|
38
|
+
return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
|
39
39
|
|
40
40
|
# in case of a collection (Array[Integer]) the type is an instance of a collection,
|
41
41
|
# so we need to figure out the actual type
|
@@ -0,0 +1,24 @@
|
|
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
|
18
|
+
|
19
|
+
# only exists to make it shorter for external use
|
20
|
+
module Grape
|
21
|
+
module Types
|
22
|
+
InvalidValue = Class.new(Grape::Validations::Types::InvalidValue)
|
23
|
+
end
|
24
|
+
end
|
@@ -22,6 +22,7 @@ module Grape
|
|
22
22
|
|
23
23
|
# Allow nulls and blank strings
|
24
24
|
return if input.nil? || input.match?(/^\s*$/)
|
25
|
+
|
25
26
|
JSON.parse(input, symbolize_names: true)
|
26
27
|
end
|
27
28
|
|
@@ -41,7 +42,7 @@ module Grape
|
|
41
42
|
# @param value [Object] result of {#parse}
|
42
43
|
# @return [true,false]
|
43
44
|
def coerced_collection?(value)
|
44
|
-
value.is_a?(::Array) && value.all?
|
45
|
+
value.is_a?(::Array) && value.all?(::Hash)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
end
|
@@ -11,15 +11,15 @@ module Grape
|
|
11
11
|
class PrimitiveCoercer < DryTypeCoercer
|
12
12
|
MAPPING = {
|
13
13
|
Grape::API::Boolean => DryTypes::Params::Bool,
|
14
|
-
BigDecimal
|
14
|
+
BigDecimal => DryTypes::Params::Decimal,
|
15
15
|
|
16
16
|
# unfortunately, a +Params+ scope doesn't contain String
|
17
|
-
String
|
17
|
+
String => DryTypes::Coercible::String
|
18
18
|
}.freeze
|
19
19
|
|
20
20
|
STRICT_MAPPING = {
|
21
21
|
Grape::API::Boolean => DryTypes::Strict::Bool,
|
22
|
-
BigDecimal
|
22
|
+
BigDecimal => DryTypes::Strict::Decimal
|
23
23
|
}.freeze
|
24
24
|
|
25
25
|
def initialize(type, strict = false)
|
@@ -7,6 +7,7 @@ require_relative 'types/multiple_type_coercer'
|
|
7
7
|
require_relative 'types/variant_collection_coercer'
|
8
8
|
require_relative 'types/json'
|
9
9
|
require_relative 'types/file'
|
10
|
+
require_relative 'types/invalid_value'
|
10
11
|
|
11
12
|
module Grape
|
12
13
|
module Validations
|
@@ -21,10 +22,6 @@ module Grape
|
|
21
22
|
# and {Grape::Dsl::Parameters#optional}. The main
|
22
23
|
# entry point for this process is {Types.build_coercer}.
|
23
24
|
module Types
|
24
|
-
# Instances of this class may be used as tokens to denote that
|
25
|
-
# a parameter value could not be coerced.
|
26
|
-
class InvalidValue; end
|
27
|
-
|
28
25
|
# Types representing a single value, which are coerced.
|
29
26
|
PRIMITIVES = [
|
30
27
|
# Numerical
|
@@ -3,14 +3,10 @@
|
|
3
3
|
module Grape
|
4
4
|
module Validations
|
5
5
|
class AsValidator < Base
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def validate_param!(attr_name, params)
|
12
|
-
params[@renamed_options] = params[attr_name]
|
13
|
-
end
|
6
|
+
# We use a validator for renaming parameters. This is just a marker for
|
7
|
+
# the parameter scope to handle the renaming. No actual validation
|
8
|
+
# happens here.
|
9
|
+
def validate_param!(*); end
|
14
10
|
end
|
15
11
|
end
|
16
12
|
end
|
@@ -12,14 +12,15 @@ module Grape
|
|
12
12
|
# @param options [Object] implementation-dependent Validator options
|
13
13
|
# @param required [Boolean] attribute(s) are required or optional
|
14
14
|
# @param scope [ParamsScope] parent scope for this Validator
|
15
|
-
# @param opts [
|
16
|
-
def initialize(attrs, options, required, scope,
|
15
|
+
# @param opts [Array] additional validation options
|
16
|
+
def initialize(attrs, options, required, scope, *opts)
|
17
17
|
@attrs = Array(attrs)
|
18
18
|
@option = options
|
19
19
|
@required = required
|
20
20
|
@scope = scope
|
21
|
-
|
22
|
-
@
|
21
|
+
opts = opts.any? ? opts.shift : {}
|
22
|
+
@fail_fast = opts.fetch(:fail_fast, false)
|
23
|
+
@allow_blank = opts.fetch(:allow_blank, false)
|
23
24
|
end
|
24
25
|
|
25
26
|
# Validates a given request.
|
@@ -29,6 +30,7 @@ module Grape
|
|
29
30
|
# @return [void]
|
30
31
|
def validate(request)
|
31
32
|
return unless @scope.should_validate?(request.params)
|
33
|
+
|
32
34
|
validate!(request.params)
|
33
35
|
end
|
34
36
|
|
@@ -43,17 +45,19 @@ module Grape
|
|
43
45
|
# there may be more than one error per field
|
44
46
|
array_errors = []
|
45
47
|
|
46
|
-
attributes.each do |val, attr_name, empty_val|
|
48
|
+
attributes.each do |val, attr_name, empty_val, skip_value|
|
49
|
+
next if skip_value
|
47
50
|
next if !@scope.required? && empty_val
|
48
51
|
next unless @scope.meets_dependency?(val, params)
|
52
|
+
|
49
53
|
begin
|
50
|
-
validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name)
|
54
|
+
validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))
|
51
55
|
rescue Grape::Exceptions::Validation => e
|
52
56
|
array_errors << e
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
56
|
-
raise Grape::Exceptions::ValidationArrayErrors
|
60
|
+
raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
|
57
61
|
end
|
58
62
|
|
59
63
|
def self.convert_to_short_name(klass)
|
@@ -67,6 +71,7 @@ module Grape
|
|
67
71
|
|
68
72
|
def self.inherited(klass)
|
69
73
|
return unless klass.name.present?
|
74
|
+
|
70
75
|
Validations.register_validator(convert_to_short_name(klass), klass)
|
71
76
|
end
|
72
77
|
|
@@ -17,7 +17,7 @@ module Grape
|
|
17
17
|
|
18
18
|
module Validations
|
19
19
|
class CoerceValidator < Base
|
20
|
-
def initialize(
|
20
|
+
def initialize(attrs, options, required, scope, **opts)
|
21
21
|
super
|
22
22
|
|
23
23
|
@converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
|
@@ -27,16 +27,12 @@ module Grape
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def validate(request)
|
31
|
-
super
|
32
|
-
end
|
33
|
-
|
34
30
|
def validate_param!(attr_name, params)
|
35
31
|
raise validation_exception(attr_name) unless params.is_a? Hash
|
36
32
|
|
37
33
|
new_value = coerce_value(params[attr_name])
|
38
34
|
|
39
|
-
raise validation_exception(attr_name) unless valid_type?(new_value)
|
35
|
+
raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)
|
40
36
|
|
41
37
|
# Don't assign a value if it is identical. It fixes a problem with Hashie::Mash
|
42
38
|
# which looses wrappers for hashes and arrays after reassigning values
|
@@ -47,7 +43,7 @@ module Grape
|
|
47
43
|
# h[:list] = list
|
48
44
|
# h
|
49
45
|
# => #<Hashie::Mash list=[1, 2, 3, 4]>
|
50
|
-
return if params[attr_name].
|
46
|
+
return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value
|
51
47
|
|
52
48
|
params[attr_name] = new_value
|
53
49
|
end
|
@@ -80,8 +76,11 @@ module Grape
|
|
80
76
|
@option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]
|
81
77
|
end
|
82
78
|
|
83
|
-
def validation_exception(attr_name)
|
84
|
-
Grape::Exceptions::Validation.new(
|
79
|
+
def validation_exception(attr_name, custom_msg = nil)
|
80
|
+
Grape::Exceptions::Validation.new(
|
81
|
+
params: [@scope.full_name(attr_name)],
|
82
|
+
message: custom_msg || message(:coerce)
|
83
|
+
)
|
85
84
|
end
|
86
85
|
end
|
87
86
|
end
|
@@ -22,6 +22,7 @@ module Grape
|
|
22
22
|
attrs = SingleAttributeIterator.new(self, @scope, params)
|
23
23
|
attrs.each do |resource_params, attr_name|
|
24
24
|
next unless @scope.meets_dependency?(resource_params, params)
|
25
|
+
|
25
26
|
validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?
|
26
27
|
end
|
27
28
|
end
|
@@ -9,6 +9,7 @@ module Grape
|
|
9
9
|
keys = keys_in_common(params)
|
10
10
|
return if keys.length == 1
|
11
11
|
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero?
|
12
|
+
|
12
13
|
raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
|
13
14
|
end
|
14
15
|
end
|
@@ -7,7 +7,9 @@ module Grape
|
|
7
7
|
attributes = MultipleAttributesIterator.new(self, @scope, params)
|
8
8
|
array_errors = []
|
9
9
|
|
10
|
-
attributes.each do |resource_params|
|
10
|
+
attributes.each do |resource_params, skip_value|
|
11
|
+
next if skip_value
|
12
|
+
|
11
13
|
begin
|
12
14
|
validate_params!(resource_params)
|
13
15
|
rescue Grape::Exceptions::Validation => e
|
@@ -15,13 +17,14 @@ module Grape
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
raise Grape::Exceptions::ValidationArrayErrors
|
20
|
+
raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
25
|
def keys_in_common(resource_params)
|
24
26
|
return [] unless resource_params.is_a?(Hash)
|
27
|
+
|
25
28
|
all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) }
|
26
29
|
end
|
27
30
|
|
@@ -5,6 +5,7 @@ module Grape
|
|
5
5
|
class PresenceValidator < Base
|
6
6
|
def validate_param!(attr_name, params)
|
7
7
|
return if params.respond_to?(:key?) && params.key?(attr_name)
|
8
|
+
|
8
9
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))
|
9
10
|
end
|
10
11
|
end
|
@@ -6,6 +6,7 @@ module Grape
|
|
6
6
|
def validate_param!(attr_name, params)
|
7
7
|
return unless params.respond_to?(:key?) && params.key?(attr_name)
|
8
8
|
return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
|
9
|
+
|
9
10
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
|
10
11
|
end
|
11
12
|
end
|
@@ -6,6 +6,7 @@ module Grape
|
|
6
6
|
def validate_param!(attr_name, params)
|
7
7
|
confirmation = options_key?(:value) ? @option[:value] : @option
|
8
8
|
return if params[attr_name] == params[confirmation]
|
9
|
+
|
9
10
|
raise Grape::Exceptions::Validation.new(
|
10
11
|
params: [@scope.full_name(attr_name)],
|
11
12
|
message: build_message
|