grape 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +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
|
|