grape 1.5.3 → 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 +13 -0
- data/README.md +23 -3
- data/UPGRADING.md +43 -1
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +5 -12
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/helpers.rb +6 -4
- data/lib/grape/dsl/inside_route.rb +17 -8
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +3 -3
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +2 -2
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/endpoint.rb +20 -35
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/validation.rb +1 -2
- 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/middleware/base.rb +2 -0
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +2 -2
- 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/path.rb +1 -0
- data/lib/grape/request.rb +1 -0
- 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/params_scope.rb +88 -55
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +3 -3
- 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 +4 -1
- data/lib/grape/validations/validators/coerce.rb +1 -5
- 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 +2 -0
- 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 +1 -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_spec.rb +126 -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 +247 -0
- data/spec/grape/endpoint_spec.rb +5 -5
- data/spec/grape/entity_spec.rb +9 -9
- 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 +3 -1
- data/spec/grape/validations/params_scope_spec.rb +37 -3
- data/spec/grape/validations/single_attribute_iterator_spec.rb +1 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +2 -2
- data/spec/grape/validations/validators/coerce_spec.rb +13 -10
- 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 +54 -42
- data/spec/shared/versioning_examples.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- 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
|
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
|
@@ -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
|
-
|
133
|
-
|
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:
|
195
|
-
element:
|
196
|
-
|
214
|
+
api: @api,
|
215
|
+
element: attrs.first,
|
216
|
+
element_renamed: attrs[1][:as],
|
217
|
+
parent: self,
|
197
218
|
optional: optional,
|
198
|
-
type:
|
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:
|
213
|
-
element:
|
214
|
-
parent:
|
215
|
-
options:
|
216
|
-
type:
|
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:
|
230
|
-
parent:
|
231
|
-
group:
|
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
|
-
|
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
|
-
|
293
|
-
|
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
|
-
|
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:
|
419
|
-
options:
|
420
|
-
required:
|
421
|
-
params_scope:
|
422
|
-
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
|
-
|
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:
|
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
|
@@ -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
|
@@ -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)
|
@@ -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
|
@@ -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].
|
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
|
|
@@ -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
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('
|
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{[
|
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{[
|
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{[
|
38
|
-
get ':another_id_with_dots', requirements: { ns_with_dots: %r{[
|
39
|
-
another_id_with_dots: %r{[
|
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{[
|
51
|
-
get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[
|
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
|