grape 1.5.0 → 1.6.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 +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
|