grape 2.1.3 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +9 -7
  4. data/UPGRADING.md +27 -0
  5. data/grape.gemspec +7 -6
  6. data/lib/grape/api/instance.rb +22 -58
  7. data/lib/grape/api.rb +2 -11
  8. data/lib/grape/content_types.rb +13 -8
  9. data/lib/grape/dsl/desc.rb +27 -24
  10. data/lib/grape/dsl/helpers.rb +7 -3
  11. data/lib/grape/dsl/inside_route.rb +18 -24
  12. data/lib/grape/dsl/parameters.rb +2 -2
  13. data/lib/grape/dsl/request_response.rb +14 -18
  14. data/lib/grape/dsl/routing.rb +5 -12
  15. data/lib/grape/endpoint.rb +90 -82
  16. data/lib/grape/error_formatter/base.rb +51 -21
  17. data/lib/grape/error_formatter/json.rb +7 -15
  18. data/lib/grape/error_formatter/jsonapi.rb +7 -0
  19. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  20. data/lib/grape/error_formatter/txt.rb +13 -20
  21. data/lib/grape/error_formatter/xml.rb +3 -13
  22. data/lib/grape/error_formatter.rb +5 -25
  23. data/lib/grape/exceptions/base.rb +18 -30
  24. data/lib/grape/exceptions/validation.rb +5 -4
  25. data/lib/grape/exceptions/validation_errors.rb +2 -2
  26. data/lib/grape/formatter/base.rb +16 -0
  27. data/lib/grape/formatter/json.rb +4 -6
  28. data/lib/grape/formatter/serializable_hash.rb +1 -1
  29. data/lib/grape/formatter/txt.rb +3 -5
  30. data/lib/grape/formatter/xml.rb +4 -6
  31. data/lib/grape/formatter.rb +7 -25
  32. data/lib/grape/http/headers.rb +1 -0
  33. data/lib/grape/locale/en.yml +1 -0
  34. data/lib/grape/middleware/base.rb +14 -13
  35. data/lib/grape/middleware/error.rb +13 -9
  36. data/lib/grape/middleware/formatter.rb +3 -3
  37. data/lib/grape/middleware/versioner/accept_version_header.rb +7 -30
  38. data/lib/grape/middleware/versioner/base.rb +82 -0
  39. data/lib/grape/middleware/versioner/header.rb +89 -10
  40. data/lib/grape/middleware/versioner/param.rb +4 -22
  41. data/lib/grape/middleware/versioner/path.rb +10 -32
  42. data/lib/grape/middleware/versioner.rb +7 -14
  43. data/lib/grape/namespace.rb +1 -1
  44. data/lib/grape/parser/base.rb +16 -0
  45. data/lib/grape/parser/json.rb +6 -8
  46. data/lib/grape/parser/jsonapi.rb +7 -0
  47. data/lib/grape/parser/xml.rb +6 -8
  48. data/lib/grape/parser.rb +5 -23
  49. data/lib/grape/path.rb +39 -56
  50. data/lib/grape/request.rb +2 -2
  51. data/lib/grape/router/base_route.rb +2 -2
  52. data/lib/grape/router/greedy_route.rb +2 -2
  53. data/lib/grape/router/pattern.rb +23 -18
  54. data/lib/grape/router/route.rb +13 -5
  55. data/lib/grape/router.rb +5 -5
  56. data/lib/grape/util/registry.rb +27 -0
  57. data/lib/grape/validations/contract_scope.rb +2 -39
  58. data/lib/grape/validations/params_scope.rb +7 -11
  59. data/lib/grape/validations/types/dry_type_coercer.rb +10 -6
  60. data/lib/grape/validations/validator_factory.rb +2 -2
  61. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  62. data/lib/grape/validations/validators/base.rb +5 -9
  63. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  64. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  65. data/lib/grape/validations/validators/default_validator.rb +1 -1
  66. data/lib/grape/validations/validators/except_values_validator.rb +1 -1
  67. data/lib/grape/validations/validators/length_validator.rb +11 -4
  68. data/lib/grape/validations/validators/regexp_validator.rb +1 -1
  69. data/lib/grape/validations/validators/values_validator.rb +15 -57
  70. data/lib/grape/validations.rb +8 -17
  71. data/lib/grape/version.rb +1 -1
  72. data/lib/grape.rb +1 -1
  73. metadata +15 -12
  74. data/lib/grape/util/accept_header_handler.rb +0 -105
  75. data/lib/grape/util/registrable.rb +0 -15
  76. data/lib/grape/validations/types/build_coercer.rb +0 -92
@@ -4,7 +4,7 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class DefaultValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts = {})
8
8
  @default = options
9
9
  super
10
10
  end
@@ -4,7 +4,7 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class ExceptValuesValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts)
8
8
  @except = options.is_a?(Hash) ? options[:value] : options
9
9
  super
10
10
  end
@@ -4,23 +4,28 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class LengthValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
7
+ def initialize(attrs, options, required, scope, opts)
8
8
  @min = options[:min]
9
9
  @max = options[:max]
10
+ @is = options[:is]
10
11
 
11
12
  super
12
13
 
13
14
  raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)
14
15
  raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)
15
16
  raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if !@min.nil? && !@max.nil? && @min > @max
17
+
18
+ return if @is.nil?
19
+ raise ArgumentError, 'is must be an integer greater than zero' if !@is.is_a?(Integer) || !@is.positive?
20
+ raise ArgumentError, 'is cannot be combined with min or max' if !@min.nil? || !@max.nil?
16
21
  end
17
22
 
18
23
  def validate_param!(attr_name, params)
19
24
  param = params[attr_name]
20
25
 
21
- raise ArgumentError, "parameter #{param} does not support #length" unless param.respond_to?(:length)
26
+ return unless param.respond_to?(:length)
22
27
 
23
- return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max)
28
+ return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)
24
29
 
25
30
  raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)
26
31
  end
@@ -32,8 +37,10 @@ module Grape
32
37
  format I18n.t(:length, scope: 'grape.errors.messages'), min: @min, max: @max
33
38
  elsif @min
34
39
  format I18n.t(:length_min, scope: 'grape.errors.messages'), min: @min
35
- else
40
+ elsif @max
36
41
  format I18n.t(:length_max, scope: 'grape.errors.messages'), max: @max
42
+ else
43
+ format I18n.t(:length_is, scope: 'grape.errors.messages'), is: @is
37
44
  end
38
45
  end
39
46
  end
@@ -6,7 +6,7 @@ module Grape
6
6
  class RegexpValidator < Base
7
7
  def validate_param!(attr_name, params)
8
8
  return unless params.respond_to?(:key?) && params.key?(attr_name)
9
- return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) }
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))
12
12
  end
@@ -4,23 +4,8 @@ module Grape
4
4
  module Validations
5
5
  module Validators
6
6
  class ValuesValidator < Base
7
- def initialize(attrs, options, required, scope, **opts)
8
- if options.is_a?(Hash)
9
- @excepts = options[:except]
10
- @values = options[:value]
11
- @proc = options[:proc]
12
-
13
- Grape.deprecator.warn('The values validator except option is deprecated. Use the except validator instead.') if @excepts
14
-
15
- raise ArgumentError, 'proc must be a Proc' if @proc && !@proc.is_a?(Proc)
16
-
17
- Grape.deprecator.warn('The values validator proc option is deprecated. The lambda expression can now be assigned directly to values.') if @proc
18
- else
19
- @excepts = nil
20
- @values = nil
21
- @proc = nil
22
- @values = options
23
- end
7
+ def initialize(attrs, options, required, scope, opts)
8
+ @values = options.is_a?(Hash) ? options[:value] : options
24
9
  super
25
10
  end
26
11
 
@@ -31,57 +16,34 @@ module Grape
31
16
 
32
17
  return if val.nil? && !required_for_root_scope?
33
18
 
19
+ val = val.scrub if val.respond_to?(:scrub)
20
+
34
21
  # don't forget that +false.blank?+ is true
35
22
  return if val != false && val.blank? && @allow_blank
36
23
 
37
- param_array = val.nil? ? [nil] : Array.wrap(val)
38
-
39
- raise validation_exception(attr_name, except_message) \
40
- unless check_excepts(param_array)
41
-
42
- raise validation_exception(attr_name, message(:values)) \
43
- unless check_values(param_array, attr_name)
24
+ return if check_values?(val, attr_name)
44
25
 
45
- raise validation_exception(attr_name, message(:values)) \
46
- if @proc && !validate_proc(@proc, param_array)
26
+ raise Grape::Exceptions::Validation.new(
27
+ params: [@scope.full_name(attr_name)],
28
+ message: message(:values)
29
+ )
47
30
  end
48
31
 
49
32
  private
50
33
 
51
- def check_values(param_array, attr_name)
34
+ def check_values?(val, attr_name)
52
35
  values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values
53
36
  return true if values.nil?
54
37
 
38
+ param_array = val.nil? ? [nil] : Array.wrap(val)
39
+ return param_array.all? { |param| values.include?(param) } unless values.is_a?(Proc)
40
+
55
41
  begin
56
- return param_array.all? { |param| values.call(param) } if values.is_a? Proc
42
+ param_array.all? { |param| values.call(param) }
57
43
  rescue StandardError => e
58
44
  warn "Error '#{e}' raised while validating attribute '#{attr_name}'"
59
- return false
45
+ false
60
46
  end
61
- param_array.all? { |param| values.include?(param) }
62
- end
63
-
64
- def check_excepts(param_array)
65
- excepts = @excepts.is_a?(Proc) ? @excepts.call : @excepts
66
- return true if excepts.nil?
67
-
68
- param_array.none? { |param| excepts.include?(param) }
69
- end
70
-
71
- def validate_proc(proc, param_array)
72
- case proc.arity
73
- when 0
74
- param_array.all? { |_param| proc.call }
75
- when 1
76
- param_array.all? { |param| proc.call(param) }
77
- else
78
- raise ArgumentError, 'proc arity must be 0 or 1'
79
- end
80
- end
81
-
82
- def except_message
83
- options = instance_variable_get(:@option)
84
- options_key?(:except_message) ? options[:except_message] : message(:except_values)
85
47
  end
86
48
 
87
49
  def required_for_root_scope?
@@ -92,10 +54,6 @@ module Grape
92
54
 
93
55
  scope.root?
94
56
  end
95
-
96
- def validation_exception(attr_name, message)
97
- Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message)
98
- end
99
57
  end
100
58
  end
101
59
  end
@@ -2,29 +2,20 @@
2
2
 
3
3
  module Grape
4
4
  module Validations
5
+ extend Grape::Util::Registry
6
+
5
7
  module_function
6
8
 
7
- def validators
8
- @validators ||= {}
9
- end
9
+ def require_validator(short_name)
10
+ raise Grape::Exceptions::UnknownValidator, short_name unless registry.key?(short_name)
10
11
 
11
- # Register a new validator, so it can be used to validate parameters.
12
- # @param short_name [String] all lower-case, no spaces
13
- # @param klass [Class] the validator class. Should inherit from
14
- # Grape::Validations::Validators::Base.
15
- def register_validator(short_name, klass)
16
- validators[short_name] = klass
12
+ registry[short_name]
17
13
  end
18
14
 
19
- def deregister_validator(short_name)
20
- validators.delete(short_name)
21
- end
15
+ def build_short_name(klass)
16
+ return if klass.name.blank?
22
17
 
23
- def require_validator(short_name)
24
- str_name = short_name.to_s
25
- validators.fetch(str_name) { Grape::Validations::Validators.const_get(:"#{str_name.camelize}Validator") }
26
- rescue NameError
27
- raise Grape::Exceptions::UnknownValidator, short_name
18
+ klass.name.demodulize.underscore.delete_suffix('_validator')
28
19
  end
29
20
  end
30
21
  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.1.3'
5
+ VERSION = '2.3.0'
6
6
  end
data/lib/grape.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'active_support'
4
5
  require 'active_support/concern'
5
6
  require 'active_support/configurable'
@@ -33,7 +34,6 @@ require 'date'
33
34
  require 'dry-types'
34
35
  require 'forwardable'
35
36
  require 'json'
36
- require 'logger'
37
37
  require 'mustermann/grape'
38
38
  require 'pathname'
39
39
  require 'rack'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-07-13 00:00:00.000000000 Z
10
+ date: 2025-02-08 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -120,6 +119,8 @@ files:
120
119
  - lib/grape/error_formatter.rb
121
120
  - lib/grape/error_formatter/base.rb
122
121
  - lib/grape/error_formatter/json.rb
122
+ - lib/grape/error_formatter/jsonapi.rb
123
+ - lib/grape/error_formatter/serializable_hash.rb
123
124
  - lib/grape/error_formatter/txt.rb
124
125
  - lib/grape/error_formatter/xml.rb
125
126
  - lib/grape/exceptions/base.rb
@@ -149,6 +150,7 @@ files:
149
150
  - lib/grape/extensions/hash.rb
150
151
  - lib/grape/extensions/hashie/mash.rb
151
152
  - lib/grape/formatter.rb
153
+ - lib/grape/formatter/base.rb
152
154
  - lib/grape/formatter/json.rb
153
155
  - lib/grape/formatter/serializable_hash.rb
154
156
  - lib/grape/formatter/txt.rb
@@ -169,12 +171,15 @@ files:
169
171
  - lib/grape/middleware/stack.rb
170
172
  - lib/grape/middleware/versioner.rb
171
173
  - lib/grape/middleware/versioner/accept_version_header.rb
174
+ - lib/grape/middleware/versioner/base.rb
172
175
  - lib/grape/middleware/versioner/header.rb
173
176
  - lib/grape/middleware/versioner/param.rb
174
177
  - lib/grape/middleware/versioner/path.rb
175
178
  - lib/grape/namespace.rb
176
179
  - lib/grape/parser.rb
180
+ - lib/grape/parser/base.rb
177
181
  - lib/grape/parser/json.rb
182
+ - lib/grape/parser/jsonapi.rb
178
183
  - lib/grape/parser/xml.rb
179
184
  - lib/grape/path.rb
180
185
  - lib/grape/presenters/presenter.rb
@@ -189,7 +194,6 @@ files:
189
194
  - lib/grape/serve_stream/sendfile_response.rb
190
195
  - lib/grape/serve_stream/stream_response.rb
191
196
  - lib/grape/types/invalid_value.rb
192
- - lib/grape/util/accept_header_handler.rb
193
197
  - lib/grape/util/base_inheritable.rb
194
198
  - lib/grape/util/cache.rb
195
199
  - lib/grape/util/endpoint_configuration.rb
@@ -203,7 +207,7 @@ files:
203
207
  - lib/grape/util/lazy/value_enumerable.rb
204
208
  - lib/grape/util/lazy/value_hash.rb
205
209
  - lib/grape/util/media_type.rb
206
- - lib/grape/util/registrable.rb
210
+ - lib/grape/util/registry.rb
207
211
  - lib/grape/util/reverse_stackable_values.rb
208
212
  - lib/grape/util/stackable_values.rb
209
213
  - lib/grape/util/strict_hash_configuration.rb
@@ -216,7 +220,6 @@ files:
216
220
  - lib/grape/validations/single_attribute_iterator.rb
217
221
  - lib/grape/validations/types.rb
218
222
  - lib/grape/validations/types/array_coercer.rb
219
- - lib/grape/validations/types/build_coercer.rb
220
223
  - lib/grape/validations/types/custom_type_coercer.rb
221
224
  - lib/grape/validations/types/custom_type_collection_coercer.rb
222
225
  - lib/grape/validations/types/dry_type_coercer.rb
@@ -234,6 +237,7 @@ files:
234
237
  - lib/grape/validations/validators/at_least_one_of_validator.rb
235
238
  - lib/grape/validations/validators/base.rb
236
239
  - lib/grape/validations/validators/coerce_validator.rb
240
+ - lib/grape/validations/validators/contract_scope_validator.rb
237
241
  - lib/grape/validations/validators/default_validator.rb
238
242
  - lib/grape/validations/validators/exactly_one_of_validator.rb
239
243
  - lib/grape/validations/validators/except_values_validator.rb
@@ -251,10 +255,10 @@ licenses:
251
255
  - MIT
252
256
  metadata:
253
257
  bug_tracker_uri: https://github.com/ruby-grape/grape/issues
254
- changelog_uri: https://github.com/ruby-grape/grape/blob/v2.1.3/CHANGELOG.md
255
- documentation_uri: https://www.rubydoc.info/gems/grape/2.1.3
256
- source_code_uri: https://github.com/ruby-grape/grape/tree/v2.1.3
257
- post_install_message:
258
+ changelog_uri: https://github.com/ruby-grape/grape/blob/v2.3.0/CHANGELOG.md
259
+ documentation_uri: https://www.rubydoc.info/gems/grape/2.3.0
260
+ source_code_uri: https://github.com/ruby-grape/grape/tree/v2.3.0
261
+ rubygems_mfa_required: 'true'
258
262
  rdoc_options: []
259
263
  require_paths:
260
264
  - lib
@@ -269,8 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
273
  - !ruby/object:Gem::Version
270
274
  version: '0'
271
275
  requirements: []
272
- rubygems_version: 3.5.13
273
- signing_key:
276
+ rubygems_version: 3.6.2
274
277
  specification_version: 4
275
278
  summary: A simple Ruby framework for building REST-like APIs.
276
279
  test_files: []
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Util
5
- class AcceptHeaderHandler
6
- attr_reader :accept_header, :versions, :vendor, :strict, :cascade
7
-
8
- def initialize(accept_header:, versions:, **options)
9
- @accept_header = accept_header
10
- @versions = versions
11
- @vendor = options.fetch(:vendor, nil)
12
- @strict = options.fetch(:strict, false)
13
- @cascade = options.fetch(:cascade, true)
14
- end
15
-
16
- def match_best_quality_media_type!(content_types: Grape::ContentTypes::CONTENT_TYPES, allowed_methods: nil)
17
- return unless vendor
18
-
19
- strict_header_checks!
20
- media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types(content_types))
21
- if media_type
22
- yield media_type
23
- else
24
- fail!(allowed_methods)
25
- end
26
- end
27
-
28
- private
29
-
30
- def strict_header_checks!
31
- return unless strict
32
-
33
- accept_header_check!
34
- version_and_vendor_check!
35
- end
36
-
37
- def accept_header_check!
38
- return if accept_header.present?
39
-
40
- invalid_accept_header!('Accept header must be set.')
41
- end
42
-
43
- def version_and_vendor_check!
44
- return if versions.blank? || version_and_vendor?
45
-
46
- invalid_accept_header!('API vendor or version not found.')
47
- end
48
-
49
- def q_values_mime_types
50
- @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)
51
- end
52
-
53
- def version_and_vendor?
54
- q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }
55
- end
56
-
57
- def invalid_accept_header!(message)
58
- raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)
59
- end
60
-
61
- def invalid_version_header!(message)
62
- raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)
63
- end
64
-
65
- def fail!(grape_allowed_methods)
66
- return grape_allowed_methods if grape_allowed_methods.present?
67
-
68
- media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }
69
- vendor_not_found!(media_types) || version_not_found!(media_types)
70
- end
71
-
72
- def vendor_not_found!(media_types)
73
- return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }
74
-
75
- invalid_accept_header!('API vendor not found.')
76
- end
77
-
78
- def version_not_found!(media_types)
79
- return unless media_types.all? { |media_type| media_type&.version && versions.exclude?(media_type.version) }
80
-
81
- invalid_version_header!('API version not found.')
82
- end
83
-
84
- def error_headers
85
- cascade ? { Grape::Http::Headers::X_CASCADE => 'pass' } : {}
86
- end
87
-
88
- def available_media_types(content_types)
89
- [].tap do |available_media_types|
90
- base_media_type = "application/vnd.#{vendor}"
91
- content_types.each_key do |extension|
92
- versions&.reverse_each do |version|
93
- available_media_types << "#{base_media_type}-#{version}+#{extension}"
94
- available_media_types << "#{base_media_type}-#{version}"
95
- end
96
- available_media_types << "#{base_media_type}+#{extension}"
97
- end
98
-
99
- available_media_types << base_media_type
100
- available_media_types.concat(content_types.values.flatten)
101
- end
102
- end
103
- end
104
- end
105
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Util
5
- module Registrable
6
- def default_elements
7
- @default_elements ||= {}
8
- end
9
-
10
- def register(format, element)
11
- default_elements[format] = element unless default_elements[format]
12
- end
13
- end
14
- end
15
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grape
4
- module Validations
5
- module Types
6
- module BuildCoercer
7
- # Chooses the best coercer for the given type. For example, if the type
8
- # is Integer, it will return a coercer which will be able to coerce a value
9
- # to the integer.
10
- #
11
- # There are a few very special coercers which might be returned.
12
- #
13
- # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
14
- # the given type implies values in an array with different types.
15
- # For example, +[Integer, String]+ allows integer and string values in
16
- # an array.
17
- #
18
- # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
19
- # a method is specified by a user with +coerce_with+ option or the user
20
- # specifies a custom type which implements requirments of
21
- # +Grape::Types::CustomTypeCoercer+.
22
- #
23
- # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
24
- # previous one, but it expects an array or set of values having a custom
25
- # type implemented by the user.
26
- #
27
- # There is also a group of custom types implemented by Grape, check
28
- # +Grape::Validations::Types::SPECIAL+ to get the full list.
29
- #
30
- # @param type [Class] the type to which input strings
31
- # should be coerced
32
- # @param method [Class,#call] the coercion method to use
33
- # @return [Object] object to be used
34
- # for coercion and type validation
35
- def self.build_coercer(type, method: nil, strict: false)
36
- cache_instance(type, method, strict) do
37
- create_coercer_instance(type, method, strict)
38
- end
39
- end
40
-
41
- def self.create_coercer_instance(type, method, strict)
42
- # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
43
- type = Types.map_special(type)
44
-
45
- # Use a special coercer for multiply-typed parameters.
46
- if Types.multiple?(type)
47
- MultipleTypeCoercer.new(type, method)
48
-
49
- # Use a special coercer for custom types and coercion methods.
50
- elsif method || Types.custom?(type)
51
- CustomTypeCoercer.new(type, method)
52
-
53
- # Special coercer for collections of types that implement a parse method.
54
- # CustomTypeCoercer (above) already handles such types when an explicit coercion
55
- # method is supplied.
56
- elsif Types.collection_of_custom?(type)
57
- Types::CustomTypeCollectionCoercer.new(
58
- Types.map_special(type.first), type.is_a?(Set)
59
- )
60
- else
61
- DryTypeCoercer.coercer_instance_for(type, strict)
62
- end
63
- end
64
-
65
- def self.cache_instance(type, method, strict, &_block)
66
- key = cache_key(type, method, strict)
67
-
68
- return @__cache[key] if @__cache.key?(key)
69
-
70
- instance = yield
71
-
72
- @__cache_write_lock.synchronize do
73
- @__cache[key] = instance
74
- end
75
-
76
- instance
77
- end
78
-
79
- def self.cache_key(type, method, strict)
80
- [type, method, strict].each_with_object(+'_') do |val, memo|
81
- next if val.nil?
82
-
83
- memo << '_' << val.to_s
84
- end
85
- end
86
-
87
- instance_variable_set(:@__cache, {})
88
- instance_variable_set(:@__cache_write_lock, Mutex.new)
89
- end
90
- end
91
- end
92
- end