grape 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/CONTRIBUTING.md +2 -10
- data/README.md +106 -43
- data/UPGRADING.md +90 -1
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +51 -73
- data/lib/grape/api.rb +56 -89
- data/lib/grape/cookies.rb +31 -25
- data/lib/grape/dry_types.rb +48 -4
- data/lib/grape/dsl/callbacks.rb +8 -58
- data/lib/grape/dsl/desc.rb +8 -67
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +60 -65
- data/lib/grape/dsl/inside_route.rb +26 -61
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +10 -19
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +230 -194
- data/lib/grape/dsl/settings.rb +22 -134
- data/lib/grape/dsl/validations.rb +37 -45
- data/lib/grape/endpoint.rb +91 -126
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/conflicting_types.rb +11 -0
- data/lib/grape/exceptions/invalid_parameters.rb +11 -0
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
- data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
- data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
- data/lib/grape/extensions/hash.rb +2 -1
- data/lib/grape/extensions/hashie/mash.rb +3 -5
- data/lib/grape/locale/en.yml +44 -44
- data/lib/grape/middleware/auth/base.rb +11 -32
- data/lib/grape/middleware/auth/dsl.rb +22 -29
- data/lib/grape/middleware/base.rb +30 -11
- data/lib/grape/middleware/error.rb +14 -32
- data/lib/grape/middleware/formatter.rb +40 -72
- data/lib/grape/middleware/stack.rb +28 -38
- data/lib/grape/middleware/versioner/accept_version_header.rb +2 -4
- data/lib/grape/middleware/versioner/base.rb +30 -56
- data/lib/grape/middleware/versioner/header.rb +2 -2
- data/lib/grape/middleware/versioner/param.rb +2 -3
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +11 -0
- data/lib/grape/params_builder/base.rb +20 -0
- data/lib/grape/params_builder/hash.rb +11 -0
- data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
- data/lib/grape/params_builder/hashie_mash.rb +11 -0
- data/lib/grape/params_builder.rb +32 -0
- data/lib/grape/request.rb +161 -22
- data/lib/grape/router/route.rb +1 -1
- data/lib/grape/router.rb +27 -8
- data/lib/grape/util/api_description.rb +56 -0
- data/lib/grape/util/base_inheritable.rb +5 -2
- data/lib/grape/util/inheritable_setting.rb +7 -0
- data/lib/grape/util/media_type.rb +1 -1
- data/lib/grape/util/registry.rb +1 -1
- data/lib/grape/validations/contract_scope.rb +2 -2
- data/lib/grape/validations/params_documentation.rb +50 -0
- data/lib/grape/validations/params_scope.rb +46 -56
- data/lib/grape/validations/types/array_coercer.rb +2 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
- data/lib/grape/validations/types/primitive_coercer.rb +1 -28
- data/lib/grape/validations/types.rb +10 -25
- data/lib/grape/validations/validators/base.rb +2 -9
- data/lib/grape/validations/validators/except_values_validator.rb +1 -1
- data/lib/grape/validations/validators/presence_validator.rb +1 -1
- data/lib/grape/validations/validators/regexp_validator.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +18 -9
- metadata +35 -20
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -19
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/error_formatter/jsonapi.rb +0 -7
- data/lib/grape/http/headers.rb +0 -56
- data/lib/grape/middleware/helpers.rb +0 -12
- data/lib/grape/parser/jsonapi.rb +0 -7
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/lazy/object.rb +0 -45
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Validations
|
|
5
|
+
# Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
|
|
6
|
+
# internal API), the class only cleans up attributes to avoid junk in RAM.
|
|
7
|
+
|
|
8
|
+
module ParamsDocumentation
|
|
9
|
+
def document_params(attrs, validations, type = nil, values = nil, except_values = nil)
|
|
10
|
+
return validations.except!(:desc, :description, :documentation) if @api.inheritable_setting.namespace_inheritable[:do_not_document]
|
|
11
|
+
|
|
12
|
+
documented_attrs = attrs.each_with_object({}) do |name, memo|
|
|
13
|
+
memo[full_name(name)] = extract_details(validations, type, values, except_values)
|
|
14
|
+
end
|
|
15
|
+
@api.inheritable_setting.namespace_stackable[:params] = documented_attrs
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def extract_details(validations, type, values, except_values)
|
|
21
|
+
{}.tap do |details|
|
|
22
|
+
details[:required] = validations.key?(:presence)
|
|
23
|
+
details[:type] = TypeCache[type] if type
|
|
24
|
+
details[:values] = values if values
|
|
25
|
+
details[:except_values] = except_values if except_values
|
|
26
|
+
details[:default] = validations[:default] if validations.key?(:default)
|
|
27
|
+
if validations.key?(:length)
|
|
28
|
+
details[:min_length] = validations[:length][:min] if validations[:length].key?(:min)
|
|
29
|
+
details[:max_length] = validations[:length][:max] if validations[:length].key?(:max)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc = validations.delete(:desc) || validations.delete(:description)
|
|
33
|
+
details[:desc] = desc if desc
|
|
34
|
+
|
|
35
|
+
documentation = validations.delete(:documentation)
|
|
36
|
+
details[:documentation] = documentation if documentation
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class TypeCache < Grape::Util::Cache
|
|
41
|
+
def initialize
|
|
42
|
+
super
|
|
43
|
+
@cache = Hash.new do |h, type|
|
|
44
|
+
h[type] = type.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -4,9 +4,10 @@ module Grape
|
|
|
4
4
|
module Validations
|
|
5
5
|
class ParamsScope
|
|
6
6
|
attr_accessor :element, :parent, :index
|
|
7
|
-
attr_reader :type
|
|
7
|
+
attr_reader :type, :params_meeting_dependency
|
|
8
8
|
|
|
9
9
|
include Grape::DSL::Parameters
|
|
10
|
+
include Grape::Validations::ParamsDocumentation
|
|
10
11
|
|
|
11
12
|
# There are a number of documentation options on entities that don't have
|
|
12
13
|
# corresponding validators. Since there is nowhere that enumerates them all,
|
|
@@ -67,6 +68,7 @@ module Grape
|
|
|
67
68
|
@type = opts[:type]
|
|
68
69
|
@group = opts[:group]
|
|
69
70
|
@dependent_on = opts[:dependent_on]
|
|
71
|
+
@params_meeting_dependency = []
|
|
70
72
|
@declared_params = []
|
|
71
73
|
@index = nil
|
|
72
74
|
|
|
@@ -94,7 +96,11 @@ module Grape
|
|
|
94
96
|
def meets_dependency?(params, request_params)
|
|
95
97
|
return true unless @dependent_on
|
|
96
98
|
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
|
97
|
-
|
|
99
|
+
|
|
100
|
+
if params.is_a?(Array)
|
|
101
|
+
@params_meeting_dependency = params.flatten.filter { |param| meets_dependency?(param, request_params) }
|
|
102
|
+
return @params_meeting_dependency.present?
|
|
103
|
+
end
|
|
98
104
|
|
|
99
105
|
meets_hash_dependency?(params)
|
|
100
106
|
end
|
|
@@ -127,7 +133,7 @@ module Grape
|
|
|
127
133
|
def full_name(name, index: nil)
|
|
128
134
|
if nested?
|
|
129
135
|
# Find our containing element's name, and append ours.
|
|
130
|
-
"#{@parent.full_name(@element)}#{brackets(
|
|
136
|
+
"#{@parent.full_name(@element)}#{brackets(index || @index)}#{brackets(name)}"
|
|
131
137
|
elsif lateral?
|
|
132
138
|
# Find the name of the element as if it was at the same nesting level
|
|
133
139
|
# as our parent. We need to forward our index upward to achieve this.
|
|
@@ -205,28 +211,32 @@ module Grape
|
|
|
205
211
|
# @param new_name [String, Symbol] the new name of the parameter (the
|
|
206
212
|
# renamed name, with the +as: ...+ semantic)
|
|
207
213
|
def push_renamed_param(path, new_name)
|
|
208
|
-
|
|
214
|
+
api_route_setting = @api.inheritable_setting.route
|
|
215
|
+
base = api_route_setting[:renamed_params] || {}
|
|
209
216
|
base[Array(path).map(&:to_s)] = new_name.to_s
|
|
210
|
-
|
|
217
|
+
api_route_setting[:renamed_params] = base
|
|
211
218
|
end
|
|
212
219
|
|
|
213
220
|
def require_required_and_optional_fields(context, opts)
|
|
221
|
+
except_fields = Array.wrap(opts[:except])
|
|
222
|
+
using_fields = opts[:using].keys.delete_if { |f| except_fields.include?(f) }
|
|
223
|
+
|
|
214
224
|
if context == :all
|
|
215
|
-
optional_fields =
|
|
216
|
-
required_fields =
|
|
225
|
+
optional_fields = except_fields
|
|
226
|
+
required_fields = using_fields
|
|
217
227
|
else # context == :none
|
|
218
|
-
required_fields =
|
|
219
|
-
optional_fields =
|
|
228
|
+
required_fields = except_fields
|
|
229
|
+
optional_fields = using_fields
|
|
220
230
|
end
|
|
221
231
|
required_fields.each do |field|
|
|
222
232
|
field_opts = opts[:using][field]
|
|
223
233
|
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
|
224
234
|
|
|
225
|
-
requires(field, field_opts)
|
|
235
|
+
requires(field, **field_opts)
|
|
226
236
|
end
|
|
227
237
|
optional_fields.each do |field|
|
|
228
238
|
field_opts = opts[:using][field]
|
|
229
|
-
optional(field, field_opts) if field_opts
|
|
239
|
+
optional(field, **field_opts) if field_opts
|
|
230
240
|
end
|
|
231
241
|
end
|
|
232
242
|
|
|
@@ -238,7 +248,7 @@ module Grape
|
|
|
238
248
|
end
|
|
239
249
|
optional_fields.each do |field|
|
|
240
250
|
field_opts = opts[:using][field]
|
|
241
|
-
optional(field, field_opts) if field_opts
|
|
251
|
+
optional(field, **field_opts) if field_opts
|
|
242
252
|
end
|
|
243
253
|
end
|
|
244
254
|
|
|
@@ -255,9 +265,9 @@ module Grape
|
|
|
255
265
|
# @param optional [Boolean] whether the parameter this are nested under
|
|
256
266
|
# is optional or not (and hence, whether this block's params will be).
|
|
257
267
|
# @yield parameter scope
|
|
258
|
-
def new_scope(attrs, optional = false, &block)
|
|
268
|
+
def new_scope(attrs, opts, optional = false, &block)
|
|
259
269
|
# if required params are grouped and no type or unsupported type is provided, raise an error
|
|
260
|
-
type =
|
|
270
|
+
type = opts[:type]
|
|
261
271
|
if attrs.first && !optional
|
|
262
272
|
raise Grape::Exceptions::MissingGroupType if type.nil?
|
|
263
273
|
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
|
@@ -266,7 +276,7 @@ module Grape
|
|
|
266
276
|
self.class.new(
|
|
267
277
|
api: @api,
|
|
268
278
|
element: attrs.first,
|
|
269
|
-
element_renamed:
|
|
279
|
+
element_renamed: opts[:as],
|
|
270
280
|
parent: self,
|
|
271
281
|
optional: optional,
|
|
272
282
|
type: type || Array,
|
|
@@ -310,7 +320,7 @@ module Grape
|
|
|
310
320
|
if nested?
|
|
311
321
|
@parent.push_declared_params [element => @declared_params]
|
|
312
322
|
else
|
|
313
|
-
@api.namespace_stackable
|
|
323
|
+
@api.inheritable_setting.namespace_stackable[:declared_params] = @declared_params
|
|
314
324
|
end
|
|
315
325
|
|
|
316
326
|
# params were stored in settings, it can be cleaned from the params scope
|
|
@@ -318,56 +328,40 @@ module Grape
|
|
|
318
328
|
end
|
|
319
329
|
|
|
320
330
|
def validates(attrs, validations)
|
|
321
|
-
doc = AttributesDoc.new @api, self
|
|
322
|
-
doc.extract_details validations
|
|
323
|
-
|
|
324
331
|
coerce_type = infer_coercion(validations)
|
|
325
|
-
|
|
326
|
-
doc.type = coerce_type
|
|
327
|
-
|
|
332
|
+
required = validations.key?(:presence)
|
|
328
333
|
default = validations[:default]
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
values = values_hash[:value]
|
|
332
|
-
# NB: excepts is deprecated
|
|
333
|
-
excepts = values_hash[:except]
|
|
334
|
-
else
|
|
335
|
-
values = validations[:values]
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
doc.values = values
|
|
339
|
-
|
|
340
|
-
except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
|
|
334
|
+
values = validations[:values].is_a?(Hash) ? validations.dig(:values, :value) : validations[:values]
|
|
335
|
+
except_values = validations[:except_values].is_a?(Hash) ? validations.dig(:except_values, :value) : validations[:except_values]
|
|
341
336
|
|
|
342
337
|
# NB. values and excepts should be nil, Proc, Array, or Range.
|
|
343
338
|
# Specifically, values should NOT be a Hash
|
|
344
|
-
|
|
345
339
|
# use values or excepts to guess coerce type when stated type is Array
|
|
346
|
-
coerce_type = guess_coerce_type(coerce_type, values, except_values
|
|
340
|
+
coerce_type = guess_coerce_type(coerce_type, values, except_values)
|
|
347
341
|
|
|
348
342
|
# default value should be present in values array, if both exist and are not procs
|
|
349
|
-
check_incompatible_option_values(default, values, except_values
|
|
343
|
+
check_incompatible_option_values(default, values, except_values)
|
|
350
344
|
|
|
351
345
|
# type should be compatible with values array, if both exist
|
|
352
|
-
validate_value_coercion(coerce_type, values, except_values
|
|
346
|
+
validate_value_coercion(coerce_type, values, except_values)
|
|
353
347
|
|
|
354
|
-
|
|
348
|
+
document_params attrs, validations, coerce_type, values, except_values
|
|
355
349
|
|
|
356
350
|
opts = derive_validator_options(validations)
|
|
357
351
|
|
|
358
352
|
# Validate for presence before any other validators
|
|
359
|
-
validates_presence(validations, attrs,
|
|
353
|
+
validates_presence(validations, attrs, opts)
|
|
360
354
|
|
|
361
355
|
# Before we run the rest of the validators, let's handle
|
|
362
356
|
# whatever coercion so that we are working with correctly
|
|
363
357
|
# type casted values
|
|
364
|
-
coerce_type validations, attrs,
|
|
358
|
+
coerce_type validations, attrs, required, opts
|
|
365
359
|
|
|
366
360
|
validations.each do |type, options|
|
|
367
361
|
# Don't try to look up validators for documentation params that don't have one.
|
|
368
362
|
next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type)
|
|
369
363
|
|
|
370
|
-
validate(type, options, attrs,
|
|
364
|
+
validate(type, options, attrs, required, opts)
|
|
371
365
|
end
|
|
372
366
|
end
|
|
373
367
|
|
|
@@ -431,7 +425,7 @@ module Grape
|
|
|
431
425
|
# composited from more than one +requires+/+optional+
|
|
432
426
|
# parameter, and needs to be run before most other
|
|
433
427
|
# validations.
|
|
434
|
-
def coerce_type(validations, attrs,
|
|
428
|
+
def coerce_type(validations, attrs, required, opts)
|
|
435
429
|
check_coerce_with(validations)
|
|
436
430
|
|
|
437
431
|
return unless validations.key?(:coerce)
|
|
@@ -441,7 +435,7 @@ module Grape
|
|
|
441
435
|
method: validations[:coerce_with],
|
|
442
436
|
message: validations[:coerce_message]
|
|
443
437
|
}
|
|
444
|
-
validate('coerce', coerce_options, attrs,
|
|
438
|
+
validate('coerce', coerce_options, attrs, required, opts)
|
|
445
439
|
validations.delete(:coerce_with)
|
|
446
440
|
validations.delete(:coerce)
|
|
447
441
|
validations.delete(:coerce_message)
|
|
@@ -457,30 +451,26 @@ module Grape
|
|
|
457
451
|
coerce_type
|
|
458
452
|
end
|
|
459
453
|
|
|
460
|
-
def check_incompatible_option_values(default, values, except_values
|
|
454
|
+
def check_incompatible_option_values(default, values, except_values)
|
|
461
455
|
return unless default && !default.is_a?(Proc)
|
|
462
456
|
|
|
463
457
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
|
|
464
458
|
|
|
465
|
-
|
|
466
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
|
467
|
-
end
|
|
459
|
+
return unless except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
|
468
460
|
|
|
469
|
-
|
|
470
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, excepts) \
|
|
471
|
-
unless Array(default).none? { |def_val| excepts.include?(def_val) }
|
|
461
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
|
472
462
|
end
|
|
473
463
|
|
|
474
|
-
def validate(type, options, attrs,
|
|
464
|
+
def validate(type, options, attrs, required, opts)
|
|
475
465
|
validator_options = {
|
|
476
466
|
attributes: attrs,
|
|
477
467
|
options: options,
|
|
478
|
-
required:
|
|
468
|
+
required: required,
|
|
479
469
|
params_scope: self,
|
|
480
470
|
opts: opts,
|
|
481
471
|
validator_class: Validations.require_validator(type)
|
|
482
472
|
}
|
|
483
|
-
@api.namespace_stackable
|
|
473
|
+
@api.inheritable_setting.namespace_stackable[:validations] = validator_options
|
|
484
474
|
end
|
|
485
475
|
|
|
486
476
|
def validate_value_coercion(coerce_type, *values_list)
|
|
@@ -522,10 +512,10 @@ module Grape
|
|
|
522
512
|
}
|
|
523
513
|
end
|
|
524
514
|
|
|
525
|
-
def validates_presence(validations, attrs,
|
|
515
|
+
def validates_presence(validations, attrs, opts)
|
|
526
516
|
return unless validations.key?(:presence) && validations[:presence]
|
|
527
517
|
|
|
528
|
-
validate('presence', validations.delete(:presence), attrs,
|
|
518
|
+
validate('presence', validations.delete(:presence), attrs, true, opts)
|
|
529
519
|
validations.delete(:message) if validations.key?(:message)
|
|
530
520
|
end
|
|
531
521
|
end
|
|
@@ -7,15 +7,14 @@ module Grape
|
|
|
7
7
|
# an array of arrays of integers.
|
|
8
8
|
#
|
|
9
9
|
# It could've been possible to use an +of+
|
|
10
|
-
# method (https://dry-rb.org/gems/dry-types/
|
|
10
|
+
# method (https://dry-rb.org/gems/dry-types/main/array-with-member/)
|
|
11
11
|
# provided by dry-types. Unfortunately, it doesn't work for Grape because of
|
|
12
12
|
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
|
13
13
|
# maintains Virtus behavior in coercing.
|
|
14
14
|
class ArrayCoercer < DryTypeCoercer
|
|
15
15
|
def initialize(type, strict = false)
|
|
16
16
|
super
|
|
17
|
-
|
|
18
|
-
@coercer = scope::Array
|
|
17
|
+
@coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array
|
|
19
18
|
@subtype = type.first
|
|
20
19
|
end
|
|
21
20
|
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module DryTypes
|
|
4
|
-
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
|
5
|
-
# a container in this case. Check documentation for more information
|
|
6
|
-
# https://dry-rb.org/gems/dry-types/1.2/getting-started/
|
|
7
|
-
include Dry.Types()
|
|
8
|
-
end
|
|
9
|
-
|
|
10
3
|
module Grape
|
|
11
4
|
module Validations
|
|
12
5
|
module Types
|
|
13
6
|
# A base class for classes which must identify a coercer to be used.
|
|
14
7
|
# If the +strict+ argument is true, it won't coerce the given value
|
|
15
8
|
# but check its type. More information there
|
|
16
|
-
# https://dry-rb.org/gems/dry-types/
|
|
9
|
+
# https://dry-rb.org/gems/dry-types/main/built-in-types/
|
|
17
10
|
class DryTypeCoercer
|
|
18
11
|
class << self
|
|
19
12
|
# Returns a collection coercer which corresponds to a given type.
|
|
@@ -42,7 +35,7 @@ module Grape
|
|
|
42
35
|
def initialize(type, strict = false)
|
|
43
36
|
@type = type
|
|
44
37
|
@strict = strict
|
|
45
|
-
@
|
|
38
|
+
@cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache
|
|
46
39
|
end
|
|
47
40
|
|
|
48
41
|
# Coerces the given value to a type which was specified during
|
|
@@ -53,13 +46,13 @@ module Grape
|
|
|
53
46
|
return if val.nil?
|
|
54
47
|
|
|
55
48
|
@coercer[val]
|
|
56
|
-
rescue Dry::Types::CoercionError
|
|
49
|
+
rescue Dry::Types::CoercionError
|
|
57
50
|
InvalidValue.new
|
|
58
51
|
end
|
|
59
52
|
|
|
60
53
|
protected
|
|
61
54
|
|
|
62
|
-
attr_reader :
|
|
55
|
+
attr_reader :type, :strict, :cache_coercer
|
|
63
56
|
end
|
|
64
57
|
end
|
|
65
58
|
end
|
|
@@ -7,37 +7,10 @@ module Grape
|
|
|
7
7
|
# initialization. When +strict+ is true, it doesn't coerce a value but check
|
|
8
8
|
# that it has the proper type.
|
|
9
9
|
class PrimitiveCoercer < DryTypeCoercer
|
|
10
|
-
MAPPING = {
|
|
11
|
-
Grape::API::Boolean => DryTypes::Params::Bool,
|
|
12
|
-
BigDecimal => DryTypes::Params::Decimal,
|
|
13
|
-
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
|
14
|
-
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
|
15
|
-
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
|
16
|
-
|
|
17
|
-
# unfortunately, a +Params+ scope doesn't contain String
|
|
18
|
-
String => DryTypes::Coercible::String
|
|
19
|
-
}.freeze
|
|
20
|
-
|
|
21
|
-
STRICT_MAPPING = {
|
|
22
|
-
Grape::API::Boolean => DryTypes::Strict::Bool,
|
|
23
|
-
BigDecimal => DryTypes::Strict::Decimal,
|
|
24
|
-
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
|
25
|
-
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
|
26
|
-
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
|
27
|
-
}.freeze
|
|
28
|
-
|
|
29
10
|
def initialize(type, strict = false)
|
|
30
11
|
super
|
|
31
12
|
|
|
32
|
-
@
|
|
33
|
-
|
|
34
|
-
@coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
|
|
35
|
-
scope.const_get(type.name, false)
|
|
36
|
-
rescue NameError
|
|
37
|
-
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
|
|
38
|
-
|
|
39
|
-
type
|
|
40
|
-
end
|
|
13
|
+
@coercer = cache_coercer[type]
|
|
41
14
|
end
|
|
42
15
|
|
|
43
16
|
def call(val)
|
|
@@ -155,9 +155,10 @@ module Grape
|
|
|
155
155
|
# @return [Object] object to be used
|
|
156
156
|
# for coercion and type validation
|
|
157
157
|
def build_coercer(type, method: nil, strict: false)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
# no cache since unique
|
|
159
|
+
return create_coercer_instance(type, method, strict) if method.respond_to?(:call)
|
|
160
|
+
|
|
161
|
+
CoercerCache[[type, method, strict]]
|
|
161
162
|
end
|
|
162
163
|
|
|
163
164
|
def create_coercer_instance(type, method, strict)
|
|
@@ -184,30 +185,14 @@ module Grape
|
|
|
184
185
|
end
|
|
185
186
|
end
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
@__cache_write_lock.synchronize do
|
|
195
|
-
@__cache[key] = instance
|
|
188
|
+
class CoercerCache < Grape::Util::Cache
|
|
189
|
+
def initialize
|
|
190
|
+
super
|
|
191
|
+
@cache = Hash.new do |h, (type, method, strict)|
|
|
192
|
+
h[[type, method, strict]] = Grape::Validations::Types.create_coercer_instance(type, method, strict)
|
|
193
|
+
end
|
|
196
194
|
end
|
|
197
|
-
|
|
198
|
-
instance
|
|
199
195
|
end
|
|
200
|
-
|
|
201
|
-
def cache_key(type, method, strict)
|
|
202
|
-
[type, method, strict].each_with_object(+'_') do |val, memo|
|
|
203
|
-
next if val.nil?
|
|
204
|
-
|
|
205
|
-
memo << '_' << val.to_s
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
instance_variable_set(:@__cache, {})
|
|
210
|
-
instance_variable_set(:@__cache_write_lock, Mutex.new)
|
|
211
196
|
end
|
|
212
197
|
end
|
|
213
198
|
end
|
|
@@ -49,7 +49,7 @@ module Grape
|
|
|
49
49
|
next if !@scope.required? && empty_val
|
|
50
50
|
next unless @scope.meets_dependency?(val, params)
|
|
51
51
|
|
|
52
|
-
validate_param!(attr_name, val) if @required ||
|
|
52
|
+
validate_param!(attr_name, val) if @required || val.try(:key?, attr_name)
|
|
53
53
|
rescue Grape::Exceptions::Validation => e
|
|
54
54
|
array_errors << e
|
|
55
55
|
end
|
|
@@ -69,7 +69,7 @@ module Grape
|
|
|
69
69
|
|
|
70
70
|
def options_key?(key, options = nil)
|
|
71
71
|
options = instance_variable_get(:@option) if options.nil?
|
|
72
|
-
options.
|
|
72
|
+
options.try(:key?, key) && !options[key].nil?
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def fail_fast?
|
|
@@ -79,10 +79,3 @@ module Grape
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
|
-
|
|
83
|
-
Grape::Validations::Base = Class.new(Grape::Validations::Validators::Base) do
|
|
84
|
-
def self.inherited(*)
|
|
85
|
-
Grape.deprecator.warn 'Grape::Validations::Base is deprecated! Use Grape::Validations::Validators::Base instead.'
|
|
86
|
-
super
|
|
87
|
-
end
|
|
88
|
-
end
|
|
@@ -10,7 +10,7 @@ module Grape
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def validate_param!(attr_name, params)
|
|
13
|
-
return unless params.
|
|
13
|
+
return unless params.try(:key?, attr_name)
|
|
14
14
|
|
|
15
15
|
excepts = @except.is_a?(Proc) ? @except.call : @except
|
|
16
16
|
return if excepts.nil?
|
|
@@ -5,7 +5,7 @@ module Grape
|
|
|
5
5
|
module Validators
|
|
6
6
|
class PresenceValidator < Base
|
|
7
7
|
def validate_param!(attr_name, params)
|
|
8
|
-
return if params.
|
|
8
|
+
return if params.try(:key?, attr_name)
|
|
9
9
|
|
|
10
10
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))
|
|
11
11
|
end
|
|
@@ -5,7 +5,7 @@ module Grape
|
|
|
5
5
|
module Validators
|
|
6
6
|
class RegexpValidator < Base
|
|
7
7
|
def validate_param!(attr_name, params)
|
|
8
|
-
return unless params.
|
|
8
|
+
return unless params.try(:key?, attr_name)
|
|
9
9
|
return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.scrub.match?((options_key?(:value) ? @option[:value] : @option)) }
|
|
10
10
|
|
|
11
11
|
raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))
|
data/lib/grape/version.rb
CHANGED
data/lib/grape.rb
CHANGED
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
require 'logger'
|
|
4
4
|
require 'active_support'
|
|
5
5
|
require 'active_support/concern'
|
|
6
|
-
require 'active_support/configurable'
|
|
7
6
|
require 'active_support/version'
|
|
8
|
-
require 'active_support/isolated_execution_state'
|
|
7
|
+
require 'active_support/isolated_execution_state'
|
|
9
8
|
require 'active_support/core_ext/array/conversions'
|
|
10
|
-
require 'active_support/core_ext/array/extract_options'
|
|
11
9
|
require 'active_support/core_ext/array/wrap'
|
|
12
10
|
require 'active_support/core_ext/enumerable'
|
|
13
11
|
require 'active_support/core_ext/hash/conversions'
|
|
14
12
|
require 'active_support/core_ext/hash/deep_merge'
|
|
13
|
+
require 'active_support/core_ext/hash/deep_transform_values'
|
|
15
14
|
require 'active_support/core_ext/hash/except'
|
|
16
15
|
require 'active_support/core_ext/hash/indifferent_access'
|
|
17
16
|
require 'active_support/core_ext/hash/keys'
|
|
@@ -20,6 +19,7 @@ require 'active_support/core_ext/hash/slice'
|
|
|
20
19
|
require 'active_support/core_ext/module/delegation'
|
|
21
20
|
require 'active_support/core_ext/object/blank'
|
|
22
21
|
require 'active_support/core_ext/object/deep_dup'
|
|
22
|
+
require 'active_support/core_ext/object/try'
|
|
23
23
|
require 'active_support/core_ext/object/duplicable'
|
|
24
24
|
require 'active_support/core_ext/string/output_safety'
|
|
25
25
|
require 'active_support/core_ext/string/exclude'
|
|
@@ -27,6 +27,7 @@ require 'active_support/deprecation'
|
|
|
27
27
|
require 'active_support/inflector'
|
|
28
28
|
require 'active_support/ordered_options'
|
|
29
29
|
require 'active_support/notifications'
|
|
30
|
+
require 'dry-configurable'
|
|
30
31
|
|
|
31
32
|
require 'English'
|
|
32
33
|
require 'bigdecimal'
|
|
@@ -56,16 +57,24 @@ loader.setup
|
|
|
56
57
|
I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
|
|
57
58
|
|
|
58
59
|
module Grape
|
|
59
|
-
|
|
60
|
+
extend Dry::Configurable
|
|
61
|
+
|
|
62
|
+
setting :param_builder, default: :hash_with_indifferent_access
|
|
63
|
+
setting :lint, default: false
|
|
64
|
+
|
|
65
|
+
HTTP_SUPPORTED_METHODS = [
|
|
66
|
+
Rack::GET,
|
|
67
|
+
Rack::POST,
|
|
68
|
+
Rack::PUT,
|
|
69
|
+
Rack::PATCH,
|
|
70
|
+
Rack::DELETE,
|
|
71
|
+
Rack::HEAD,
|
|
72
|
+
Rack::OPTIONS
|
|
73
|
+
].freeze
|
|
60
74
|
|
|
61
75
|
def self.deprecator
|
|
62
76
|
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')
|
|
63
77
|
end
|
|
64
|
-
|
|
65
|
-
configure do |config|
|
|
66
|
-
config.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder
|
|
67
|
-
config.compile_methods!
|
|
68
|
-
end
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
# https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html
|