grape 1.6.1 → 1.7.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 +36 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +120 -19
- data/UPGRADING.md +19 -4
- data/lib/grape/api/instance.rb +1 -1
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/callbacks.rb +0 -2
- data/lib/grape/dsl/configuration.rb +0 -2
- data/lib/grape/dsl/desc.rb +0 -15
- data/lib/grape/dsl/helpers.rb +0 -2
- data/lib/grape/dsl/inside_route.rb +33 -29
- data/lib/grape/dsl/middleware.rb +0 -2
- data/lib/grape/dsl/parameters.rb +5 -7
- data/lib/grape/dsl/request_response.rb +0 -2
- data/lib/grape/dsl/routing.rb +4 -2
- data/lib/grape/dsl/settings.rb +0 -2
- data/lib/grape/dsl/validations.rb +0 -15
- data/lib/grape/error_formatter/json.rb +7 -1
- data/lib/grape/exceptions/base.rb +2 -2
- data/lib/grape/exceptions/missing_group_type.rb +8 -1
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
- data/lib/grape/exceptions/validation.rb +0 -4
- data/lib/grape/locale/en.yml +9 -8
- data/lib/grape/middleware/auth/dsl.rb +0 -1
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/request.rb +2 -0
- data/lib/grape/validations/attributes_doc.rb +58 -0
- data/lib/grape/validations/params_scope.rb +66 -40
- data/lib/grape/validations/types/array_coercer.rb +2 -2
- data/lib/grape/validations/types/build_coercer.rb +94 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +13 -8
- data/lib/grape/validations/types/json.rb +2 -0
- data/lib/grape/validations/types/primitive_coercer.rb +20 -10
- data/lib/grape/validations/types/set_coercer.rb +3 -2
- data/lib/grape/validations/types.rb +20 -26
- data/lib/grape/validations/validators/base.rb +7 -0
- data/lib/grape/validations.rb +16 -6
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +20 -15
- data/spec/grape/api/custom_validations_spec.rb +41 -2
- data/spec/grape/api/deeply_included_options_spec.rb +0 -2
- data/spec/grape/api/defines_boolean_in_params_spec.rb +0 -2
- data/spec/grape/api/documentation_spec.rb +59 -0
- data/spec/grape/api/inherited_helpers_spec.rb +0 -2
- data/spec/grape/api/instance_spec.rb +0 -1
- data/spec/grape/api/invalid_format_spec.rb +0 -2
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/nested_helpers_spec.rb +0 -2
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/parameters_modification_spec.rb +0 -2
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
- data/spec/grape/api/recognize_path_spec.rb +0 -2
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
- data/spec/grape/api/routes_with_requirements_spec.rb +0 -2
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +0 -2
- data/spec/grape/api/shared_helpers_spec.rb +0 -2
- data/spec/grape/api_remount_spec.rb +0 -1
- data/spec/grape/api_spec.rb +18 -5
- data/spec/grape/config_spec.rb +0 -2
- data/spec/grape/dsl/callbacks_spec.rb +0 -2
- data/spec/grape/dsl/configuration_spec.rb +0 -2
- data/spec/grape/dsl/desc_spec.rb +0 -2
- data/spec/grape/dsl/headers_spec.rb +2 -4
- data/spec/grape/dsl/helpers_spec.rb +0 -2
- data/spec/grape/dsl/inside_route_spec.rb +10 -12
- data/spec/grape/dsl/logger_spec.rb +0 -2
- data/spec/grape/dsl/middleware_spec.rb +0 -2
- data/spec/grape/dsl/parameters_spec.rb +0 -2
- data/spec/grape/dsl/request_response_spec.rb +6 -8
- data/spec/grape/dsl/routing_spec.rb +1 -3
- data/spec/grape/dsl/settings_spec.rb +0 -2
- data/spec/grape/dsl/validations_spec.rb +0 -17
- data/spec/grape/endpoint/declared_spec.rb +2 -4
- data/spec/grape/endpoint_spec.rb +22 -3
- data/spec/grape/entity_spec.rb +0 -1
- data/spec/grape/exceptions/base_spec.rb +16 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
- data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
- data/spec/grape/exceptions/missing_option_spec.rb +1 -3
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +0 -1
- data/spec/grape/exceptions/validation_spec.rb +1 -3
- data/spec/grape/extensions/param_builders/hash_spec.rb +0 -2
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +0 -2
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +0 -2
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +0 -2
- data/spec/grape/integration/rack_spec.rb +0 -2
- data/spec/grape/loading_spec.rb +0 -2
- data/spec/grape/middleware/auth/base_spec.rb +0 -1
- data/spec/grape/middleware/auth/dsl_spec.rb +0 -2
- data/spec/grape/middleware/auth/strategies_spec.rb +0 -2
- data/spec/grape/middleware/base_spec.rb +0 -2
- data/spec/grape/middleware/error_spec.rb +6 -1
- data/spec/grape/middleware/exception_spec.rb +0 -2
- data/spec/grape/middleware/formatter_spec.rb +0 -2
- data/spec/grape/middleware/globals_spec.rb +0 -2
- data/spec/grape/middleware/stack_spec.rb +0 -2
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +0 -2
- data/spec/grape/middleware/versioner/header_spec.rb +18 -4
- data/spec/grape/middleware/versioner/param_spec.rb +0 -2
- data/spec/grape/middleware/versioner/path_spec.rb +0 -2
- data/spec/grape/middleware/versioner_spec.rb +0 -2
- data/spec/grape/named_api_spec.rb +0 -2
- data/spec/grape/parser_spec.rb +0 -2
- data/spec/grape/path_spec.rb +0 -2
- data/spec/grape/presenters/presenter_spec.rb +0 -2
- data/spec/grape/request_spec.rb +0 -2
- data/spec/grape/util/inheritable_setting_spec.rb +0 -1
- data/spec/grape/util/inheritable_values_spec.rb +0 -1
- data/spec/grape/util/reverse_stackable_values_spec.rb +0 -1
- data/spec/grape/util/stackable_values_spec.rb +0 -1
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
- data/spec/grape/validations/attributes_doc_spec.rb +153 -0
- data/spec/grape/validations/attributes_iterator_spec.rb +0 -2
- data/spec/grape/validations/instance_behaivour_spec.rb +0 -2
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +0 -2
- data/spec/grape/validations/params_scope_spec.rb +315 -86
- data/spec/grape/validations/single_attribute_iterator_spec.rb +0 -2
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
- data/spec/grape/validations/types/primitive_coercer_spec.rb +20 -5
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
- data/spec/grape/validations/types_spec.rb +28 -2
- data/spec/grape/validations/validators/all_or_none_spec.rb +0 -2
- data/spec/grape/validations/validators/allow_blank_spec.rb +0 -2
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +0 -2
- data/spec/grape/validations/validators/coerce_spec.rb +0 -2
- data/spec/grape/validations/validators/default_spec.rb +0 -2
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +0 -2
- data/spec/grape/validations/validators/except_values_spec.rb +0 -2
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +0 -2
- data/spec/grape/validations/validators/presence_spec.rb +0 -2
- data/spec/grape/validations/validators/regexp_spec.rb +0 -2
- data/spec/grape/validations/validators/same_as_spec.rb +0 -2
- data/spec/grape/validations/validators/values_spec.rb +0 -2
- data/spec/grape/validations_spec.rb +50 -22
- data/spec/integration/multi_json/json_spec.rb +0 -2
- data/spec/integration/multi_xml/xml_spec.rb +0 -2
- data/spec/spec_helper.rb +9 -4
- metadata +17 -8
- data/spec/support/eager_load.rb +0 -19
@@ -2,10 +2,17 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Exceptions
|
5
|
-
class
|
5
|
+
class MissingGroupType < Base
|
6
6
|
def initialize
|
7
7
|
super(message: compose_message(:missing_group_type))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
Grape::Exceptions::MissingGroupTypeError = Class.new(Grape::Exceptions::MissingGroupType) do
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
warn '[DEPRECATION] `Grape::Exceptions::MissingGroupTypeError` is deprecated. Use `Grape::Exceptions::MissingGroupType` instead.'
|
17
|
+
end
|
18
|
+
end
|
@@ -2,10 +2,17 @@
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Exceptions
|
5
|
-
class
|
5
|
+
class UnsupportedGroupType < Base
|
6
6
|
def initialize
|
7
7
|
super(message: compose_message(:unsupported_group_type))
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
13
|
+
Grape::Exceptions::UnsupportedGroupTypeError = Class.new(Grape::Exceptions::UnsupportedGroupType) do
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
warn '[DEPRECATION] `Grape::Exceptions::UnsupportedGroupTypeError` is deprecated. Use `Grape::Exceptions::UnsupportedGroupType` instead.'
|
17
|
+
end
|
18
|
+
end
|
data/lib/grape/locale/en.yml
CHANGED
@@ -11,8 +11,8 @@ en:
|
|
11
11
|
except_values: 'has a value not allowed'
|
12
12
|
same_as: 'is not the same as %{parameter}'
|
13
13
|
missing_vendor_option:
|
14
|
-
problem: 'missing :vendor option
|
15
|
-
summary: 'when version using header, you must specify :vendor option
|
14
|
+
problem: 'missing :vendor option'
|
15
|
+
summary: 'when version using header, you must specify :vendor option'
|
16
16
|
resolution: "eg: version 'v1', using: :header, vendor: 'twitter'"
|
17
17
|
missing_mime_type:
|
18
18
|
problem: 'missing mime type for %{new_format}'
|
@@ -21,12 +21,12 @@ en:
|
|
21
21
|
or add your own with content_type :%{new_format}, 'application/%{new_format}'
|
22
22
|
"
|
23
23
|
invalid_with_option_for_represent:
|
24
|
-
problem: '
|
24
|
+
problem: 'you must specify an entity class in the :with option'
|
25
25
|
resolution: 'eg: represent User, :with => Entity::User'
|
26
|
-
missing_option: '
|
26
|
+
missing_option: 'you must specify :%{option} options'
|
27
27
|
invalid_formatter: 'cannot convert %{klass} to %{to_format}'
|
28
28
|
invalid_versioner_option:
|
29
|
-
problem: '
|
29
|
+
problem: 'unknown :using for versioner: %{strategy}'
|
30
30
|
resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'
|
31
31
|
unknown_validator: 'unknown validator: %{validator_type}'
|
32
32
|
unknown_options: 'unknown options: %{options}'
|
@@ -44,11 +44,12 @@ en:
|
|
44
44
|
"when specifying %{body_format} as content-type, you must pass valid
|
45
45
|
%{body_format} in the request's 'body'
|
46
46
|
"
|
47
|
-
empty_message_body: '
|
47
|
+
empty_message_body: 'empty message body supplied with %{body_format} content-type'
|
48
|
+
too_many_multipart_files: "the number of uploaded files exceeded the system's configured limit (%{limit})"
|
48
49
|
invalid_accept_header:
|
49
|
-
problem: '
|
50
|
+
problem: 'invalid accept header'
|
50
51
|
resolution: '%{message}'
|
51
52
|
invalid_version_header:
|
52
|
-
problem: '
|
53
|
+
problem: 'invalid version header'
|
53
54
|
resolution: '%{message}'
|
54
55
|
invalid_response: 'Invalid response'
|
@@ -72,7 +72,7 @@ module Grape
|
|
72
72
|
|
73
73
|
def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type })
|
74
74
|
message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML
|
75
|
-
Rack::Response.new([message], status, headers)
|
75
|
+
Rack::Response.new([message], Rack::Utils.status_code(status), headers)
|
76
76
|
end
|
77
77
|
|
78
78
|
def format_message(message, backtrace, original_exception = nil)
|
@@ -121,7 +121,7 @@ module Grape
|
|
121
121
|
|
122
122
|
def run_rescue_handler(handler, error)
|
123
123
|
if handler.instance_of?(Symbol)
|
124
|
-
raise NoMethodError, "undefined method
|
124
|
+
raise NoMethodError, "undefined method '#{handler}'" unless respond_to?(handler)
|
125
125
|
|
126
126
|
handler = public_method(handler)
|
127
127
|
end
|
data/lib/grape/request.rb
CHANGED
@@ -17,6 +17,8 @@ module Grape
|
|
17
17
|
@params ||= build_params
|
18
18
|
rescue EOFError
|
19
19
|
raise Grape::Exceptions::EmptyMessageBody.new(content_type)
|
20
|
+
rescue Rack::Multipart::MultipartPartLimitError
|
21
|
+
raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)
|
20
22
|
end
|
21
23
|
|
22
24
|
def headers
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
module Validations
|
5
|
+
class ParamsScope
|
6
|
+
# Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
|
7
|
+
# internal API), the class only cleans up attributes to avoid junk in RAM.
|
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
|
+
|
19
|
+
def extract_details(validations)
|
20
|
+
details[:required] = validations.key?(:presence)
|
21
|
+
|
22
|
+
desc = validations.delete(:desc) || validations.delete(:description)
|
23
|
+
|
24
|
+
details[:desc] = desc if desc
|
25
|
+
|
26
|
+
documentation = validations.delete(:documentation)
|
27
|
+
|
28
|
+
details[:documentation] = documentation if documentation
|
29
|
+
|
30
|
+
details[:default] = validations[:default] if validations.key?(:default)
|
31
|
+
end
|
32
|
+
|
33
|
+
def document(attrs)
|
34
|
+
return if @api.namespace_inheritable(:do_not_document)
|
35
|
+
|
36
|
+
details[:type] = type.to_s if type
|
37
|
+
details[:values] = values if values
|
38
|
+
|
39
|
+
documented_attrs = attrs.each_with_object({}) do |name, memo|
|
40
|
+
memo[@scope.full_name(name)] = details
|
41
|
+
end
|
42
|
+
|
43
|
+
@api.namespace_stackable(:params, documented_attrs)
|
44
|
+
end
|
45
|
+
|
46
|
+
def required
|
47
|
+
details[:required]
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def details
|
53
|
+
@details ||= {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'attributes_doc'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
module Validations
|
5
7
|
class ParamsScope
|
@@ -8,6 +10,35 @@ module Grape
|
|
8
10
|
|
9
11
|
include Grape::DSL::Parameters
|
10
12
|
|
13
|
+
class Attr
|
14
|
+
attr_accessor :key, :scope
|
15
|
+
|
16
|
+
# Open up a new ParamsScope::Attr
|
17
|
+
# @param key [Hash, Symbol] key of attr
|
18
|
+
# @param scope [Grape::Validations::ParamsScope] scope of attr
|
19
|
+
def initialize(key, scope)
|
20
|
+
@key = key
|
21
|
+
@scope = scope
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return Array[Symbol, Hash[Symbol => Array]] declared_params with symbol instead of Attr
|
25
|
+
def self.attrs_keys(declared_params)
|
26
|
+
declared_params.map do |declared_param_attr|
|
27
|
+
attr_key(declared_param_attr)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.attr_key(declared_param_attr)
|
32
|
+
return attr_key(declared_param_attr.key) if declared_param_attr.is_a?(self)
|
33
|
+
|
34
|
+
if declared_param_attr.is_a?(Hash)
|
35
|
+
declared_param_attr.transform_values { |value| attrs_keys(value) }
|
36
|
+
else
|
37
|
+
declared_param_attr
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
11
42
|
# Open up a new ParamsScope, allowing parameter definitions per
|
12
43
|
# Grape::DSL::Params.
|
13
44
|
# @param opts [Hash] options for this scope
|
@@ -31,7 +62,7 @@ module Grape
|
|
31
62
|
@api = opts[:api]
|
32
63
|
@optional = opts[:optional] || false
|
33
64
|
@type = opts[:type]
|
34
|
-
@group = opts[:group]
|
65
|
+
@group = opts[:group]
|
35
66
|
@dependent_on = opts[:dependent_on]
|
36
67
|
@declared_params = []
|
37
68
|
@index = nil
|
@@ -64,6 +95,18 @@ module Grape
|
|
64
95
|
|
65
96
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
66
97
|
|
98
|
+
meets_hash_dependency?(params)
|
99
|
+
end
|
100
|
+
|
101
|
+
def attr_meets_dependency?(params)
|
102
|
+
return true unless @dependent_on
|
103
|
+
|
104
|
+
return false if @parent.present? && !@parent.attr_meets_dependency?(params)
|
105
|
+
|
106
|
+
meets_hash_dependency?(params)
|
107
|
+
end
|
108
|
+
|
109
|
+
def meets_hash_dependency?(params)
|
67
110
|
# params might be anything what looks like a hash, so it must implement a `key?` method
|
68
111
|
return false unless params.respond_to?(:key?)
|
69
112
|
|
@@ -128,13 +171,14 @@ module Grape
|
|
128
171
|
# Adds a parameter declaration to our list of validations.
|
129
172
|
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
|
130
173
|
def push_declared_params(attrs, **opts)
|
174
|
+
opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
|
131
175
|
if lateral?
|
132
176
|
@parent.push_declared_params(attrs, **opts)
|
133
177
|
else
|
134
178
|
push_renamed_param(full_path + [attrs.first], opts[:as]) \
|
135
179
|
if opts && opts[:as]
|
136
180
|
|
137
|
-
@declared_params.concat
|
181
|
+
@declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
|
138
182
|
end
|
139
183
|
end
|
140
184
|
|
@@ -206,8 +250,8 @@ module Grape
|
|
206
250
|
# if required params are grouped and no type or unsupported type is provided, raise an error
|
207
251
|
type = attrs[1] ? attrs[1][:type] : nil
|
208
252
|
if attrs.first && !optional
|
209
|
-
raise Grape::Exceptions::
|
210
|
-
raise Grape::Exceptions::
|
253
|
+
raise Grape::Exceptions::MissingGroupType if type.nil?
|
254
|
+
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
211
255
|
end
|
212
256
|
|
213
257
|
self.class.new(
|
@@ -269,17 +313,14 @@ module Grape
|
|
269
313
|
end
|
270
314
|
|
271
315
|
def validates(attrs, validations)
|
272
|
-
|
316
|
+
doc = AttributesDoc.new @api, self
|
317
|
+
doc.extract_details validations
|
273
318
|
|
274
319
|
coerce_type = infer_coercion(validations)
|
275
320
|
|
276
|
-
|
277
|
-
|
278
|
-
desc = validations.delete(:desc) || validations.delete(:description)
|
279
|
-
doc_attrs[:desc] = desc if desc
|
321
|
+
doc.type = coerce_type
|
280
322
|
|
281
323
|
default = validations[:default]
|
282
|
-
doc_attrs[:default] = default if validations.key?(:default)
|
283
324
|
|
284
325
|
if (values_hash = validations[:values]).is_a? Hash
|
285
326
|
values = values_hash[:value]
|
@@ -288,7 +329,8 @@ module Grape
|
|
288
329
|
else
|
289
330
|
values = validations[:values]
|
290
331
|
end
|
291
|
-
|
332
|
+
|
333
|
+
doc.values = values
|
292
334
|
|
293
335
|
except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
|
294
336
|
|
@@ -304,28 +346,22 @@ module Grape
|
|
304
346
|
# type should be compatible with values array, if both exist
|
305
347
|
validate_value_coercion(coerce_type, values, except_values, excepts)
|
306
348
|
|
307
|
-
|
308
|
-
|
309
|
-
document_attribute(attrs, doc_attrs)
|
349
|
+
doc.document attrs
|
310
350
|
|
311
351
|
opts = derive_validator_options(validations)
|
312
352
|
|
313
|
-
order_specific_validations = Set[:as]
|
314
|
-
|
315
353
|
# Validate for presence before any other validators
|
316
|
-
validates_presence(validations, attrs,
|
317
|
-
order_specific_validations << validation_type
|
318
|
-
end
|
354
|
+
validates_presence(validations, attrs, doc, opts)
|
319
355
|
|
320
356
|
# Before we run the rest of the validators, let's handle
|
321
357
|
# whatever coercion so that we are working with correctly
|
322
358
|
# type casted values
|
323
|
-
coerce_type validations, attrs,
|
359
|
+
coerce_type validations, attrs, doc, opts
|
324
360
|
|
325
361
|
validations.each do |type, options|
|
326
|
-
next if
|
362
|
+
next if type == :as
|
327
363
|
|
328
|
-
validate(type, options, attrs,
|
364
|
+
validate(type, options, attrs, doc, opts)
|
329
365
|
end
|
330
366
|
end
|
331
367
|
|
@@ -389,7 +425,7 @@ module Grape
|
|
389
425
|
# composited from more than one +requires+/+optional+
|
390
426
|
# parameter, and needs to be run before most other
|
391
427
|
# validations.
|
392
|
-
def coerce_type(validations, attrs,
|
428
|
+
def coerce_type(validations, attrs, doc, opts)
|
393
429
|
check_coerce_with(validations)
|
394
430
|
|
395
431
|
return unless validations.key?(:coerce)
|
@@ -399,7 +435,7 @@ module Grape
|
|
399
435
|
method: validations[:coerce_with],
|
400
436
|
message: validations[:coerce_message]
|
401
437
|
}
|
402
|
-
validate('coerce', coerce_options, attrs,
|
438
|
+
validate('coerce', coerce_options, attrs, doc, opts)
|
403
439
|
validations.delete(:coerce_with)
|
404
440
|
validations.delete(:coerce)
|
405
441
|
validations.delete(:coerce_message)
|
@@ -430,18 +466,14 @@ module Grape
|
|
430
466
|
unless Array(default).none? { |def_val| excepts.include?(def_val) }
|
431
467
|
end
|
432
468
|
|
433
|
-
def validate(type, options, attrs,
|
434
|
-
validator_class = Validations.validators[type.to_s]
|
435
|
-
|
436
|
-
raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
|
437
|
-
|
469
|
+
def validate(type, options, attrs, doc, opts)
|
438
470
|
validator_options = {
|
439
471
|
attributes: attrs,
|
440
472
|
options: options,
|
441
|
-
required:
|
473
|
+
required: doc.required,
|
442
474
|
params_scope: self,
|
443
475
|
opts: opts,
|
444
|
-
validator_class:
|
476
|
+
validator_class: Validations.require_validator(type)
|
445
477
|
}
|
446
478
|
@api.namespace_stackable(:validations, validator_options)
|
447
479
|
end
|
@@ -485,17 +517,11 @@ module Grape
|
|
485
517
|
}
|
486
518
|
end
|
487
519
|
|
488
|
-
def validates_presence(validations, attrs,
|
520
|
+
def validates_presence(validations, attrs, doc, opts)
|
489
521
|
return unless validations.key?(:presence) && validations[:presence]
|
490
522
|
|
491
|
-
validate(:presence, validations
|
492
|
-
|
493
|
-
yield :message if validations.key?(:message)
|
494
|
-
end
|
495
|
-
|
496
|
-
def document_attribute(attrs, doc_attrs)
|
497
|
-
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
498
|
-
@api.document_attribute(full_attrs, doc_attrs)
|
523
|
+
validate(:presence, validations.delete(:presence), attrs, doc, opts)
|
524
|
+
validations.delete(:message) if validations.key?(:message)
|
499
525
|
end
|
500
526
|
end
|
501
527
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'dry_type_coercer'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
module Validations
|
5
7
|
module Types
|
@@ -12,8 +14,6 @@ module Grape
|
|
12
14
|
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
13
15
|
# maintains Virtus behavior in coercing.
|
14
16
|
class ArrayCoercer < DryTypeCoercer
|
15
|
-
register_collection Array
|
16
|
-
|
17
17
|
def initialize(type, strict = false)
|
18
18
|
super
|
19
19
|
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'array_coercer'
|
4
|
+
require_relative 'set_coercer'
|
5
|
+
require_relative 'primitive_coercer'
|
6
|
+
|
7
|
+
module Grape
|
8
|
+
module Validations
|
9
|
+
module Types
|
10
|
+
# Chooses the best coercer for the given type. For example, if the type
|
11
|
+
# is Integer, it will return a coercer which will be able to coerce a value
|
12
|
+
# to the integer.
|
13
|
+
#
|
14
|
+
# There are a few very special coercers which might be returned.
|
15
|
+
#
|
16
|
+
# +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when
|
17
|
+
# the given type implies values in an array with different types.
|
18
|
+
# For example, +[Integer, String]+ allows integer and string values in
|
19
|
+
# an array.
|
20
|
+
#
|
21
|
+
# +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when
|
22
|
+
# a method is specified by a user with +coerce_with+ option or the user
|
23
|
+
# specifies a custom type which implements requirments of
|
24
|
+
# +Grape::Types::CustomTypeCoercer+.
|
25
|
+
#
|
26
|
+
# +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the
|
27
|
+
# previous one, but it expects an array or set of values having a custom
|
28
|
+
# type implemented by the user.
|
29
|
+
#
|
30
|
+
# There is also a group of custom types implemented by Grape, check
|
31
|
+
# +Grape::Validations::Types::SPECIAL+ to get the full list.
|
32
|
+
#
|
33
|
+
# @param type [Class] the type to which input strings
|
34
|
+
# should be coerced
|
35
|
+
# @param method [Class,#call] the coercion method to use
|
36
|
+
# @return [Object] object to be used
|
37
|
+
# for coercion and type validation
|
38
|
+
def self.build_coercer(type, method: nil, strict: false)
|
39
|
+
cache_instance(type, method, strict) do
|
40
|
+
create_coercer_instance(type, method, strict)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create_coercer_instance(type, method, strict)
|
45
|
+
# Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!
|
46
|
+
type = Types.map_special(type)
|
47
|
+
|
48
|
+
# Use a special coercer for multiply-typed parameters.
|
49
|
+
if Types.multiple?(type)
|
50
|
+
MultipleTypeCoercer.new(type, method)
|
51
|
+
|
52
|
+
# Use a special coercer for custom types and coercion methods.
|
53
|
+
elsif method || Types.custom?(type)
|
54
|
+
CustomTypeCoercer.new(type, method)
|
55
|
+
|
56
|
+
# Special coercer for collections of types that implement a parse method.
|
57
|
+
# CustomTypeCoercer (above) already handles such types when an explicit coercion
|
58
|
+
# method is supplied.
|
59
|
+
elsif Types.collection_of_custom?(type)
|
60
|
+
Types::CustomTypeCollectionCoercer.new(
|
61
|
+
Types.map_special(type.first), type.is_a?(Set)
|
62
|
+
)
|
63
|
+
else
|
64
|
+
DryTypeCoercer.coercer_instance_for(type, strict)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.cache_instance(type, method, strict, &_block)
|
69
|
+
key = cache_key(type, method, strict)
|
70
|
+
|
71
|
+
return @__cache[key] if @__cache.key?(key)
|
72
|
+
|
73
|
+
instance = yield
|
74
|
+
|
75
|
+
@__cache_write_lock.synchronize do
|
76
|
+
@__cache[key] = instance
|
77
|
+
end
|
78
|
+
|
79
|
+
instance
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.cache_key(type, method, strict)
|
83
|
+
[type, method, strict].each_with_object(+'_') do |val, memo|
|
84
|
+
next if val.nil?
|
85
|
+
|
86
|
+
memo << '_' << val.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
instance_variable_set(:@__cache, {})
|
91
|
+
instance_variable_set(:@__cache_write_lock, Mutex.new)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,5 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dry-types'
|
4
|
+
|
5
|
+
module DryTypes
|
6
|
+
# Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
|
7
|
+
# a container in this case. Check documentation for more information
|
8
|
+
# https://dry-rb.org/gems/dry-types/1.2/getting-started/
|
9
|
+
include Dry.Types()
|
10
|
+
end
|
11
|
+
|
3
12
|
module Grape
|
4
13
|
module Validations
|
5
14
|
module Types
|
@@ -9,19 +18,15 @@ module Grape
|
|
9
18
|
# https://dry-rb.org/gems/dry-types/1.2/built-in-types/
|
10
19
|
class DryTypeCoercer
|
11
20
|
class << self
|
12
|
-
# Registers a collection coercer which could be found by a type,
|
13
|
-
# see +collection_coercer_for+ method below. This method is meant for inheritors.
|
14
|
-
def register_collection(type)
|
15
|
-
DryTypeCoercer.collection_coercers[type] = self
|
16
|
-
end
|
17
|
-
|
18
21
|
# Returns a collection coercer which corresponds to a given type.
|
19
22
|
# Example:
|
20
23
|
#
|
21
24
|
# collection_coercer_for(Array)
|
22
25
|
# #=> Grape::Validations::Types::ArrayCoercer
|
23
26
|
def collection_coercer_for(type)
|
24
|
-
collection_coercers
|
27
|
+
collection_coercers.fetch(type) do
|
28
|
+
DryTypeCoercer.collection_coercers[type] = Grape::Validations::Types.const_get("#{type.name.camelize}Coercer")
|
29
|
+
end
|
25
30
|
end
|
26
31
|
|
27
32
|
# Returns an instance of a coercer for a given type
|
@@ -43,7 +48,7 @@ module Grape
|
|
43
48
|
def initialize(type, strict = false)
|
44
49
|
@type = type
|
45
50
|
@strict = strict
|
46
|
-
@scope = strict ?
|
51
|
+
@scope = strict ? DryTypes::Strict : DryTypes::Params
|
47
52
|
end
|
48
53
|
|
49
54
|
# Coerces the given value to a type which was specified during
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'dry_type_coercer'
|
4
|
+
|
3
5
|
module Grape
|
4
6
|
module Validations
|
5
7
|
module Types
|
@@ -8,16 +10,22 @@ module Grape
|
|
8
10
|
# that it has the proper type.
|
9
11
|
class PrimitiveCoercer < DryTypeCoercer
|
10
12
|
MAPPING = {
|
11
|
-
Grape::API::Boolean =>
|
12
|
-
BigDecimal =>
|
13
|
+
Grape::API::Boolean => DryTypes::Params::Bool,
|
14
|
+
BigDecimal => DryTypes::Params::Decimal,
|
15
|
+
Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,
|
16
|
+
TrueClass => DryTypes::Params::Bool.constrained(eql: true),
|
17
|
+
FalseClass => DryTypes::Params::Bool.constrained(eql: false),
|
13
18
|
|
14
19
|
# unfortunately, a +Params+ scope doesn't contain String
|
15
|
-
String =>
|
20
|
+
String => DryTypes::Coercible::String
|
16
21
|
}.freeze
|
17
22
|
|
18
23
|
STRICT_MAPPING = {
|
19
|
-
Grape::API::Boolean =>
|
20
|
-
BigDecimal =>
|
24
|
+
Grape::API::Boolean => DryTypes::Strict::Bool,
|
25
|
+
BigDecimal => DryTypes::Strict::Decimal,
|
26
|
+
Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,
|
27
|
+
TrueClass => DryTypes::Strict::Bool.constrained(eql: true),
|
28
|
+
FalseClass => DryTypes::Strict::Bool.constrained(eql: false)
|
21
29
|
}.freeze
|
22
30
|
|
23
31
|
def initialize(type, strict = false)
|
@@ -25,11 +33,13 @@ module Grape
|
|
25
33
|
|
26
34
|
@type = type
|
27
35
|
|
28
|
-
@coercer =
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
@coercer = (strict ? STRICT_MAPPING : MAPPING).fetch(type) do
|
37
|
+
scope.const_get(type.name, false)
|
38
|
+
rescue NameError
|
39
|
+
raise ArgumentError, "type #{type} should support coercion via `[]`" unless type.respond_to?(:[])
|
40
|
+
|
41
|
+
type
|
42
|
+
end
|
33
43
|
end
|
34
44
|
|
35
45
|
def call(val)
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
require_relative 'array_coercer'
|
5
|
+
|
3
6
|
module Grape
|
4
7
|
module Validations
|
5
8
|
module Types
|
6
9
|
# Takes the given array and converts it to a set. Every element of the set
|
7
10
|
# is also coerced.
|
8
11
|
class SetCoercer < ArrayCoercer
|
9
|
-
register_collection Set
|
10
|
-
|
11
12
|
def initialize(type, strict = false)
|
12
13
|
super
|
13
14
|
|