grape 2.0.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +96 -1
- data/README.md +364 -317
- data/UPGRADING.md +205 -7
- data/grape.gemspec +7 -7
- data/lib/grape/api/instance.rb +14 -11
- data/lib/grape/api.rb +19 -10
- data/lib/grape/content_types.rb +13 -10
- data/lib/grape/cookies.rb +2 -1
- data/lib/grape/dry_types.rb +0 -2
- data/lib/grape/dsl/desc.rb +22 -20
- data/lib/grape/dsl/headers.rb +1 -1
- data/lib/grape/dsl/helpers.rb +7 -3
- data/lib/grape/dsl/inside_route.rb +51 -15
- data/lib/grape/dsl/parameters.rb +5 -4
- data/lib/grape/dsl/request_response.rb +14 -18
- data/lib/grape/dsl/routing.rb +20 -4
- data/lib/grape/dsl/validations.rb +13 -0
- data/lib/grape/endpoint.rb +43 -35
- data/lib/grape/{util/env.rb → env.rb} +0 -5
- data/lib/grape/error_formatter/json.rb +13 -4
- data/lib/grape/error_formatter/txt.rb +11 -10
- data/lib/grape/error_formatter.rb +13 -25
- data/lib/grape/exceptions/base.rb +3 -3
- data/lib/grape/exceptions/validation.rb +0 -2
- data/lib/grape/exceptions/validation_array_errors.rb +1 -0
- data/lib/grape/exceptions/validation_errors.rb +2 -4
- data/lib/grape/extensions/hash.rb +5 -1
- data/lib/grape/formatter.rb +15 -25
- data/lib/grape/http/headers.rb +18 -34
- data/lib/grape/{util/json.rb → json.rb} +1 -3
- data/lib/grape/locale/en.yml +4 -0
- data/lib/grape/middleware/auth/base.rb +0 -2
- data/lib/grape/middleware/auth/dsl.rb +0 -2
- data/lib/grape/middleware/base.rb +14 -15
- data/lib/grape/middleware/error.rb +61 -54
- data/lib/grape/middleware/formatter.rb +18 -15
- data/lib/grape/middleware/globals.rb +1 -3
- data/lib/grape/middleware/stack.rb +4 -5
- data/lib/grape/middleware/versioner/accept_version_header.rb +8 -33
- data/lib/grape/middleware/versioner/header.rb +62 -123
- data/lib/grape/middleware/versioner/param.rb +5 -23
- data/lib/grape/middleware/versioner/path.rb +11 -33
- data/lib/grape/middleware/versioner.rb +5 -14
- data/lib/grape/middleware/versioner_helpers.rb +75 -0
- data/lib/grape/namespace.rb +3 -4
- data/lib/grape/parser.rb +8 -24
- data/lib/grape/path.rb +24 -29
- data/lib/grape/request.rb +4 -12
- data/lib/grape/router/base_route.rb +39 -0
- data/lib/grape/router/greedy_route.rb +20 -0
- data/lib/grape/router/pattern.rb +39 -30
- data/lib/grape/router/route.rb +22 -59
- data/lib/grape/router.rb +32 -37
- data/lib/grape/util/base_inheritable.rb +4 -4
- data/lib/grape/util/cache.rb +0 -3
- data/lib/grape/util/endpoint_configuration.rb +1 -1
- data/lib/grape/util/header.rb +13 -0
- data/lib/grape/util/inheritable_values.rb +0 -2
- data/lib/grape/util/lazy/block.rb +29 -0
- data/lib/grape/util/lazy/object.rb +45 -0
- data/lib/grape/util/lazy/value.rb +38 -0
- data/lib/grape/util/lazy/value_array.rb +21 -0
- data/lib/grape/util/lazy/value_enumerable.rb +34 -0
- data/lib/grape/util/lazy/value_hash.rb +21 -0
- data/lib/grape/util/media_type.rb +70 -0
- data/lib/grape/util/reverse_stackable_values.rb +1 -6
- data/lib/grape/util/stackable_values.rb +1 -6
- data/lib/grape/util/strict_hash_configuration.rb +3 -3
- data/lib/grape/validations/attributes_doc.rb +38 -36
- data/lib/grape/validations/attributes_iterator.rb +1 -0
- data/lib/grape/validations/contract_scope.rb +71 -0
- data/lib/grape/validations/params_scope.rb +22 -19
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/build_coercer.rb +69 -71
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -11
- data/lib/grape/validations/types/json.rb +0 -2
- data/lib/grape/validations/types/primitive_coercer.rb +0 -2
- data/lib/grape/validations/types/set_coercer.rb +0 -3
- data/lib/grape/validations/types.rb +0 -3
- data/lib/grape/validations/validators/base.rb +1 -0
- data/lib/grape/validations/validators/default_validator.rb +5 -1
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
- data/lib/grape/validations/validators/length_validator.rb +49 -0
- data/lib/grape/validations/validators/values_validator.rb +6 -1
- data/lib/grape/validations.rb +3 -7
- data/lib/grape/version.rb +1 -1
- data/lib/grape/{util/xml.rb → xml.rb} +1 -1
- data/lib/grape.rb +30 -274
- metadata +31 -38
- data/lib/grape/eager_load.rb +0 -20
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
- data/lib/grape/router/attribute_translator.rb +0 -63
- data/lib/grape/util/lazy_block.rb +0 -27
- data/lib/grape/util/lazy_object.rb +0 -43
- data/lib/grape/util/lazy_value.rb +0 -91
- data/lib/grape/util/registrable.rb +0 -15
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Util
|
5
|
+
class MediaType
|
6
|
+
attr_reader :type, :subtype, :vendor, :version, :format
|
7
|
+
|
8
|
+
# based on the HTTP Accept header with the pattern:
|
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
|
11
|
+
|
12
|
+
def initialize(type:, subtype:)
|
13
|
+
@type = type
|
14
|
+
@subtype = subtype
|
15
|
+
VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|
|
16
|
+
@vendor = m[:vendor]
|
17
|
+
@version = m[:version]
|
18
|
+
@format = m[:format]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
eql?(other)
|
24
|
+
end
|
25
|
+
|
26
|
+
def eql?(other)
|
27
|
+
self.class == other.class &&
|
28
|
+
other.type == type &&
|
29
|
+
other.subtype == subtype &&
|
30
|
+
other.vendor == vendor &&
|
31
|
+
other.version == version &&
|
32
|
+
other.format == format
|
33
|
+
end
|
34
|
+
|
35
|
+
def hash
|
36
|
+
[self.class, type, subtype, vendor, version, format].hash
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def best_quality(header, available_media_types)
|
41
|
+
parse(best_quality_media_type(header, available_media_types))
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse(media_type)
|
45
|
+
return if media_type.blank?
|
46
|
+
|
47
|
+
type, subtype = media_type.split('/', 2)
|
48
|
+
return if type.blank? || subtype.blank?
|
49
|
+
|
50
|
+
new(type: type, subtype: subtype)
|
51
|
+
end
|
52
|
+
|
53
|
+
def match?(media_type)
|
54
|
+
return false if media_type.blank?
|
55
|
+
|
56
|
+
subtype = media_type.split('/', 2).last
|
57
|
+
return false if subtype.blank?
|
58
|
+
|
59
|
+
VENDOR_VERSION_HEADER_REGEX.match?(subtype)
|
60
|
+
end
|
61
|
+
|
62
|
+
def best_quality_media_type(header, available_media_types)
|
63
|
+
header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private_class_method :best_quality_media_type
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'stackable_values'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Util
|
7
5
|
class ReverseStackableValues < StackableValues
|
@@ -10,10 +8,7 @@ module Grape
|
|
10
8
|
def concat_values(inherited_value, new_value)
|
11
9
|
return inherited_value unless new_value
|
12
10
|
|
13
|
-
|
14
|
-
value.concat(new_value)
|
15
|
-
value.concat(inherited_value)
|
16
|
-
end
|
11
|
+
new_value + inherited_value
|
17
12
|
end
|
18
13
|
end
|
19
14
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'base_inheritable'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Util
|
7
5
|
class StackableValues < BaseInheritable
|
@@ -31,10 +29,7 @@ module Grape
|
|
31
29
|
def concat_values(inherited_value, new_value)
|
32
30
|
return inherited_value unless new_value
|
33
31
|
|
34
|
-
|
35
|
-
value.concat(inherited_value)
|
36
|
-
value.concat(new_value)
|
37
|
-
end
|
32
|
+
inherited_value + new_value
|
38
33
|
end
|
39
34
|
end
|
40
35
|
end
|
@@ -56,19 +56,19 @@ module Grape
|
|
56
56
|
def self.nested_settings_methods(setting_name, new_config_class)
|
57
57
|
new_config_class.class_eval do
|
58
58
|
setting_name.each_pair do |key, value|
|
59
|
-
define_method "#{key}_context" do
|
59
|
+
define_method :"#{key}_context" do
|
60
60
|
@contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
|
61
61
|
end
|
62
62
|
|
63
63
|
define_method key do |&block|
|
64
|
-
send("#{key}_context").instance_exec(&block)
|
64
|
+
send(:"#{key}_context").instance_exec(&block)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
define_method :to_hash do
|
69
69
|
@settings.to_hash.merge(
|
70
70
|
setting_name.each_key.with_object({}) do |k, merge_hash|
|
71
|
-
merge_hash[k] = send("#{k}_context").to_hash
|
71
|
+
merge_hash[k] = send(:"#{k}_context").to_hash
|
72
72
|
end
|
73
73
|
)
|
74
74
|
end
|
@@ -2,56 +2,58 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Validations
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
+
class AttributesDoc
|
9
|
+
attr_accessor :type, :values
|
10
|
+
|
11
|
+
# @param api [Grape::API::Instance]
|
12
|
+
# @param scope [Validations::ParamsScope]
|
13
|
+
def initialize(api, scope)
|
14
|
+
@api = api
|
15
|
+
@scope = scope
|
16
|
+
@type = type
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
def extract_details(validations)
|
20
|
+
details[:required] = validations.key?(:presence)
|
21
21
|
|
22
|
-
|
22
|
+
desc = validations.delete(:desc) || validations.delete(:description)
|
23
23
|
|
24
|
-
|
24
|
+
details[:desc] = desc if desc
|
25
25
|
|
26
|
-
|
26
|
+
documentation = validations.delete(:documentation)
|
27
27
|
|
28
|
-
|
28
|
+
details[:documentation] = documentation if documentation
|
29
29
|
|
30
|
-
|
31
|
-
end
|
30
|
+
details[:default] = validations[:default] if validations.key?(:default)
|
32
31
|
|
33
|
-
|
34
|
-
|
32
|
+
details[:min_length] = validations[:length][:min] if validations.key?(:length) && validations[:length].key?(:min)
|
33
|
+
details[:max_length] = validations[:length][:max] if validations.key?(:length) && validations[:length].key?(:max)
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
def document(attrs)
|
37
|
+
return if @api.namespace_inheritable(:do_not_document)
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
end
|
39
|
+
details[:type] = type.to_s if type
|
40
|
+
details[:values] = values if values
|
42
41
|
|
43
|
-
|
42
|
+
documented_attrs = attrs.each_with_object({}) do |name, memo|
|
43
|
+
memo[@scope.full_name(name)] = details
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
@api.namespace_stackable(:params, documented_attrs)
|
47
|
+
end
|
48
|
+
|
49
|
+
def required
|
50
|
+
details[:required]
|
51
|
+
end
|
49
52
|
|
50
|
-
|
53
|
+
protected
|
51
54
|
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
+
def details
|
56
|
+
@details ||= {}
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
@@ -21,6 +21,7 @@ module Grape
|
|
21
21
|
private
|
22
22
|
|
23
23
|
def do_each(params_to_process, parent_indicies = [], &block)
|
24
|
+
@scope.reset_index # gets updated depending on the size of params_to_process
|
24
25
|
params_to_process.each_with_index do |resource_params, index|
|
25
26
|
# when we get arrays of arrays it means that target element located inside array
|
26
27
|
# we need this because we want to know parent arrays indicies
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
class ContractScope
|
6
|
+
# Declare the contract to be used for the endpoint's parameters.
|
7
|
+
# @param api [API] the API endpoint to modify.
|
8
|
+
# @param contract the contract or schema to be used for validation. Optional.
|
9
|
+
# @yield a block yielding a new schema class. Optional.
|
10
|
+
def initialize(api, contract = nil, &block)
|
11
|
+
# When block is passed, the first arg is either schema or nil.
|
12
|
+
contract = Dry::Schema.Params(parent: contract, &block) if block
|
13
|
+
|
14
|
+
if contract.respond_to?(:schema)
|
15
|
+
# It's a Dry::Validation::Contract, then.
|
16
|
+
contract = contract.new
|
17
|
+
key_map = contract.schema.key_map
|
18
|
+
else
|
19
|
+
# Dry::Schema::Processor, hopefully.
|
20
|
+
key_map = contract.key_map
|
21
|
+
end
|
22
|
+
|
23
|
+
api.namespace_stackable(:contract_key_map, key_map)
|
24
|
+
|
25
|
+
validator_options = {
|
26
|
+
validator_class: Validator,
|
27
|
+
opts: { schema: contract }
|
28
|
+
}
|
29
|
+
|
30
|
+
api.namespace_stackable(:validations, validator_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
class Validator
|
34
|
+
attr_reader :schema
|
35
|
+
|
36
|
+
def initialize(*_args, schema:)
|
37
|
+
@schema = schema
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validates a given request.
|
41
|
+
# @param request [Grape::Request] the request currently being handled
|
42
|
+
# @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed
|
43
|
+
# @return [void]
|
44
|
+
def validate(request)
|
45
|
+
res = schema.call(request.params)
|
46
|
+
|
47
|
+
if res.success?
|
48
|
+
request.params.deep_merge!(res.to_h)
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
errors = []
|
53
|
+
|
54
|
+
res.errors.messages.each do |message|
|
55
|
+
full_name = message.path.first.to_s
|
56
|
+
|
57
|
+
full_name += "[#{message.path[1..].join('][')}]" if message.path.size > 1
|
58
|
+
|
59
|
+
errors << Grape::Exceptions::Validation.new(params: [full_name], message: message.text)
|
60
|
+
end
|
61
|
+
|
62
|
+
raise Grape::Exceptions::ValidationArrayErrors.new(errors)
|
63
|
+
end
|
64
|
+
|
65
|
+
def fail_fast?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'attributes_doc'
|
4
|
-
|
5
3
|
module Grape
|
6
4
|
module Validations
|
7
5
|
class ParamsScope
|
@@ -95,9 +93,7 @@ module Grape
|
|
95
93
|
|
96
94
|
def meets_dependency?(params, request_params)
|
97
95
|
return true unless @dependent_on
|
98
|
-
|
99
96
|
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
100
|
-
|
101
97
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
102
98
|
|
103
99
|
meets_hash_dependency?(params)
|
@@ -105,7 +101,6 @@ module Grape
|
|
105
101
|
|
106
102
|
def attr_meets_dependency?(params)
|
107
103
|
return true unless @dependent_on
|
108
|
-
|
109
104
|
return false if @parent.present? && !@parent.attr_meets_dependency?(params)
|
110
105
|
|
111
106
|
meets_hash_dependency?(params)
|
@@ -171,6 +166,10 @@ module Grape
|
|
171
166
|
!@optional
|
172
167
|
end
|
173
168
|
|
169
|
+
def reset_index
|
170
|
+
@index = nil
|
171
|
+
end
|
172
|
+
|
174
173
|
protected
|
175
174
|
|
176
175
|
# Adds a parameter declaration to our list of validations.
|
@@ -191,7 +190,13 @@ module Grape
|
|
191
190
|
#
|
192
191
|
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
193
192
|
def full_path
|
194
|
-
nested?
|
193
|
+
if nested?
|
194
|
+
(@parent.full_path + [@element])
|
195
|
+
elsif lateral?
|
196
|
+
@parent.full_path
|
197
|
+
else
|
198
|
+
[]
|
199
|
+
end
|
195
200
|
end
|
196
201
|
|
197
202
|
private
|
@@ -211,11 +216,11 @@ module Grape
|
|
211
216
|
|
212
217
|
def require_required_and_optional_fields(context, opts)
|
213
218
|
if context == :all
|
214
|
-
optional_fields = Array(opts[:except])
|
215
|
-
required_fields = opts[:using].keys
|
219
|
+
optional_fields = Array.wrap(opts[:except])
|
220
|
+
required_fields = opts[:using].keys.delete_if { |f| optional_fields.include?(f) }
|
216
221
|
else # context == :none
|
217
|
-
required_fields = Array(opts[:except])
|
218
|
-
optional_fields = opts[:using].keys
|
222
|
+
required_fields = Array.wrap(opts[:except])
|
223
|
+
optional_fields = opts[:using].keys.delete_if { |f| required_fields.include?(f) }
|
219
224
|
end
|
220
225
|
required_fields.each do |field|
|
221
226
|
field_opts = opts[:using][field]
|
@@ -231,7 +236,10 @@ module Grape
|
|
231
236
|
|
232
237
|
def require_optional_fields(context, opts)
|
233
238
|
optional_fields = opts[:using].keys
|
234
|
-
|
239
|
+
unless context == :all
|
240
|
+
except_fields = Array.wrap(opts[:except])
|
241
|
+
optional_fields.delete_if { |f| except_fields.include?(f) }
|
242
|
+
end
|
235
243
|
optional_fields.each do |field|
|
236
244
|
field_opts = opts[:using][field]
|
237
245
|
optional(field, field_opts) if field_opts
|
@@ -266,6 +274,7 @@ module Grape
|
|
266
274
|
parent: self,
|
267
275
|
optional: optional,
|
268
276
|
type: type || Array,
|
277
|
+
group: @group,
|
269
278
|
&block
|
270
279
|
)
|
271
280
|
end
|
@@ -295,12 +304,7 @@ module Grape
|
|
295
304
|
# `optional` invocation that opened this scope.
|
296
305
|
# @yield parameter scope
|
297
306
|
def new_group_scope(attrs, &block)
|
298
|
-
self.class.new(
|
299
|
-
api: @api,
|
300
|
-
parent: self,
|
301
|
-
group: attrs.first,
|
302
|
-
&block
|
303
|
-
)
|
307
|
+
self.class.new(api: @api, parent: self, group: attrs.first, &block)
|
304
308
|
end
|
305
309
|
|
306
310
|
# Pushes declared params to parent or settings
|
@@ -463,8 +467,7 @@ module Grape
|
|
463
467
|
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
468
|
|
465
469
|
if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
466
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
467
|
-
|
470
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
|
468
471
|
end
|
469
472
|
|
470
473
|
return unless excepts && !excepts.is_a?(Proc)
|
@@ -1,94 +1,92 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'array_coercer'
|
4
|
-
require_relative 'set_coercer'
|
5
|
-
require_relative 'primitive_coercer'
|
6
|
-
|
7
3
|
module Grape
|
8
4
|
module Validations
|
9
5
|
module Types
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
41
39
|
end
|
42
|
-
end
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
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)
|
47
44
|
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
# Use a special coercer for multiply-typed parameters.
|
46
|
+
if Types.multiple?(type)
|
47
|
+
MultipleTypeCoercer.new(type, method)
|
51
48
|
|
52
|
-
|
53
|
-
|
54
|
-
|
49
|
+
# Use a special coercer for custom types and coercion methods.
|
50
|
+
elsif method || Types.custom?(type)
|
51
|
+
CustomTypeCoercer.new(type, method)
|
55
52
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
65
63
|
end
|
66
|
-
end
|
67
64
|
|
68
|
-
|
69
|
-
|
65
|
+
def self.cache_instance(type, method, strict, &_block)
|
66
|
+
key = cache_key(type, method, strict)
|
70
67
|
|
71
|
-
|
68
|
+
return @__cache[key] if @__cache.key?(key)
|
72
69
|
|
73
|
-
|
70
|
+
instance = yield
|
74
71
|
|
75
|
-
|
76
|
-
|
77
|
-
|
72
|
+
@__cache_write_lock.synchronize do
|
73
|
+
@__cache[key] = instance
|
74
|
+
end
|
78
75
|
|
79
|
-
|
80
|
-
|
76
|
+
instance
|
77
|
+
end
|
81
78
|
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
def self.cache_key(type, method, strict)
|
80
|
+
[type, method, strict].each_with_object(+'_') do |val, memo|
|
81
|
+
next if val.nil?
|
85
82
|
|
86
|
-
|
83
|
+
memo << '_' << val.to_s
|
84
|
+
end
|
87
85
|
end
|
88
|
-
end
|
89
86
|
|
90
|
-
|
91
|
-
|
87
|
+
instance_variable_set(:@__cache, {})
|
88
|
+
instance_variable_set(:@__cache_write_lock, Mutex.new)
|
89
|
+
end
|
92
90
|
end
|
93
91
|
end
|
94
92
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'dry-types'
|
4
|
-
|
5
3
|
module DryTypes
|
6
4
|
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
7
5
|
# a container in this case. Check documentation for more information
|
@@ -24,9 +22,7 @@ module Grape
|
|
24
22
|
# collection_coercer_for(Array)
|
25
23
|
# #=> Grape::Validations::Types::ArrayCoercer
|
26
24
|
def collection_coercer_for(type)
|
27
|
-
|
28
|
-
DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get("#{type.name.camelize}Coercer")
|
29
|
-
end
|
25
|
+
Grape::Validations::Types.const_get(:"#{type.name.camelize}Coercer")
|
30
26
|
end
|
31
27
|
|
32
28
|
# Returns an instance of a coercer for a given type
|
@@ -37,12 +33,6 @@ module Grape
|
|
37
33
|
# so we need to figure out the actual type
|
38
34
|
collection_coercer_for(type.class).new(type, strict)
|
39
35
|
end
|
40
|
-
|
41
|
-
protected
|
42
|
-
|
43
|
-
def collection_coercers
|
44
|
-
@collection_coercers ||= {}
|
45
|
-
end
|
46
36
|
end
|
47
37
|
|
48
38
|
def initialize(type, strict = false)
|