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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CONTRIBUTING.md +1 -9
  4. data/README.md +72 -31
  5. data/UPGRADING.md +34 -0
  6. data/grape.gemspec +4 -4
  7. data/lib/grape/api/instance.rb +49 -72
  8. data/lib/grape/api.rb +24 -34
  9. data/lib/grape/dry_types.rb +48 -4
  10. data/lib/grape/dsl/callbacks.rb +8 -58
  11. data/lib/grape/dsl/desc.rb +8 -67
  12. data/lib/grape/dsl/helpers.rb +59 -64
  13. data/lib/grape/dsl/inside_route.rb +20 -43
  14. data/lib/grape/dsl/logger.rb +3 -6
  15. data/lib/grape/dsl/middleware.rb +22 -40
  16. data/lib/grape/dsl/parameters.rb +7 -16
  17. data/lib/grape/dsl/request_response.rb +136 -139
  18. data/lib/grape/dsl/routing.rb +229 -201
  19. data/lib/grape/dsl/settings.rb +22 -134
  20. data/lib/grape/dsl/validations.rb +37 -45
  21. data/lib/grape/endpoint.rb +64 -96
  22. data/lib/grape/error_formatter/base.rb +2 -0
  23. data/lib/grape/exceptions/base.rb +1 -1
  24. data/lib/grape/exceptions/missing_group_type.rb +0 -2
  25. data/lib/grape/exceptions/unsupported_group_type.rb +0 -2
  26. data/lib/grape/middleware/auth/dsl.rb +5 -6
  27. data/lib/grape/middleware/error.rb +1 -11
  28. data/lib/grape/middleware/formatter.rb +4 -2
  29. data/lib/grape/middleware/stack.rb +2 -2
  30. data/lib/grape/middleware/versioner/accept_version_header.rb +1 -1
  31. data/lib/grape/middleware/versioner/base.rb +24 -42
  32. data/lib/grape/middleware/versioner/header.rb +1 -1
  33. data/lib/grape/middleware/versioner/param.rb +2 -2
  34. data/lib/grape/middleware/versioner/path.rb +1 -1
  35. data/lib/grape/namespace.rb +11 -0
  36. data/lib/grape/params_builder/base.rb +2 -0
  37. data/lib/grape/router.rb +4 -3
  38. data/lib/grape/util/api_description.rb +56 -0
  39. data/lib/grape/util/base_inheritable.rb +5 -2
  40. data/lib/grape/util/inheritable_setting.rb +7 -0
  41. data/lib/grape/util/media_type.rb +1 -1
  42. data/lib/grape/util/registry.rb +1 -1
  43. data/lib/grape/validations/contract_scope.rb +2 -2
  44. data/lib/grape/validations/params_documentation.rb +50 -0
  45. data/lib/grape/validations/params_scope.rb +38 -53
  46. data/lib/grape/validations/types/array_coercer.rb +2 -3
  47. data/lib/grape/validations/types/dry_type_coercer.rb +4 -11
  48. data/lib/grape/validations/types/primitive_coercer.rb +1 -28
  49. data/lib/grape/validations/types.rb +10 -25
  50. data/lib/grape/validations/validators/base.rb +0 -7
  51. data/lib/grape/version.rb +1 -1
  52. data/lib/grape.rb +7 -10
  53. metadata +24 -14
  54. data/lib/grape/api/helpers.rb +0 -9
  55. data/lib/grape/dsl/api.rb +0 -17
  56. data/lib/grape/dsl/configuration.rb +0 -15
  57. data/lib/grape/types/invalid_value.rb +0 -8
  58. data/lib/grape/util/strict_hash_configuration.rb +0 -108
  59. data/lib/grape/validations/attributes_doc.rb +0 -60
@@ -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)
@@ -8,6 +8,8 @@ module Grape
8
8
  raise NotImplementedError
9
9
  end
10
10
 
11
+ private
12
+
11
13
  def inherited(klass)
12
14
  super
13
15
  ParamsBuilder.register(klass)
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 +'/' unless path
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 == '/' || (path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//})))
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 = +"/#{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(key)
18
- new_values.delete key
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/.freeze
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
@@ -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(:contract_key_map, key_map)
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(:validations, validator_options)
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
- base = @api.route_setting(:renamed_params) || {}
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
- @api.route_setting(:renamed_params, base)
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 = Array.wrap(opts[:except])
221
- required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) }
225
+ optional_fields = except_fields
226
+ required_fields = using_fields
222
227
  else # context == :none
223
- required_fields = Array.wrap(opts[:except])
224
- optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) }
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 = attrs[1] ? attrs[1][:type] : nil
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: attrs[1][:as],
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(:declared_params, @declared_params)
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
- if (values_hash = validations[:values]).is_a? Hash
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, excepts)
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, excepts)
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, excepts)
346
+ validate_value_coercion(coerce_type, values, except_values)
358
347
 
359
- doc.document attrs
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, doc, opts)
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, doc, opts
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, doc, opts)
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, doc, opts)
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, doc, opts)
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, excepts)
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
- if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
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
- return unless excepts && !excepts.is_a?(Proc)
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, doc, opts)
464
+ def validate(type, options, attrs, required, opts)
480
465
  validator_options = {
481
466
  attributes: attrs,
482
467
  options: options,
483
- required: doc.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(:validations, validator_options)
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, doc, opts)
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, doc, opts)
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/1.2/array-with-member/)
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/1.2/built-in-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
- @scope = strict ? DryTypes::Strict : DryTypes::Params
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 => _e
49
+ rescue Dry::Types::CoercionError
57
50
  InvalidValue.new
58
51
  end
59
52
 
60
53
  protected
61
54
 
62
- attr_reader :scope, :type, :strict
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
- @type = type
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
- cache_instance(type, method, strict) do
159
- create_coercer_instance(type, method, strict)
160
- end
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
- def cache_instance(type, method, strict, &_block)
188
- key = cache_key(type, method, strict)
189
-
190
- return @__cache[key] if @__cache.key?(key)
191
-
192
- instance = yield
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Grape
4
4
  # The current version of Grape.
5
- VERSION = '2.4.0'
5
+ VERSION = '3.0.0'
6
6
  end
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' if ActiveSupport::VERSION::MAJOR > 6
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
- include ActiveSupport::Configurable
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