grape 1.5.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -3
- data/CONTRIBUTING.md +1 -0
- data/README.md +50 -8
- data/UPGRADING.md +69 -0
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +13 -17
- data/lib/grape/api.rb +19 -14
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dry_types.rb +12 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +7 -5
- data/lib/grape/dsl/inside_route.rb +18 -9
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +4 -4
- 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/auth/dsl.rb +7 -1
- data/lib/grape/middleware/base.rb +5 -3
- data/lib/grape/middleware/error.rb +1 -1
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +14 -20
- 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/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/json.rb +2 -0
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/params_scope.rb +89 -56
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +3 -2
- data/lib/grape/validations/types/dry_type_coercer.rb +2 -11
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -3
- data/lib/grape/validations/types/primitive_coercer.rb +5 -7
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +83 -12
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +75 -69
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/default_validator.rb +51 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +88 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +61 -24
- data/spec/grape/api/custom_validations_spec.rb +77 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -3
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +1 -1
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
- data/spec/grape/api_remount_spec.rb +25 -19
- data/spec/grape/api_spec.rb +510 -220
- data/spec/grape/dsl/callbacks_spec.rb +2 -1
- data/spec/grape/dsl/headers_spec.rb +39 -9
- data/spec/grape/dsl/helpers_spec.rb +3 -2
- data/spec/grape/dsl/inside_route_spec.rb +6 -4
- data/spec/grape/dsl/logger_spec.rb +16 -18
- data/spec/grape/dsl/middleware_spec.rb +2 -1
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +10 -7
- data/spec/grape/endpoint/declared_spec.rb +259 -12
- data/spec/grape/endpoint_spec.rb +77 -55
- data/spec/grape/entity_spec.rb +23 -23
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
- data/spec/grape/exceptions/validation_spec.rb +5 -3
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
- data/spec/grape/integration/rack_sendfile_spec.rb +1 -1
- data/spec/grape/loading_spec.rb +8 -8
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
- data/spec/grape/middleware/auth/strategies_spec.rb +60 -20
- data/spec/grape/middleware/base_spec.rb +24 -15
- data/spec/grape/middleware/error_spec.rb +2 -2
- data/spec/grape/middleware/exception_spec.rb +111 -161
- data/spec/grape/middleware/formatter_spec.rb +27 -6
- data/spec/grape/middleware/globals_spec.rb +7 -4
- data/spec/grape/middleware/stack_spec.rb +15 -14
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
- data/spec/grape/middleware/versioner/header_spec.rb +14 -13
- data/spec/grape/middleware/versioner/param_spec.rb +7 -1
- data/spec/grape/middleware/versioner/path_spec.rb +5 -1
- data/spec/grape/middleware/versioner_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +4 -0
- data/spec/grape/path_spec.rb +52 -52
- data/spec/grape/presenters/presenter_spec.rb +7 -6
- data/spec/grape/request_spec.rb +6 -4
- data/spec/grape/util/inheritable_setting_spec.rb +7 -7
- data/spec/grape/util/inheritable_values_spec.rb +3 -2
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
- data/spec/grape/util/stackable_values_spec.rb +7 -5
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -0
- data/spec/grape/validations/params_scope_spec.rb +46 -10
- data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -1
- data/spec/grape/validations/types/primitive_coercer_spec.rb +4 -4
- data/spec/grape/validations/types_spec.rb +8 -8
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
- data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
- data/spec/grape/validations/validators/coerce_spec.rb +139 -34
- data/spec/grape/validations/validators/default_spec.rb +72 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
- data/spec/grape/validations/validators/except_values_spec.rb +3 -3
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
- data/spec/grape/validations/validators/presence_spec.rb +16 -1
- data/spec/grape/validations/validators/regexp_spec.rb +25 -31
- data/spec/grape/validations/validators/same_as_spec.rb +14 -20
- data/spec/grape/validations/validators/values_spec.rb +183 -178
- data/spec/grape/validations_spec.rb +99 -58
- data/spec/integration/eager_load/eager_load_spec.rb +2 -2
- data/spec/integration/multi_json/json_spec.rb +1 -1
- data/spec/integration/multi_xml/xml_spec.rb +1 -1
- data/spec/shared/versioning_examples.rb +32 -29
- data/spec/spec_helper.rb +12 -2
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/versioned_helpers.rb +1 -1
- metadata +117 -115
- data/lib/grape/validations/types/build_coercer.rb +0 -94
- data/lib/grape/validations/validators/all_or_none.rb +0 -15
- data/lib/grape/validations/validators/allow_blank.rb +0 -18
- data/lib/grape/validations/validators/as.rb +0 -16
- data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
- data/lib/grape/validations/validators/coerce.rb +0 -88
- data/lib/grape/validations/validators/default.rb +0 -48
- data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
- data/lib/grape/validations/validators/except_values.rb +0 -22
- data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
- data/lib/grape/validations/validators/presence.rb +0 -12
- data/lib/grape/validations/validators/regexp.rb +0 -13
- data/lib/grape/validations/validators/same_as.rb +0 -26
- data/lib/grape/validations/validators/values.rb +0 -83
@@ -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,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
|
@@ -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
|
|
@@ -1,14 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry-types'
|
4
|
-
|
5
|
-
module DryTypes
|
6
|
-
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
7
|
-
# a container in this case. Check documentation for more information
|
8
|
-
# https://dry-rb.org/gems/dry-types/1.2/getting-started/
|
9
|
-
include Dry.Types()
|
10
|
-
end
|
11
|
-
|
12
3
|
module Grape
|
13
4
|
module Validations
|
14
5
|
module Types
|
@@ -35,7 +26,7 @@ module Grape
|
|
35
26
|
|
36
27
|
# Returns an instance of a coercer for a given type
|
37
28
|
def coercer_instance_for(type, strict = false)
|
38
|
-
return PrimitiveCoercer.new(type, strict) if type.
|
29
|
+
return PrimitiveCoercer.new(type, strict) if type.instance_of?(Class)
|
39
30
|
|
40
31
|
# in case of a collection (Array[Integer]) the type is an instance of a collection,
|
41
32
|
# so we need to figure out the actual type
|
@@ -52,7 +43,7 @@ module Grape
|
|
52
43
|
def initialize(type, strict = false)
|
53
44
|
@type = type
|
54
45
|
@strict = strict
|
55
|
-
@scope = strict ? DryTypes::Strict : DryTypes::Params
|
46
|
+
@scope = strict ? Grape::DryTypes::Strict : Grape::DryTypes::Params
|
56
47
|
end
|
57
48
|
|
58
49
|
# Coerces the given value to a type which was specified during
|
@@ -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
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Validations
|
7
5
|
module Types
|
@@ -22,6 +20,7 @@ module Grape
|
|
22
20
|
|
23
21
|
# Allow nulls and blank strings
|
24
22
|
return if input.nil? || input.match?(/^\s*$/)
|
23
|
+
|
25
24
|
JSON.parse(input, symbolize_names: true)
|
26
25
|
end
|
27
26
|
|
@@ -41,7 +40,7 @@ module Grape
|
|
41
40
|
# @param value [Object] result of {#parse}
|
42
41
|
# @return [true,false]
|
43
42
|
def coerced_collection?(value)
|
44
|
-
value.is_a?(::Array) && value.all?
|
43
|
+
value.is_a?(::Array) && value.all?(::Hash)
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'dry_type_coercer'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Validations
|
7
5
|
module Types
|
@@ -10,16 +8,16 @@ module Grape
|
|
10
8
|
# that it has the proper type.
|
11
9
|
class PrimitiveCoercer < DryTypeCoercer
|
12
10
|
MAPPING = {
|
13
|
-
Grape::API::Boolean => DryTypes::Params::Bool,
|
14
|
-
BigDecimal
|
11
|
+
Grape::API::Boolean => Grape::DryTypes::Params::Bool,
|
12
|
+
BigDecimal => Grape::DryTypes::Params::Decimal,
|
15
13
|
|
16
14
|
# unfortunately, a +Params+ scope doesn't contain String
|
17
|
-
String
|
15
|
+
String => Grape::DryTypes::Coercible::String
|
18
16
|
}.freeze
|
19
17
|
|
20
18
|
STRICT_MAPPING = {
|
21
|
-
Grape::API::Boolean => DryTypes::Strict::Bool,
|
22
|
-
BigDecimal
|
19
|
+
Grape::API::Boolean => Grape::DryTypes::Strict::Bool,
|
20
|
+
BigDecimal => Grape::DryTypes::Strict::Decimal
|
23
21
|
}.freeze
|
24
22
|
|
25
23
|
def initialize(type, strict = false)
|
@@ -1,13 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'types/build_coercer'
|
4
|
-
require_relative 'types/custom_type_coercer'
|
5
|
-
require_relative 'types/custom_type_collection_coercer'
|
6
|
-
require_relative 'types/multiple_type_coercer'
|
7
|
-
require_relative 'types/variant_collection_coercer'
|
8
|
-
require_relative 'types/json'
|
9
|
-
require_relative 'types/file'
|
10
|
-
|
11
3
|
module Grape
|
12
4
|
module Validations
|
13
5
|
# Module for code related to grape's system for
|
@@ -21,10 +13,6 @@ module Grape
|
|
21
13
|
# and {Grape::Dsl::Parameters#optional}. The main
|
22
14
|
# entry point for this process is {Types.build_coercer}.
|
23
15
|
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
16
|
# Types representing a single value, which are coerced.
|
29
17
|
PRIMITIVES = [
|
30
18
|
# Numerical
|
@@ -146,6 +134,89 @@ module Grape
|
|
146
134
|
def self.map_special(type)
|
147
135
|
SPECIAL.fetch(type, type)
|
148
136
|
end
|
137
|
+
|
138
|
+
# Chooses the best coercer for the given type. For example, if the type
|
139
|
+
# is Integer, it will return a coercer which will be able to coerce a value
|
140
|
+
# to the integer.
|
141
|
+
#
|
142
|
+
# There are a few very special coercers which might be returned.
|
143
|
+
#
|
144
|
+
# +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
|
145
|
+
# the given type implies values in an array with different types.
|
146
|
+
# For example, +[Integer, String]+ allows integer and string values in
|
147
|
+
# an array.
|
148
|
+
#
|
149
|
+
# +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
|
150
|
+
# a method is specified by a user with +coerce_with+ option or the user
|
151
|
+
# specifies a custom type which implements requirments of
|
152
|
+
# +Grape::Types::CustomTypeCoercer+.
|
153
|
+
#
|
154
|
+
# +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
|
155
|
+
# previous one, but it expects an array or set of values having a custom
|
156
|
+
# type implemented by the user.
|
157
|
+
#
|
158
|
+
# There is also a group of custom types implemented by Grape, check
|
159
|
+
# +Grape::Validations::Types::SPECIAL+ to get the full list.
|
160
|
+
#
|
161
|
+
# @param type [Class] the type to which input strings
|
162
|
+
# should be coerced
|
163
|
+
# @param method [Class,#call] the coercion method to use
|
164
|
+
# @return [Object] object to be used
|
165
|
+
# for coercion and type validation
|
166
|
+
def self.build_coercer(type, method: nil, strict: false)
|
167
|
+
cache_instance(type, method, strict) do
|
168
|
+
create_coercer_instance(type, method, strict)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.create_coercer_instance(type, method, strict)
|
173
|
+
# Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
|
174
|
+
type = Types.map_special(type)
|
175
|
+
|
176
|
+
# Use a special coercer for multiply-typed parameters.
|
177
|
+
if Types.multiple?(type)
|
178
|
+
MultipleTypeCoercer.new(type, method)
|
179
|
+
|
180
|
+
# Use a special coercer for custom types and coercion methods.
|
181
|
+
elsif method || Types.custom?(type)
|
182
|
+
CustomTypeCoercer.new(type, method)
|
183
|
+
|
184
|
+
# Special coercer for collections of types that implement a parse method.
|
185
|
+
# CustomTypeCoercer (above) already handles such types when an explicit coercion
|
186
|
+
# method is supplied.
|
187
|
+
elsif Types.collection_of_custom?(type)
|
188
|
+
Types::CustomTypeCollectionCoercer.new(
|
189
|
+
Types.map_special(type.first), type.is_a?(Set)
|
190
|
+
)
|
191
|
+
else
|
192
|
+
DryTypeCoercer.coercer_instance_for(type, strict)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.cache_instance(type, method, strict, &_block)
|
197
|
+
key = cache_key(type, method, strict)
|
198
|
+
|
199
|
+
return @__cache[key] if @__cache.key?(key)
|
200
|
+
|
201
|
+
instance = yield
|
202
|
+
|
203
|
+
@__cache_write_lock.synchronize do
|
204
|
+
@__cache[key] = instance
|
205
|
+
end
|
206
|
+
|
207
|
+
instance
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.cache_key(type, method, strict)
|
211
|
+
[type, method, strict].each_with_object(+'_') do |val, memo|
|
212
|
+
next if val.nil?
|
213
|
+
|
214
|
+
memo << '_' << val.to_s
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
instance_variable_set(:@__cache, {})
|
219
|
+
instance_variable_set(:@__cache_write_lock, Mutex.new)
|
149
220
|
end
|
150
221
|
end
|
151
222
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
module Validators
|
6
|
+
class AllOrNoneOfValidator < MultipleParamsBase
|
7
|
+
def validate_params!(params)
|
8
|
+
keys = keys_in_common(params)
|
9
|
+
return if keys.empty? || keys.length == all_keys.length
|
10
|
+
|
11
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
module Validators
|
6
|
+
class AllowBlankValidator < Base
|
7
|
+
def validate_param!(attr_name, params)
|
8
|
+
return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)
|
9
|
+
|
10
|
+
value = params[attr_name]
|
11
|
+
value = value.strip if value.respond_to?(:strip)
|
12
|
+
|
13
|
+
return if value == false || value.present?
|
14
|
+
|
15
|
+
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
module Validators
|
6
|
+
class AsValidator < Base
|
7
|
+
# We use a validator for renaming parameters. This is just a marker for
|
8
|
+
# the parameter scope to handle the renaming. No actual validation
|
9
|
+
# happens here.
|
10
|
+
def validate_param!(*); end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
module Validators
|
6
|
+
class AtLeastOneOfValidator < MultipleParamsBase
|
7
|
+
def validate_params!(params)
|
8
|
+
return unless keys_in_common(params).empty?
|
9
|
+
|
10
|
+
raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|