grape 2.4.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 +40 -0
- data/CONTRIBUTING.md +1 -9
- data/README.md +72 -31
- data/UPGRADING.md +34 -0
- data/grape.gemspec +4 -4
- data/lib/grape/api/instance.rb +49 -72
- data/lib/grape/api.rb +24 -34
- 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/helpers.rb +59 -64
- data/lib/grape/dsl/inside_route.rb +20 -43
- data/lib/grape/dsl/logger.rb +3 -6
- data/lib/grape/dsl/middleware.rb +22 -40
- data/lib/grape/dsl/parameters.rb +7 -16
- data/lib/grape/dsl/request_response.rb +136 -139
- data/lib/grape/dsl/routing.rb +229 -201
- data/lib/grape/dsl/settings.rb +22 -134
- data/lib/grape/dsl/validations.rb +37 -45
- data/lib/grape/endpoint.rb +64 -96
- data/lib/grape/error_formatter/base.rb +2 -0
- data/lib/grape/exceptions/base.rb +1 -1
- data/lib/grape/exceptions/missing_group_type.rb +0 -2
- data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +5 -6
- data/lib/grape/middleware/error.rb +1 -11
- data/lib/grape/middleware/formatter.rb +4 -2
- data/lib/grape/middleware/stack.rb +2 -2
- data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
- data/lib/grape/middleware/versioner/base.rb +24 -42
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/middleware/versioner/param.rb +2 -2
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/namespace.rb +11 -0
- data/lib/grape/params_builder/base.rb +2 -0
- data/lib/grape/router.rb +4 -3
- 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 +38 -53
- 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 +0 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +7 -10
- metadata +24 -14
- data/lib/grape/api/helpers.rb +0 -9
- data/lib/grape/dsl/api.rb +0 -17
- data/lib/grape/dsl/configuration.rb +0 -15
- data/lib/grape/types/invalid_value.rb +0 -8
- data/lib/grape/util/strict_hash_configuration.rb +0 -108
- data/lib/grape/validations/attributes_doc.rb +0 -60
data/lib/grape/namespace.rb
CHANGED
|
@@ -28,6 +28,17 @@ module Grape
|
|
|
28
28
|
settings&.map(&:space)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def eql?(other)
|
|
32
|
+
other.class == self.class &&
|
|
33
|
+
other.space == space &&
|
|
34
|
+
other.options == options
|
|
35
|
+
end
|
|
36
|
+
alias == eql?
|
|
37
|
+
|
|
38
|
+
def hash
|
|
39
|
+
[self.class, space, options].hash
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
# Join the namespaces from a list of settings to create a path prefix.
|
|
32
43
|
# @param settings [Array] list of Grape::Util::InheritableSettings.
|
|
33
44
|
def self.joined_space_path(settings)
|
data/lib/grape/router.rb
CHANGED
|
@@ -12,14 +12,15 @@ module Grape
|
|
|
12
12
|
# normalize_path("/%ab") # => "/%AB"
|
|
13
13
|
# https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19
|
|
14
14
|
def self.normalize_path(path)
|
|
15
|
-
return
|
|
15
|
+
return '/' unless path
|
|
16
|
+
return path if path == '/'
|
|
16
17
|
|
|
17
18
|
# Fast path for the overwhelming majority of paths that don't need to be normalized
|
|
18
|
-
return path.dup if path
|
|
19
|
+
return path.dup if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))
|
|
19
20
|
|
|
20
21
|
# Slow path
|
|
21
22
|
encoding = path.encoding
|
|
22
|
-
path =
|
|
23
|
+
path = "/#{path}"
|
|
23
24
|
path.squeeze!('/')
|
|
24
25
|
|
|
25
26
|
unless path == '/'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grape
|
|
4
|
+
module Util
|
|
5
|
+
class ApiDescription
|
|
6
|
+
def initialize(description, endpoint_configuration, &block)
|
|
7
|
+
@endpoint_configuration = endpoint_configuration
|
|
8
|
+
@attributes = { description: description }
|
|
9
|
+
instance_eval(&block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
%i[
|
|
13
|
+
body_name
|
|
14
|
+
consumes
|
|
15
|
+
default
|
|
16
|
+
deprecated
|
|
17
|
+
detail
|
|
18
|
+
entity
|
|
19
|
+
headers
|
|
20
|
+
hidden
|
|
21
|
+
http_codes
|
|
22
|
+
is_array
|
|
23
|
+
named
|
|
24
|
+
nickname
|
|
25
|
+
params
|
|
26
|
+
produces
|
|
27
|
+
security
|
|
28
|
+
summary
|
|
29
|
+
tags
|
|
30
|
+
].each do |attribute|
|
|
31
|
+
define_method attribute do |value|
|
|
32
|
+
@attributes[attribute] = value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias success entity
|
|
37
|
+
alias failure http_codes
|
|
38
|
+
|
|
39
|
+
def configuration
|
|
40
|
+
@configuration ||= eval_endpoint_config(@endpoint_configuration)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def settings
|
|
44
|
+
@attributes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def eval_endpoint_config(configuration)
|
|
50
|
+
return configuration if configuration.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
configuration.evaluate
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -14,8 +14,11 @@ module Grape
|
|
|
14
14
|
@new_values = {}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def delete(
|
|
18
|
-
|
|
17
|
+
def delete(*keys)
|
|
18
|
+
keys.map do |key|
|
|
19
|
+
# since delete returns the deleted value, seems natural to `map` the result
|
|
20
|
+
new_values.delete key
|
|
21
|
+
end
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def initialize_copy(other)
|
|
@@ -95,6 +95,13 @@ module Grape
|
|
|
95
95
|
namespace_reverse_stackable: namespace_reverse_stackable.to_hash
|
|
96
96
|
}
|
|
97
97
|
end
|
|
98
|
+
|
|
99
|
+
def namespace_stackable_with_hash(key)
|
|
100
|
+
data = namespace_stackable[key]
|
|
101
|
+
return if data.blank?
|
|
102
|
+
|
|
103
|
+
data.each_with_object({}) { |value, result| result.deep_merge!(value) }
|
|
104
|
+
end
|
|
98
105
|
end
|
|
99
106
|
end
|
|
100
107
|
end
|
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
|
|
8
8
|
# based on the HTTP Accept header with the pattern:
|
|
9
9
|
# application/vnd.:vendor-:version+:format
|
|
10
|
-
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z
|
|
10
|
+
VENDOR_VERSION_HEADER_REGEX = /\Avnd\.(?<vendor>[a-z0-9.\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\+(?<format>[a-z0-9*\-.]+))?\z/
|
|
11
11
|
|
|
12
12
|
def initialize(type:, subtype:)
|
|
13
13
|
@type = type
|
data/lib/grape/util/registry.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Grape
|
|
|
7
7
|
short_name = build_short_name(klass)
|
|
8
8
|
return if short_name.nil?
|
|
9
9
|
|
|
10
|
-
warn "#{short_name} is already registered with class #{klass}" if registry.key?(short_name)
|
|
10
|
+
warn "#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}" if registry.key?(short_name)
|
|
11
11
|
registry[short_name] = klass
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -20,14 +20,14 @@ module Grape
|
|
|
20
20
|
key_map = contract.key_map
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
api.namespace_stackable
|
|
23
|
+
api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map
|
|
24
24
|
|
|
25
25
|
validator_options = {
|
|
26
26
|
validator_class: Grape::Validations.require_validator(:contract_scope),
|
|
27
27
|
opts: { schema: contract, fail_fast: false }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
api.namespace_stackable
|
|
30
|
+
api.inheritable_setting.namespace_stackable[:validations] = validator_options
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -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
|
|
@@ -7,6 +7,7 @@ module Grape
|
|
|
7
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,
|
|
@@ -210,28 +211,32 @@ module Grape
|
|
|
210
211
|
# @param new_name [String, Symbol] the new name of the parameter (the
|
|
211
212
|
# renamed name, with the +as: ...+ semantic)
|
|
212
213
|
def push_renamed_param(path, new_name)
|
|
213
|
-
|
|
214
|
+
api_route_setting = @api.inheritable_setting.route
|
|
215
|
+
base = api_route_setting[:renamed_params] || {}
|
|
214
216
|
base[Array(path).map(&:to_s)] = new_name.to_s
|
|
215
|
-
|
|
217
|
+
api_route_setting[:renamed_params] = base
|
|
216
218
|
end
|
|
217
219
|
|
|
218
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
|
+
|
|
219
224
|
if context == :all
|
|
220
|
-
optional_fields =
|
|
221
|
-
required_fields =
|
|
225
|
+
optional_fields = except_fields
|
|
226
|
+
required_fields = using_fields
|
|
222
227
|
else # context == :none
|
|
223
|
-
required_fields =
|
|
224
|
-
optional_fields =
|
|
228
|
+
required_fields = except_fields
|
|
229
|
+
optional_fields = using_fields
|
|
225
230
|
end
|
|
226
231
|
required_fields.each do |field|
|
|
227
232
|
field_opts = opts[:using][field]
|
|
228
233
|
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
|
229
234
|
|
|
230
|
-
requires(field, field_opts)
|
|
235
|
+
requires(field, **field_opts)
|
|
231
236
|
end
|
|
232
237
|
optional_fields.each do |field|
|
|
233
238
|
field_opts = opts[:using][field]
|
|
234
|
-
optional(field, field_opts) if field_opts
|
|
239
|
+
optional(field, **field_opts) if field_opts
|
|
235
240
|
end
|
|
236
241
|
end
|
|
237
242
|
|
|
@@ -243,7 +248,7 @@ module Grape
|
|
|
243
248
|
end
|
|
244
249
|
optional_fields.each do |field|
|
|
245
250
|
field_opts = opts[:using][field]
|
|
246
|
-
optional(field, field_opts) if field_opts
|
|
251
|
+
optional(field, **field_opts) if field_opts
|
|
247
252
|
end
|
|
248
253
|
end
|
|
249
254
|
|
|
@@ -260,9 +265,9 @@ module Grape
|
|
|
260
265
|
# @param optional [Boolean] whether the parameter this are nested under
|
|
261
266
|
# is optional or not (and hence, whether this block's params will be).
|
|
262
267
|
# @yield parameter scope
|
|
263
|
-
def new_scope(attrs, optional = false, &block)
|
|
268
|
+
def new_scope(attrs, opts, optional = false, &block)
|
|
264
269
|
# if required params are grouped and no type or unsupported type is provided, raise an error
|
|
265
|
-
type =
|
|
270
|
+
type = opts[:type]
|
|
266
271
|
if attrs.first && !optional
|
|
267
272
|
raise Grape::Exceptions::MissingGroupType if type.nil?
|
|
268
273
|
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
|
@@ -271,7 +276,7 @@ module Grape
|
|
|
271
276
|
self.class.new(
|
|
272
277
|
api: @api,
|
|
273
278
|
element: attrs.first,
|
|
274
|
-
element_renamed:
|
|
279
|
+
element_renamed: opts[:as],
|
|
275
280
|
parent: self,
|
|
276
281
|
optional: optional,
|
|
277
282
|
type: type || Array,
|
|
@@ -315,7 +320,7 @@ module Grape
|
|
|
315
320
|
if nested?
|
|
316
321
|
@parent.push_declared_params [element => @declared_params]
|
|
317
322
|
else
|
|
318
|
-
@api.namespace_stackable
|
|
323
|
+
@api.inheritable_setting.namespace_stackable[:declared_params] = @declared_params
|
|
319
324
|
end
|
|
320
325
|
|
|
321
326
|
# params were stored in settings, it can be cleaned from the params scope
|
|
@@ -323,56 +328,40 @@ module Grape
|
|
|
323
328
|
end
|
|
324
329
|
|
|
325
330
|
def validates(attrs, validations)
|
|
326
|
-
doc = AttributesDoc.new @api, self
|
|
327
|
-
doc.extract_details validations
|
|
328
|
-
|
|
329
331
|
coerce_type = infer_coercion(validations)
|
|
330
|
-
|
|
331
|
-
doc.type = coerce_type
|
|
332
|
-
|
|
332
|
+
required = validations.key?(:presence)
|
|
333
333
|
default = validations[:default]
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
values = values_hash[:value]
|
|
337
|
-
# NB: excepts is deprecated
|
|
338
|
-
excepts = values_hash[:except]
|
|
339
|
-
else
|
|
340
|
-
values = validations[:values]
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
doc.values = values
|
|
344
|
-
|
|
345
|
-
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]
|
|
346
336
|
|
|
347
337
|
# NB. values and excepts should be nil, Proc, Array, or Range.
|
|
348
338
|
# Specifically, values should NOT be a Hash
|
|
349
|
-
|
|
350
339
|
# use values or excepts to guess coerce type when stated type is Array
|
|
351
|
-
coerce_type = guess_coerce_type(coerce_type, values, except_values
|
|
340
|
+
coerce_type = guess_coerce_type(coerce_type, values, except_values)
|
|
352
341
|
|
|
353
342
|
# default value should be present in values array, if both exist and are not procs
|
|
354
|
-
check_incompatible_option_values(default, values, except_values
|
|
343
|
+
check_incompatible_option_values(default, values, except_values)
|
|
355
344
|
|
|
356
345
|
# type should be compatible with values array, if both exist
|
|
357
|
-
validate_value_coercion(coerce_type, values, except_values
|
|
346
|
+
validate_value_coercion(coerce_type, values, except_values)
|
|
358
347
|
|
|
359
|
-
|
|
348
|
+
document_params attrs, validations, coerce_type, values, except_values
|
|
360
349
|
|
|
361
350
|
opts = derive_validator_options(validations)
|
|
362
351
|
|
|
363
352
|
# Validate for presence before any other validators
|
|
364
|
-
validates_presence(validations, attrs,
|
|
353
|
+
validates_presence(validations, attrs, opts)
|
|
365
354
|
|
|
366
355
|
# Before we run the rest of the validators, let's handle
|
|
367
356
|
# whatever coercion so that we are working with correctly
|
|
368
357
|
# type casted values
|
|
369
|
-
coerce_type validations, attrs,
|
|
358
|
+
coerce_type validations, attrs, required, opts
|
|
370
359
|
|
|
371
360
|
validations.each do |type, options|
|
|
372
361
|
# Don't try to look up validators for documentation params that don't have one.
|
|
373
362
|
next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type)
|
|
374
363
|
|
|
375
|
-
validate(type, options, attrs,
|
|
364
|
+
validate(type, options, attrs, required, opts)
|
|
376
365
|
end
|
|
377
366
|
end
|
|
378
367
|
|
|
@@ -436,7 +425,7 @@ module Grape
|
|
|
436
425
|
# composited from more than one +requires+/+optional+
|
|
437
426
|
# parameter, and needs to be run before most other
|
|
438
427
|
# validations.
|
|
439
|
-
def coerce_type(validations, attrs,
|
|
428
|
+
def coerce_type(validations, attrs, required, opts)
|
|
440
429
|
check_coerce_with(validations)
|
|
441
430
|
|
|
442
431
|
return unless validations.key?(:coerce)
|
|
@@ -446,7 +435,7 @@ module Grape
|
|
|
446
435
|
method: validations[:coerce_with],
|
|
447
436
|
message: validations[:coerce_message]
|
|
448
437
|
}
|
|
449
|
-
validate('coerce', coerce_options, attrs,
|
|
438
|
+
validate('coerce', coerce_options, attrs, required, opts)
|
|
450
439
|
validations.delete(:coerce_with)
|
|
451
440
|
validations.delete(:coerce)
|
|
452
441
|
validations.delete(:coerce_message)
|
|
@@ -462,30 +451,26 @@ module Grape
|
|
|
462
451
|
coerce_type
|
|
463
452
|
end
|
|
464
453
|
|
|
465
|
-
def check_incompatible_option_values(default, values, except_values
|
|
454
|
+
def check_incompatible_option_values(default, values, except_values)
|
|
466
455
|
return unless default && !default.is_a?(Proc)
|
|
467
456
|
|
|
468
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) }
|
|
469
458
|
|
|
470
|
-
|
|
471
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
|
472
|
-
end
|
|
459
|
+
return unless except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
|
473
460
|
|
|
474
|
-
|
|
475
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, excepts) \
|
|
476
|
-
unless Array(default).none? { |def_val| excepts.include?(def_val) }
|
|
461
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
|
477
462
|
end
|
|
478
463
|
|
|
479
|
-
def validate(type, options, attrs,
|
|
464
|
+
def validate(type, options, attrs, required, opts)
|
|
480
465
|
validator_options = {
|
|
481
466
|
attributes: attrs,
|
|
482
467
|
options: options,
|
|
483
|
-
required:
|
|
468
|
+
required: required,
|
|
484
469
|
params_scope: self,
|
|
485
470
|
opts: opts,
|
|
486
471
|
validator_class: Validations.require_validator(type)
|
|
487
472
|
}
|
|
488
|
-
@api.namespace_stackable
|
|
473
|
+
@api.inheritable_setting.namespace_stackable[:validations] = validator_options
|
|
489
474
|
end
|
|
490
475
|
|
|
491
476
|
def validate_value_coercion(coerce_type, *values_list)
|
|
@@ -527,10 +512,10 @@ module Grape
|
|
|
527
512
|
}
|
|
528
513
|
end
|
|
529
514
|
|
|
530
|
-
def validates_presence(validations, attrs,
|
|
515
|
+
def validates_presence(validations, attrs, opts)
|
|
531
516
|
return unless validations.key?(:presence) && validations[:presence]
|
|
532
517
|
|
|
533
|
-
validate('presence', validations.delete(:presence), attrs,
|
|
518
|
+
validate('presence', validations.delete(:presence), attrs, true, opts)
|
|
534
519
|
validations.delete(:message) if validations.key?(:message)
|
|
535
520
|
end
|
|
536
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
|
|
@@ -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
|
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'
|
|
@@ -28,6 +27,7 @@ require 'active_support/deprecation'
|
|
|
28
27
|
require 'active_support/inflector'
|
|
29
28
|
require 'active_support/ordered_options'
|
|
30
29
|
require 'active_support/notifications'
|
|
30
|
+
require 'dry-configurable'
|
|
31
31
|
|
|
32
32
|
require 'English'
|
|
33
33
|
require 'bigdecimal'
|
|
@@ -57,7 +57,10 @@ loader.setup
|
|
|
57
57
|
I18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)
|
|
58
58
|
|
|
59
59
|
module Grape
|
|
60
|
-
|
|
60
|
+
extend Dry::Configurable
|
|
61
|
+
|
|
62
|
+
setting :param_builder, default: :hash_with_indifferent_access
|
|
63
|
+
setting :lint, default: false
|
|
61
64
|
|
|
62
65
|
HTTP_SUPPORTED_METHODS = [
|
|
63
66
|
Rack::GET,
|
|
@@ -72,12 +75,6 @@ module Grape
|
|
|
72
75
|
def self.deprecator
|
|
73
76
|
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')
|
|
74
77
|
end
|
|
75
|
-
|
|
76
|
-
configure do |config|
|
|
77
|
-
config.param_builder = :hash_with_indifferent_access
|
|
78
|
-
config.lint = false
|
|
79
|
-
config.compile_methods!
|
|
80
|
-
end
|
|
81
78
|
end
|
|
82
79
|
|
|
83
80
|
# https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html
|