grape 1.5.3 → 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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +23 -3
  4. data/UPGRADING.md +43 -1
  5. data/grape.gemspec +5 -5
  6. data/lib/grape/api/instance.rb +13 -17
  7. data/lib/grape/api.rb +5 -12
  8. data/lib/grape/cookies.rb +2 -0
  9. data/lib/grape/dsl/desc.rb +3 -5
  10. data/lib/grape/dsl/helpers.rb +6 -4
  11. data/lib/grape/dsl/inside_route.rb +17 -8
  12. data/lib/grape/dsl/middleware.rb +4 -4
  13. data/lib/grape/dsl/parameters.rb +3 -3
  14. data/lib/grape/dsl/request_response.rb +9 -6
  15. data/lib/grape/dsl/routing.rb +2 -2
  16. data/lib/grape/dsl/settings.rb +5 -5
  17. data/lib/grape/endpoint.rb +20 -35
  18. data/lib/grape/error_formatter/json.rb +2 -6
  19. data/lib/grape/error_formatter/xml.rb +2 -6
  20. data/lib/grape/exceptions/validation.rb +1 -2
  21. data/lib/grape/formatter/json.rb +1 -0
  22. data/lib/grape/formatter/serializable_hash.rb +2 -1
  23. data/lib/grape/formatter/xml.rb +1 -0
  24. data/lib/grape/middleware/base.rb +2 -0
  25. data/lib/grape/middleware/formatter.rb +4 -4
  26. data/lib/grape/middleware/stack.rb +2 -2
  27. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  28. data/lib/grape/middleware/versioner/header.rb +6 -4
  29. data/lib/grape/middleware/versioner/param.rb +1 -0
  30. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  31. data/lib/grape/middleware/versioner/path.rb +2 -0
  32. data/lib/grape/path.rb +1 -0
  33. data/lib/grape/request.rb +1 -0
  34. data/lib/grape/router/pattern.rb +1 -1
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/router.rb +6 -0
  37. data/lib/grape/util/inheritable_setting.rb +1 -3
  38. data/lib/grape/util/lazy_value.rb +3 -2
  39. data/lib/grape/validations/params_scope.rb +88 -55
  40. data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
  41. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  42. data/lib/grape/validations/types/json.rb +2 -1
  43. data/lib/grape/validations/types/primitive_coercer.rb +3 -3
  44. data/lib/grape/validations/validators/all_or_none.rb +1 -0
  45. data/lib/grape/validations/validators/as.rb +4 -8
  46. data/lib/grape/validations/validators/at_least_one_of.rb +1 -0
  47. data/lib/grape/validations/validators/base.rb +4 -1
  48. data/lib/grape/validations/validators/coerce.rb +1 -5
  49. data/lib/grape/validations/validators/default.rb +1 -0
  50. data/lib/grape/validations/validators/exactly_one_of.rb +1 -0
  51. data/lib/grape/validations/validators/multiple_params_base.rb +2 -0
  52. data/lib/grape/validations/validators/mutual_exclusion.rb +1 -0
  53. data/lib/grape/validations/validators/presence.rb +1 -0
  54. data/lib/grape/validations/validators/regexp.rb +1 -0
  55. data/lib/grape/validations/validators/same_as.rb +1 -0
  56. data/lib/grape/validations/validators/values.rb +3 -0
  57. data/lib/grape/version.rb +1 -1
  58. data/lib/grape.rb +1 -1
  59. data/spec/grape/api/custom_validations_spec.rb +1 -0
  60. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  61. data/spec/grape/api_spec.rb +126 -37
  62. data/spec/grape/dsl/callbacks_spec.rb +1 -1
  63. data/spec/grape/dsl/middleware_spec.rb +1 -1
  64. data/spec/grape/dsl/parameters_spec.rb +1 -0
  65. data/spec/grape/dsl/routing_spec.rb +1 -1
  66. data/spec/grape/endpoint/declared_spec.rb +247 -0
  67. data/spec/grape/endpoint_spec.rb +5 -5
  68. data/spec/grape/entity_spec.rb +9 -9
  69. data/spec/grape/middleware/auth/dsl_spec.rb +1 -1
  70. data/spec/grape/middleware/error_spec.rb +1 -2
  71. data/spec/grape/middleware/formatter_spec.rb +2 -2
  72. data/spec/grape/middleware/stack_spec.rb +3 -1
  73. data/spec/grape/validations/params_scope_spec.rb +37 -3
  74. data/spec/grape/validations/single_attribute_iterator_spec.rb +1 -1
  75. data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
  76. data/spec/grape/validations/validators/coerce_spec.rb +13 -10
  77. data/spec/grape/validations/validators/except_values_spec.rb +2 -2
  78. data/spec/grape/validations/validators/values_spec.rb +15 -11
  79. data/spec/grape/validations_spec.rb +54 -42
  80. data/spec/shared/versioning_examples.rb +2 -2
  81. data/spec/spec_helper.rb +1 -1
  82. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  83. metadata +6 -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,17 +25,18 @@ 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
@@ -50,15 +53,14 @@ 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
66
 
@@ -129,18 +131,35 @@ module Grape
129
131
  if lateral?
130
132
  @parent.push_declared_params(attrs, **opts)
131
133
  else
132
- if opts && opts[:as]
133
- @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || [])
134
- @api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
135
- attrs = [opts[:as]]
136
- end
134
+ push_renamed_param(full_path + [attrs.first], opts[:as]) \
135
+ if opts && opts[:as]
137
136
 
138
137
  @declared_params.concat attrs
139
138
  end
140
139
  end
141
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
+
142
148
  private
143
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
+
144
163
  def require_required_and_optional_fields(context, opts)
145
164
  if context == :all
146
165
  optional_fields = Array(opts[:except])
@@ -152,6 +171,7 @@ module Grape
152
171
  required_fields.each do |field|
153
172
  field_opts = opts[:using][field]
154
173
  raise ArgumentError, "required field not exist: #{field}" unless field_opts
174
+
155
175
  requires(field, field_opts)
156
176
  end
157
177
  optional_fields.each do |field|
@@ -191,11 +211,12 @@ module Grape
191
211
  end
192
212
 
193
213
  self.class.new(
194
- api: @api,
195
- element: attrs[1][:as] || attrs.first,
196
- parent: self,
214
+ api: @api,
215
+ element: attrs.first,
216
+ element_renamed: attrs[1][:as],
217
+ parent: self,
197
218
  optional: optional,
198
- type: type || Array,
219
+ type: type || Array,
199
220
  &block
200
221
  )
201
222
  end
@@ -209,11 +230,11 @@ module Grape
209
230
  # @yield parameter scope
210
231
  def new_lateral_scope(options, &block)
211
232
  self.class.new(
212
- api: @api,
213
- element: nil,
214
- parent: self,
215
- options: @optional,
216
- type: type == Array ? Array : Hash,
233
+ api: @api,
234
+ element: nil,
235
+ parent: self,
236
+ options: @optional,
237
+ type: type == Array ? Array : Hash,
217
238
  dependent_on: options[:dependent_on],
218
239
  &block
219
240
  )
@@ -226,15 +247,17 @@ module Grape
226
247
  # @yield parameter scope
227
248
  def new_group_scope(attrs, &block)
228
249
  self.class.new(
229
- api: @api,
230
- parent: self,
231
- group: attrs.first,
250
+ api: @api,
251
+ parent: self,
252
+ group: attrs.first,
232
253
  &block
233
254
  )
234
255
  end
235
256
 
236
257
  # Pushes declared params to parent or settings
237
258
  def configure_declared_params
259
+ push_renamed_param(full_path, @element_renamed) if @element_renamed
260
+
238
261
  if nested?
239
262
  @parent.push_declared_params [element => @declared_params]
240
263
  else
@@ -283,16 +306,15 @@ module Grape
283
306
 
284
307
  doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
285
308
 
286
- full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
287
- @api.document_attribute(full_attrs, doc_attrs)
309
+ document_attribute(attrs, doc_attrs)
288
310
 
289
311
  opts = derive_validator_options(validations)
290
312
 
313
+ order_specific_validations = Set[:as]
314
+
291
315
  # Validate for presence before any other validators
292
- if validations.key?(:presence) && validations[:presence]
293
- validate('presence', validations[:presence], attrs, doc_attrs, opts)
294
- validations.delete(:presence)
295
- validations.delete(:message) if validations.key?(:message)
316
+ validates_presence(validations, attrs, doc_attrs, opts) do |validation_type|
317
+ order_specific_validations << validation_type
296
318
  end
297
319
 
298
320
  # Before we run the rest of the validators, let's handle
@@ -301,6 +323,8 @@ module Grape
301
323
  coerce_type validations, attrs, doc_attrs, opts
302
324
 
303
325
  validations.each do |type, options|
326
+ next if order_specific_validations.include?(type)
327
+
304
328
  validate(type, options, attrs, doc_attrs, opts)
305
329
  end
306
330
  end
@@ -319,9 +343,7 @@ module Grape
319
343
  # @return [class-like] type to which the parameter will be coerced
320
344
  # @raise [ArgumentError] if the given type options are invalid
321
345
  def infer_coercion(validations)
322
- if validations.key?(:type) && validations.key?(:types)
323
- raise ArgumentError, ':type may not be supplied with :types'
324
- end
346
+ raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
325
347
 
326
348
  validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
327
349
  validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
@@ -357,6 +379,7 @@ module Grape
357
379
  # but not special JSON types, which
358
380
  # already imply coercion method
359
381
  return unless [JSON, Array[JSON]].include? validations[:coerce]
382
+
360
383
  raise ArgumentError, 'coerce_with disallowed for type: JSON'
361
384
  end
362
385
 
@@ -384,6 +407,7 @@ module Grape
384
407
 
385
408
  def guess_coerce_type(coerce_type, *values_list)
386
409
  return coerce_type unless coerce_type == Array
410
+
387
411
  values_list.each do |values|
388
412
  next if !values || values.is_a?(Proc)
389
413
  return values.first.class if values.is_a?(Range) || !values.empty?
@@ -394,14 +418,11 @@ module Grape
394
418
  def check_incompatible_option_values(default, values, except_values, excepts)
395
419
  return unless default && !default.is_a?(Proc)
396
420
 
397
- if values && !values.is_a?(Proc)
398
- raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
399
- unless Array(default).all? { |def_val| values.include?(def_val) }
400
- 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) }
401
422
 
402
- 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) }
403
424
  raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
404
- unless Array(default).none? { |def_val| except_values.include?(def_val) }
425
+
405
426
  end
406
427
 
407
428
  return unless excepts && !excepts.is_a?(Proc)
@@ -415,11 +436,11 @@ module Grape
415
436
  raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
416
437
 
417
438
  validator_options = {
418
- attributes: attrs,
419
- options: options,
420
- required: doc_attrs[:required],
421
- params_scope: self,
422
- opts: opts,
439
+ attributes: attrs,
440
+ options: options,
441
+ required: doc_attrs[:required],
442
+ params_scope: self,
443
+ opts: opts,
423
444
  validator_class: validator_class
424
445
  }
425
446
  @api.namespace_stackable(:validations, validator_options)
@@ -427,21 +448,20 @@ module Grape
427
448
 
428
449
  def validate_value_coercion(coerce_type, *values_list)
429
450
  return unless coerce_type
451
+
430
452
  coerce_type = coerce_type.first if coerce_type.is_a?(Array)
431
453
  values_list.each do |values|
432
454
  next if !values || values.is_a?(Proc)
455
+
433
456
  value_types = values.is_a?(Range) ? [values.begin, values.end] : values
434
- if coerce_type == Grape::API::Boolean
435
- value_types = value_types.map { |type| Grape::API::Boolean.build(type) }
436
- end
437
- unless value_types.all? { |v| v.is_a? coerce_type }
438
- raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
439
- 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)
440
459
  end
441
460
  end
442
461
 
443
462
  def extract_message_option(attrs)
444
463
  return nil unless attrs.is_a?(Array)
464
+
445
465
  opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
446
466
  opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
447
467
  end
@@ -461,9 +481,22 @@ module Grape
461
481
 
462
482
  {
463
483
  allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
464
- fail_fast: validations.delete(:fail_fast) || false
484
+ fail_fast: validations.delete(:fail_fast) || false
465
485
  }
466
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
467
500
  end
468
501
  end
469
502
  end
@@ -56,6 +56,7 @@ module Grape
56
56
 
57
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
@@ -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)
@@ -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
@@ -30,6 +30,7 @@ module Grape
30
30
  # @return [void]
31
31
  def validate(request)
32
32
  return unless @scope.should_validate?(request.params)
33
+
33
34
  validate!(request.params)
34
35
  end
35
36
 
@@ -48,8 +49,9 @@ module Grape
48
49
  next if skip_value
49
50
  next if !@scope.required? && empty_val
50
51
  next unless @scope.meets_dependency?(val, params)
52
+
51
53
  begin
52
- 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))
53
55
  rescue Grape::Exceptions::Validation => e
54
56
  array_errors << e
55
57
  end
@@ -69,6 +71,7 @@ module Grape
69
71
 
70
72
  def self.inherited(klass)
71
73
  return unless klass.name.present?
74
+
72
75
  Validations.register_validator(convert_to_short_name(klass), klass)
73
76
  end
74
77
 
@@ -27,10 +27,6 @@ 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
 
@@ -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
@@ -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
@@ -9,6 +9,7 @@ module Grape
9
9
 
10
10
  attributes.each do |resource_params, skip_value|
11
11
  next if skip_value
12
+
12
13
  begin
13
14
  validate_params!(resource_params)
14
15
  rescue Grape::Exceptions::Validation => e
@@ -23,6 +24,7 @@ module Grape
23
24
 
24
25
  def keys_in_common(resource_params)
25
26
  return [] unless resource_params.is_a?(Hash)
27
+
26
28
  all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) }
27
29
  end
28
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
@@ -13,6 +13,7 @@ module Grape
13
13
  'Use the except validator instead.' if @excepts
14
14
 
15
15
  raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
16
+
16
17
  warn '[DEPRECATION] The values validator proc option is deprecated. ' \
17
18
  'The lambda expression can now be assigned directly to values.' if @proc
18
19
  else
@@ -51,6 +52,7 @@ module Grape
51
52
  def check_values(param_array, attr_name)
52
53
  values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
53
54
  return true if values.nil?
55
+
54
56
  begin
55
57
  return param_array.all? { |param| values.call(param) } if values.is_a? Proc
56
58
  rescue StandardError => e
@@ -63,6 +65,7 @@ module Grape
63
65
  def check_excepts(param_array)
64
66
  excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
65
67
  return true if excepts.nil?
68
+
66
69
  param_array.none? { |param| excepts.include?(param) }
67
70
  end
68
71
 
data/lib/grape/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '1.5.3'
5
+ VERSION = '1.6.0'
6
6
  end
data/lib/grape.rb CHANGED
@@ -22,7 +22,7 @@ require 'active_support/dependencies/autoload'
22
22
  require 'active_support/notifications'
23
23
  require 'i18n'
24
24
 
25
- I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__)
25
+ I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
26
26
 
27
27
  module Grape
28
28
  extend ::ActiveSupport::Autoload
@@ -10,6 +10,7 @@ describe Grape::Validations do
10
10
  def validate_param!(attr_name, params)
11
11
  @option = params[:max].to_i if params.key?(:max)
12
12
  return if params[attr_name].length <= @option
13
+
13
14
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long")
14
15
  end
15
16
  end
@@ -11,7 +11,7 @@ describe Grape::Endpoint do
11
11
 
12
12
  context 'get' do
13
13
  it 'routes to a namespace param with dots' do
14
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
14
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
15
15
  get '/' do
16
16
  params[:ns_with_dots]
17
17
  end
@@ -23,8 +23,8 @@ describe Grape::Endpoint do
23
23
  end
24
24
 
25
25
  it 'routes to a path with multiple params with dots' do
26
- subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+},
27
- another_id_with_dots: %r{[^\/]+} } do
26
+ subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^/]+},
27
+ another_id_with_dots: %r{[^/]+} } do
28
28
  "#{params[:id_with_dots]}/#{params[:another_id_with_dots]}"
29
29
  end
30
30
 
@@ -34,9 +34,9 @@ describe Grape::Endpoint do
34
34
  end
35
35
 
36
36
  it 'routes to namespace and path params with dots, with overridden requirements' do
37
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
38
- get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^\/]+},
39
- another_id_with_dots: %r{[^\/]+} } do
37
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
38
+ get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^/]+},
39
+ another_id_with_dots: %r{[^/]+} } do
40
40
  "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
41
41
  end
42
42
  end
@@ -47,8 +47,8 @@ describe Grape::Endpoint do
47
47
  end
48
48
 
49
49
  it 'routes to namespace and path params with dots, with merged requirements' do
50
- subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do
51
- get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^\/]+} } do
50
+ subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do
51
+ get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^/]+} } do
52
52
  "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}"
53
53
  end
54
54
  end