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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -2
  3. data/README.md +43 -10
  4. data/UPGRADING.md +91 -0
  5. data/grape.gemspec +5 -5
  6. data/lib/grape/api/instance.rb +13 -17
  7. data/lib/grape/api.rb +7 -14
  8. data/lib/grape/cookies.rb +2 -0
  9. data/lib/grape/dsl/callbacks.rb +1 -1
  10. data/lib/grape/dsl/desc.rb +3 -5
  11. data/lib/grape/dsl/helpers.rb +6 -4
  12. data/lib/grape/dsl/inside_route.rb +18 -9
  13. data/lib/grape/dsl/middleware.rb +4 -4
  14. data/lib/grape/dsl/parameters.rb +11 -7
  15. data/lib/grape/dsl/request_response.rb +9 -6
  16. data/lib/grape/dsl/routing.rb +7 -6
  17. data/lib/grape/dsl/settings.rb +5 -5
  18. data/lib/grape/endpoint.rb +21 -36
  19. data/lib/grape/error_formatter/json.rb +2 -6
  20. data/lib/grape/error_formatter/xml.rb +2 -6
  21. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  22. data/lib/grape/exceptions/validation.rb +2 -3
  23. data/lib/grape/exceptions/validation_errors.rb +1 -1
  24. data/lib/grape/formatter/json.rb +1 -0
  25. data/lib/grape/formatter/serializable_hash.rb +2 -1
  26. data/lib/grape/formatter/xml.rb +1 -0
  27. data/lib/grape/locale/en.yml +1 -1
  28. data/lib/grape/middleware/auth/base.rb +3 -3
  29. data/lib/grape/middleware/base.rb +4 -2
  30. data/lib/grape/middleware/error.rb +1 -1
  31. data/lib/grape/middleware/formatter.rb +4 -4
  32. data/lib/grape/middleware/stack.rb +10 -16
  33. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  34. data/lib/grape/middleware/versioner/header.rb +6 -4
  35. data/lib/grape/middleware/versioner/param.rb +1 -0
  36. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  37. data/lib/grape/middleware/versioner/path.rb +2 -0
  38. data/lib/grape/parser/json.rb +1 -1
  39. data/lib/grape/parser/xml.rb +1 -1
  40. data/lib/grape/path.rb +1 -0
  41. data/lib/grape/request.rb +3 -0
  42. data/lib/grape/router/attribute_translator.rb +1 -1
  43. data/lib/grape/router/pattern.rb +1 -1
  44. data/lib/grape/router/route.rb +2 -2
  45. data/lib/grape/router.rb +6 -0
  46. data/lib/grape/util/inheritable_setting.rb +1 -3
  47. data/lib/grape/util/lazy_value.rb +3 -2
  48. data/lib/grape/validations/attributes_iterator.rb +8 -0
  49. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  50. data/lib/grape/validations/params_scope.rb +92 -58
  51. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  52. data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
  53. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  54. data/lib/grape/validations/types/invalid_value.rb +24 -0
  55. data/lib/grape/validations/types/json.rb +2 -1
  56. data/lib/grape/validations/types/primitive_coercer.rb +3 -3
  57. data/lib/grape/validations/types.rb +1 -4
  58. data/lib/grape/validations/validator_factory.rb +1 -1
  59. data/lib/grape/validations/validators/all_or_none.rb +1 -0
  60. data/lib/grape/validations/validators/as.rb +4 -8
  61. data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
  62. data/lib/grape/validations/validators/base.rb +12 -7
  63. data/lib/grape/validations/validators/coerce.rb +8 -9
  64. data/lib/grape/validations/validators/default.rb +1 -0
  65. data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
  66. data/lib/grape/validations/validators/multiple_params_base.rb +5 -2
  67. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
  68. data/lib/grape/validations/validators/presence.rb +1 -0
  69. data/lib/grape/validations/validators/regexp.rb +1 -0
  70. data/lib/grape/validations/validators/same_as.rb +1 -0
  71. data/lib/grape/validations/validators/values.rb +3 -0
  72. data/lib/grape/version.rb +1 -1
  73. data/lib/grape.rb +3 -1
  74. data/spec/grape/api/custom_validations_spec.rb +1 -0
  75. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  76. data/spec/grape/api_remount_spec.rb +9 -4
  77. data/spec/grape/api_spec.rb +203 -37
  78. data/spec/grape/dsl/callbacks_spec.rb +1 -1
  79. data/spec/grape/dsl/middleware_spec.rb +1 -1
  80. data/spec/grape/dsl/parameters_spec.rb +1 -0
  81. data/spec/grape/dsl/routing_spec.rb +1 -1
  82. data/spec/grape/endpoint/declared_spec.rb +259 -1
  83. data/spec/grape/endpoint_spec.rb +18 -5
  84. data/spec/grape/entity_spec.rb +10 -10
  85. data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
  86. data/spec/grape/middleware/error_spec.rb +1 -2
  87. data/spec/grape/middleware/formatter_spec.rb +2 -2
  88. data/spec/grape/middleware/stack_spec.rb +4 -3
  89. data/spec/grape/request_spec.rb +1 -1
  90. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +13 -3
  91. data/spec/grape/validations/params_scope_spec.rb +37 -3
  92. data/spec/grape/validations/single_attribute_iterator_spec.rb +17 -6
  93. data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
  94. data/spec/grape/validations/validators/coerce_spec.rb +129 -22
  95. data/spec/grape/validations/validators/except_values_spec.rb +2 -2
  96. data/spec/grape/validations/validators/values_spec.rb +15 -11
  97. data/spec/grape/validations_spec.rb +280 -0
  98. data/spec/shared/versioning_examples.rb +22 -22
  99. data/spec/spec_helper.rb +1 -1
  100. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  101. data/spec/support/versioned_helpers.rb +1 -1
  102. 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 = opts[:element]
27
- @parent = opts[:parent]
28
- @api = opts[:api]
29
- @optional = opts[:optional] || false
30
- @type = opts[:type]
31
- @group = opts[:group] || {}
32
- @dependent_on = opts[:dependent_on]
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 block_given?
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
- return false unless params.respond_to?(:with_indifferent_access)
65
- params = params.with_indifferent_access
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
- if opts && opts[:as]
132
- @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
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: @api,
194
- element: attrs[1][:as] || attrs.first,
195
- parent: self,
214
+ api: @api,
215
+ element: attrs.first,
216
+ element_renamed: attrs[1][:as],
217
+ parent: self,
196
218
  optional: optional,
197
- type: type || Array,
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: @api,
212
- element: nil,
213
- parent: self,
214
- options: @optional,
215
- type: type == Array ? Array : Hash,
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: @api,
229
- parent: self,
230
- group: attrs.first,
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
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
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
- if validations.key?(:presence) && validations[:presence]
292
- validate('presence', validations[:presence], attrs, doc_attrs, opts)
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
- unless Array(default).none? { |def_val| except_values.include?(def_val) }
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: attrs,
418
- options: options,
419
- required: doc_attrs[:required],
420
- params_scope: self,
421
- opts: 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
- value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
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: validations.delete(:fail_fast) || false
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
@@ -7,7 +7,7 @@ module Grape
7
7
 
8
8
  def yield_attributes(val, attrs)
9
9
  attrs.each do |attr_name|
10
- yield val, attr_name, empty?(val)
10
+ yield val, attr_name, empty?(val), skip?(val)
11
11
  end
12
12
  end
13
13
 
@@ -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.class == Class
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? { |i| i.is_a? ::Hash }
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 => DryTypes::Params::Decimal,
14
+ BigDecimal => DryTypes::Params::Decimal,
15
15
 
16
16
  # unfortunately, a +Params+ scope doesn't contain String
17
- String => DryTypes::Coercible::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 => DryTypes::Strict::Decimal
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
@@ -8,7 +8,7 @@ module Grape
8
8
  options[:options],
9
9
  options[:required],
10
10
  options[:params_scope],
11
- options[:opts])
11
+ **options[:opts])
12
12
  end
13
13
  end
14
14
  end
@@ -8,6 +8,7 @@ module Grape
8
8
  def validate_params!(params)
9
9
  keys = keys_in_common(params)
10
10
  return if keys.empty? || keys.length == all_keys.length
11
+
11
12
  raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
12
13
  end
13
14
  end
@@ -3,14 +3,10 @@
3
3
  module Grape
4
4
  module Validations
5
5
  class AsValidator < Base
6
- def initialize(attrs, options, required, scope, **opts)
7
- @renamed_options = options
8
- super
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
@@ -7,6 +7,7 @@ module Grape
7
7
  class AtLeastOneOfValidator < MultipleParamsBase
8
8
  def validate_params!(params)
9
9
  return unless keys_in_common(params).empty?
10
+
10
11
  raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
11
12
  end
12
13
  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 [Hash] additional validation options
16
- def initialize(attrs, options, required, scope, **opts)
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
- @fail_fast = opts[:fail_fast] || false
22
- @allow_blank = opts[:allow_blank] || false
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, array_errors if array_errors.any?
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(*_args)
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].class == new_value.class && params[attr_name] == new_value
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(params: [@scope.full_name(attr_name)], message: message(:coerce))
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, array_errors if array_errors.any?
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
 
@@ -8,6 +8,7 @@ module Grape
8
8
  def validate_params!(params)
9
9
  keys = keys_in_common(params)
10
10
  return if keys.length <= 1
11
+
11
12
  raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))
12
13
  end
13
14
  end
@@ -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