grape 1.5.3 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -0
- data/CONTRIBUTING.md +32 -1
- data/README.md +176 -25
- data/UPGRADING.md +61 -4
- data/grape.gemspec +6 -6
- data/lib/grape/api/instance.rb +14 -18
- data/lib/grape/api.rb +17 -12
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dry_types.rb +12 -0
- 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 +4 -20
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +7 -7
- data/lib/grape/dsl/inside_route.rb +43 -30
- data/lib/grape/dsl/middleware.rb +4 -6
- data/lib/grape/dsl/parameters.rb +13 -10
- data/lib/grape/dsl/request_response.rb +9 -8
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/dsl/settings.rb +5 -7
- data/lib/grape/dsl/validations.rb +0 -15
- data/lib/grape/endpoint.rb +22 -37
- data/lib/grape/error_formatter/json.rb +9 -7
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/base.rb +3 -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 +1 -6
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +9 -8
- data/lib/grape/middleware/auth/dsl.rb +7 -2
- data/lib/grape/middleware/base.rb +3 -1
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/cache.rb +1 -1
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_doc.rb +58 -0
- data/lib/grape/validations/params_scope.rb +138 -79
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
- data/lib/grape/validations/types/invalid_value.rb +0 -7
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +16 -8
- data/lib/grape/validations/types/set_coercer.rb +0 -2
- data/lib/grape/validations/types.rb +98 -30
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +82 -70
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/default_validator.rb +51 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +88 -0
- data/lib/grape/validations.rb +16 -6
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +77 -29
- data/spec/grape/api/custom_validations_spec.rb +116 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -5
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
- 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 +2 -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 +1 -3
- 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 +8 -10
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
- data/spec/grape/api/shared_helpers_spec.rb +0 -2
- data/spec/grape/api_remount_spec.rb +16 -16
- data/spec/grape/api_spec.rb +462 -251
- data/spec/grape/config_spec.rb +0 -2
- data/spec/grape/dsl/callbacks_spec.rb +2 -3
- data/spec/grape/dsl/desc_spec.rb +2 -2
- data/spec/grape/dsl/headers_spec.rb +39 -11
- data/spec/grape/dsl/helpers_spec.rb +3 -4
- data/spec/grape/dsl/inside_route_spec.rb +16 -16
- data/spec/grape/dsl/logger_spec.rb +15 -19
- data/spec/grape/dsl/middleware_spec.rb +2 -3
- data/spec/grape/dsl/parameters_spec.rb +2 -2
- data/spec/grape/dsl/request_response_spec.rb +7 -8
- data/spec/grape/dsl/routing_spec.rb +11 -10
- 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 +261 -16
- data/spec/grape/endpoint_spec.rb +88 -59
- data/spec/grape/entity_spec.rb +22 -23
- data/spec/grape/exceptions/base_spec.rb +16 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +64 -24
- 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 +13 -11
- data/spec/grape/exceptions/validation_spec.rb +5 -5
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
- data/spec/grape/integration/rack_spec.rb +6 -7
- data/spec/grape/loading_spec.rb +8 -10
- data/spec/grape/middleware/auth/base_spec.rb +0 -1
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
- data/spec/grape/middleware/base_spec.rb +28 -19
- data/spec/grape/middleware/error_spec.rb +8 -3
- data/spec/grape/middleware/exception_spec.rb +111 -163
- data/spec/grape/middleware/formatter_spec.rb +33 -14
- data/spec/grape/middleware/globals_spec.rb +7 -6
- data/spec/grape/middleware/stack_spec.rb +14 -14
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
- data/spec/grape/middleware/versioner/header_spec.rb +30 -15
- data/spec/grape/middleware/versioner/param_spec.rb +7 -3
- data/spec/grape/middleware/versioner/path_spec.rb +5 -3
- data/spec/grape/middleware/versioner_spec.rb +1 -3
- data/spec/grape/named_api_spec.rb +0 -2
- data/spec/grape/parser_spec.rb +4 -2
- data/spec/grape/path_spec.rb +52 -54
- data/spec/grape/presenters/presenter_spec.rb +7 -8
- data/spec/grape/request_spec.rb +6 -6
- data/spec/grape/util/inheritable_setting_spec.rb +7 -8
- data/spec/grape/util/inheritable_values_spec.rb +3 -3
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
- data/spec/grape/util/stackable_values_spec.rb +7 -6
- 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/instance_behaivour_spec.rb +9 -12
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
- data/spec/grape/validations/params_scope_spec.rb +361 -96
- data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
- data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
- data/spec/grape/validations/types_spec.rb +36 -10
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
- data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
- data/spec/grape/validations/validators/coerce_spec.rb +23 -24
- data/spec/grape/validations/validators/default_spec.rb +72 -80
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
- data/spec/grape/validations/validators/except_values_spec.rb +3 -5
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
- data/spec/grape/validations/validators/presence_spec.rb +16 -3
- data/spec/grape/validations/validators/regexp_spec.rb +25 -33
- data/spec/grape/validations/validators/same_as_spec.rb +14 -22
- data/spec/grape/validations/validators/values_spec.rb +201 -179
- data/spec/grape/validations_spec.rb +171 -79
- data/spec/integration/eager_load/eager_load_spec.rb +2 -2
- data/spec/integration/multi_json/json_spec.rb +1 -3
- data/spec/integration/multi_xml/xml_spec.rb +1 -3
- data/spec/shared/versioning_examples.rb +12 -9
- data/spec/spec_helper.rb +21 -6
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- metadata +41 -29
- data/lib/grape/validations/validators/all_or_none.rb +0 -15
- data/lib/grape/validations/validators/allow_blank.rb +0 -18
- data/lib/grape/validations/validators/as.rb +0 -16
- data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
- data/lib/grape/validations/validators/coerce.rb +0 -91
- data/lib/grape/validations/validators/default.rb +0 -48
- data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
- data/lib/grape/validations/validators/except_values.rb +0 -22
- data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
- data/lib/grape/validations/validators/presence.rb +0 -12
- data/lib/grape/validations/validators/regexp.rb +0 -13
- data/lib/grape/validations/validators/same_as.rb +0 -26
- data/lib/grape/validations/validators/values.rb +0 -83
- data/spec/grape/dsl/configuration_spec.rb +0 -16
- data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
- data/spec/support/eager_load.rb +0 -19
data/lib/grape/path.rb
CHANGED
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
|
@@ -37,6 +39,7 @@ module Grape
|
|
37
39
|
Grape::Util::LazyObject.new do
|
38
40
|
env.each_pair.with_object({}) do |(k, v), headers|
|
39
41
|
next unless k.to_s.start_with? HTTP_PREFIX
|
42
|
+
|
40
43
|
transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
|
41
44
|
headers[transformed_header] = v
|
42
45
|
end
|
@@ -44,7 +47,7 @@ module Grape
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def transform_header(header)
|
47
|
-
-header[5
|
50
|
+
-header[5..].split('_').each(&:capitalize!).join('-')
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
data/lib/grape/router/pattern.rb
CHANGED
data/lib/grape/router/route.rb
CHANGED
@@ -84,8 +84,8 @@ module Grape
|
|
84
84
|
path, line = *location.scan(SOURCE_LOCATION_REGEXP).first
|
85
85
|
path = File.realpath(path) if Pathname.new(path).relative?
|
86
86
|
expected ||= name
|
87
|
-
warn
|
88
|
-
#{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
|
87
|
+
warn <<~WARNING
|
88
|
+
#{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}.
|
89
89
|
WARNING
|
90
90
|
end
|
91
91
|
end
|
data/lib/grape/router.rb
CHANGED
@@ -28,6 +28,7 @@ module Grape
|
|
28
28
|
|
29
29
|
def compile!
|
30
30
|
return if compiled
|
31
|
+
|
31
32
|
@union = Regexp.union(@neutral_regexes)
|
32
33
|
@neutral_regexes = nil
|
33
34
|
self.class.supported_methods.each do |method|
|
@@ -60,6 +61,7 @@ module Grape
|
|
60
61
|
def recognize_path(input)
|
61
62
|
any = with_optimization { greedy_match?(input) }
|
62
63
|
return if any == default_response
|
64
|
+
|
63
65
|
any.endpoint
|
64
66
|
end
|
65
67
|
|
@@ -80,6 +82,7 @@ module Grape
|
|
80
82
|
map[method].each do |route|
|
81
83
|
next if exact_route == route
|
82
84
|
next unless route.match?(input)
|
85
|
+
|
83
86
|
response = process_route(route, env)
|
84
87
|
break unless cascade?(response)
|
85
88
|
end
|
@@ -91,6 +94,7 @@ module Grape
|
|
91
94
|
response = yield(input, method)
|
92
95
|
|
93
96
|
return response if response && !(cascade = cascade?(response))
|
97
|
+
|
94
98
|
last_neighbor_route = greedy_match?(input)
|
95
99
|
|
96
100
|
# If last_neighbor_route exists and request method is OPTIONS,
|
@@ -139,12 +143,14 @@ module Grape
|
|
139
143
|
def match?(input, method)
|
140
144
|
current_regexp = @optimized_map[method]
|
141
145
|
return unless current_regexp.match(input)
|
146
|
+
|
142
147
|
last_match = Regexp.last_match
|
143
148
|
@map[method].detect { |route| last_match["_#{route.index}"] }
|
144
149
|
end
|
145
150
|
|
146
151
|
def greedy_match?(input)
|
147
152
|
return unless @union.match(input)
|
153
|
+
|
148
154
|
last_match = Regexp.last_match
|
149
155
|
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
150
156
|
end
|
data/lib/grape/util/cache.rb
CHANGED
@@ -5,9 +5,7 @@ module Grape
|
|
5
5
|
# A branchable, inheritable settings object which can store both stackable
|
6
6
|
# and inheritable values (see InheritableValues and StackableValues).
|
7
7
|
class InheritableSetting
|
8
|
-
attr_accessor :route, :api_class, :namespace
|
9
|
-
attr_accessor :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable
|
10
|
-
attr_accessor :parent, :point_in_time_copies
|
8
|
+
attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies
|
11
9
|
|
12
10
|
# Retrieve global settings.
|
13
11
|
def self.global
|
data/lib/grape/util/json.rb
CHANGED
@@ -49,9 +49,10 @@ module Grape
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def []=(key, value)
|
52
|
-
@value_hash[key] =
|
52
|
+
@value_hash[key] = case value
|
53
|
+
when Hash
|
53
54
|
LazyValueHash.new(value)
|
54
|
-
|
55
|
+
when Array
|
55
56
|
LazyValueArray.new(value)
|
56
57
|
else
|
57
58
|
LazyValue.new(value)
|
@@ -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,11 +10,42 @@ 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
|
14
45
|
# @option opts :element [Symbol] the element that contains this scope; for
|
15
46
|
# this to be relevant, @parent must be set
|
47
|
+
# @option opts :element_renamed [Symbol, nil] whenever this scope should
|
48
|
+
# be renamed and to what, given +nil+ no renaming is done
|
16
49
|
# @option opts :parent [ParamsScope] the scope containing this scope
|
17
50
|
# @option opts :api [API] the API endpoint to modify
|
18
51
|
# @option opts :optional [Boolean] whether or not this scope needs to have
|
@@ -23,17 +56,18 @@ module Grape
|
|
23
56
|
# validate if this param is present in the parent scope
|
24
57
|
# @yield the instance context, open for parameter definitions
|
25
58
|
def initialize(opts, &block)
|
26
|
-
@element
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@
|
59
|
+
@element = opts[:element]
|
60
|
+
@element_renamed = opts[:element_renamed]
|
61
|
+
@parent = opts[:parent]
|
62
|
+
@api = opts[:api]
|
63
|
+
@optional = opts[:optional] || false
|
64
|
+
@type = opts[:type]
|
65
|
+
@group = opts[:group]
|
66
|
+
@dependent_on = opts[:dependent_on]
|
33
67
|
@declared_params = []
|
34
68
|
@index = nil
|
35
69
|
|
36
|
-
instance_eval(&block) if
|
70
|
+
instance_eval(&block) if block
|
37
71
|
|
38
72
|
configure_declared_params
|
39
73
|
end
|
@@ -50,18 +84,29 @@ module Grape
|
|
50
84
|
return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
|
51
85
|
return false unless meets_dependency?(scoped_params, parameters)
|
52
86
|
return true if parent.nil?
|
87
|
+
|
53
88
|
parent.should_validate?(parameters)
|
54
89
|
end
|
55
90
|
|
56
91
|
def meets_dependency?(params, request_params)
|
57
92
|
return true unless @dependent_on
|
58
93
|
|
59
|
-
if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
60
|
-
return false
|
61
|
-
end
|
94
|
+
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
62
95
|
|
63
96
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
64
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)
|
65
110
|
# params might be anything what looks like a hash, so it must implement a `key?` method
|
66
111
|
return false unless params.respond_to?(:key?)
|
67
112
|
|
@@ -126,21 +171,39 @@ module Grape
|
|
126
171
|
# Adds a parameter declaration to our list of validations.
|
127
172
|
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
|
128
173
|
def push_declared_params(attrs, **opts)
|
174
|
+
opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
|
129
175
|
if lateral?
|
130
176
|
@parent.push_declared_params(attrs, **opts)
|
131
177
|
else
|
132
|
-
|
133
|
-
|
134
|
-
@api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
|
135
|
-
attrs = [opts[:as]]
|
136
|
-
end
|
178
|
+
push_renamed_param(full_path + [attrs.first], opts[:as]) \
|
179
|
+
if opts && opts[:as]
|
137
180
|
|
138
|
-
@declared_params.concat
|
181
|
+
@declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
|
139
182
|
end
|
140
183
|
end
|
141
184
|
|
185
|
+
# Get the full path of the parameter scope in the hierarchy.
|
186
|
+
#
|
187
|
+
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
188
|
+
def full_path
|
189
|
+
nested? ? @parent.full_path + [@element] : []
|
190
|
+
end
|
191
|
+
|
142
192
|
private
|
143
193
|
|
194
|
+
# Add a new parameter which should be renamed when using the +#declared+
|
195
|
+
# method.
|
196
|
+
#
|
197
|
+
# @param path [Array<String, Symbol>] the full path of the parameter
|
198
|
+
# (including the parameter name as last array element)
|
199
|
+
# @param new_name [String, Symbol] the new name of the parameter (the
|
200
|
+
# renamed name, with the +as: ...+ semantic)
|
201
|
+
def push_renamed_param(path, new_name)
|
202
|
+
base = @api.route_setting(:renamed_params) || {}
|
203
|
+
base[Array(path).map(&:to_s)] = new_name.to_s
|
204
|
+
@api.route_setting(:renamed_params, base)
|
205
|
+
end
|
206
|
+
|
144
207
|
def require_required_and_optional_fields(context, opts)
|
145
208
|
if context == :all
|
146
209
|
optional_fields = Array(opts[:except])
|
@@ -152,6 +215,7 @@ module Grape
|
|
152
215
|
required_fields.each do |field|
|
153
216
|
field_opts = opts[:using][field]
|
154
217
|
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
218
|
+
|
155
219
|
requires(field, field_opts)
|
156
220
|
end
|
157
221
|
optional_fields.each do |field|
|
@@ -186,16 +250,17 @@ module Grape
|
|
186
250
|
# if required params are grouped and no type or unsupported type is provided, raise an error
|
187
251
|
type = attrs[1] ? attrs[1][:type] : nil
|
188
252
|
if attrs.first && !optional
|
189
|
-
raise Grape::Exceptions::
|
190
|
-
raise Grape::Exceptions::
|
253
|
+
raise Grape::Exceptions::MissingGroupType if type.nil?
|
254
|
+
raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)
|
191
255
|
end
|
192
256
|
|
193
257
|
self.class.new(
|
194
|
-
api:
|
195
|
-
element:
|
196
|
-
|
258
|
+
api: @api,
|
259
|
+
element: attrs.first,
|
260
|
+
element_renamed: attrs[1][:as],
|
261
|
+
parent: self,
|
197
262
|
optional: optional,
|
198
|
-
type:
|
263
|
+
type: type || Array,
|
199
264
|
&block
|
200
265
|
)
|
201
266
|
end
|
@@ -209,11 +274,11 @@ module Grape
|
|
209
274
|
# @yield parameter scope
|
210
275
|
def new_lateral_scope(options, &block)
|
211
276
|
self.class.new(
|
212
|
-
api:
|
213
|
-
element:
|
214
|
-
parent:
|
215
|
-
options:
|
216
|
-
type:
|
277
|
+
api: @api,
|
278
|
+
element: nil,
|
279
|
+
parent: self,
|
280
|
+
options: @optional,
|
281
|
+
type: type == Array ? Array : Hash,
|
217
282
|
dependent_on: options[:dependent_on],
|
218
283
|
&block
|
219
284
|
)
|
@@ -226,15 +291,17 @@ module Grape
|
|
226
291
|
# @yield parameter scope
|
227
292
|
def new_group_scope(attrs, &block)
|
228
293
|
self.class.new(
|
229
|
-
api:
|
230
|
-
parent:
|
231
|
-
group:
|
294
|
+
api: @api,
|
295
|
+
parent: self,
|
296
|
+
group: attrs.first,
|
232
297
|
&block
|
233
298
|
)
|
234
299
|
end
|
235
300
|
|
236
301
|
# Pushes declared params to parent or settings
|
237
302
|
def configure_declared_params
|
303
|
+
push_renamed_param(full_path, @element_renamed) if @element_renamed
|
304
|
+
|
238
305
|
if nested?
|
239
306
|
@parent.push_declared_params [element => @declared_params]
|
240
307
|
else
|
@@ -246,17 +313,14 @@ module Grape
|
|
246
313
|
end
|
247
314
|
|
248
315
|
def validates(attrs, validations)
|
249
|
-
|
316
|
+
doc = AttributesDoc.new @api, self
|
317
|
+
doc.extract_details validations
|
250
318
|
|
251
319
|
coerce_type = infer_coercion(validations)
|
252
320
|
|
253
|
-
|
254
|
-
|
255
|
-
desc = validations.delete(:desc) || validations.delete(:description)
|
256
|
-
doc_attrs[:desc] = desc if desc
|
321
|
+
doc.type = coerce_type
|
257
322
|
|
258
323
|
default = validations[:default]
|
259
|
-
doc_attrs[:default] = default if validations.key?(:default)
|
260
324
|
|
261
325
|
if (values_hash = validations[:values]).is_a? Hash
|
262
326
|
values = values_hash[:value]
|
@@ -265,7 +329,8 @@ module Grape
|
|
265
329
|
else
|
266
330
|
values = validations[:values]
|
267
331
|
end
|
268
|
-
|
332
|
+
|
333
|
+
doc.values = values
|
269
334
|
|
270
335
|
except_values = options_key?(:except_values, :value, validations) ? validations[:except_values][:value] : validations[:except_values]
|
271
336
|
|
@@ -281,27 +346,22 @@ module Grape
|
|
281
346
|
# type should be compatible with values array, if both exist
|
282
347
|
validate_value_coercion(coerce_type, values, except_values, excepts)
|
283
348
|
|
284
|
-
|
285
|
-
|
286
|
-
full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } }
|
287
|
-
@api.document_attribute(full_attrs, doc_attrs)
|
349
|
+
doc.document attrs
|
288
350
|
|
289
351
|
opts = derive_validator_options(validations)
|
290
352
|
|
291
353
|
# Validate for presence before any other validators
|
292
|
-
|
293
|
-
validate('presence', validations[:presence], attrs, doc_attrs, opts)
|
294
|
-
validations.delete(:presence)
|
295
|
-
validations.delete(:message) if validations.key?(:message)
|
296
|
-
end
|
354
|
+
validates_presence(validations, attrs, doc, opts)
|
297
355
|
|
298
356
|
# Before we run the rest of the validators, let's handle
|
299
357
|
# whatever coercion so that we are working with correctly
|
300
358
|
# type casted values
|
301
|
-
coerce_type validations, attrs,
|
359
|
+
coerce_type validations, attrs, doc, opts
|
302
360
|
|
303
361
|
validations.each do |type, options|
|
304
|
-
|
362
|
+
next if type == :as
|
363
|
+
|
364
|
+
validate(type, options, attrs, doc, opts)
|
305
365
|
end
|
306
366
|
end
|
307
367
|
|
@@ -319,9 +379,7 @@ module Grape
|
|
319
379
|
# @return [class-like] type to which the parameter will be coerced
|
320
380
|
# @raise [ArgumentError] if the given type options are invalid
|
321
381
|
def infer_coercion(validations)
|
322
|
-
if validations.key?(:type) && validations.key?(:types)
|
323
|
-
raise ArgumentError, ':type may not be supplied with :types'
|
324
|
-
end
|
382
|
+
raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
|
325
383
|
|
326
384
|
validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
|
327
385
|
validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
|
@@ -357,6 +415,7 @@ module Grape
|
|
357
415
|
# but not special JSON types, which
|
358
416
|
# already imply coercion method
|
359
417
|
return unless [JSON, Array[JSON]].include? validations[:coerce]
|
418
|
+
|
360
419
|
raise ArgumentError, 'coerce_with disallowed for type: JSON'
|
361
420
|
end
|
362
421
|
|
@@ -366,7 +425,7 @@ module Grape
|
|
366
425
|
# composited from more than one +requires+/+optional+
|
367
426
|
# parameter, and needs to be run before most other
|
368
427
|
# validations.
|
369
|
-
def coerce_type(validations, attrs,
|
428
|
+
def coerce_type(validations, attrs, doc, opts)
|
370
429
|
check_coerce_with(validations)
|
371
430
|
|
372
431
|
return unless validations.key?(:coerce)
|
@@ -376,7 +435,7 @@ module Grape
|
|
376
435
|
method: validations[:coerce_with],
|
377
436
|
message: validations[:coerce_message]
|
378
437
|
}
|
379
|
-
validate('coerce', coerce_options, attrs,
|
438
|
+
validate('coerce', coerce_options, attrs, doc, opts)
|
380
439
|
validations.delete(:coerce_with)
|
381
440
|
validations.delete(:coerce)
|
382
441
|
validations.delete(:coerce_message)
|
@@ -384,6 +443,7 @@ module Grape
|
|
384
443
|
|
385
444
|
def guess_coerce_type(coerce_type, *values_list)
|
386
445
|
return coerce_type unless coerce_type == Array
|
446
|
+
|
387
447
|
values_list.each do |values|
|
388
448
|
next if !values || values.is_a?(Proc)
|
389
449
|
return values.first.class if values.is_a?(Range) || !values.empty?
|
@@ -394,14 +454,11 @@ module Grape
|
|
394
454
|
def check_incompatible_option_values(default, values, except_values, excepts)
|
395
455
|
return unless default && !default.is_a?(Proc)
|
396
456
|
|
397
|
-
if values && !values.is_a?(Proc)
|
398
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
|
399
|
-
unless Array(default).all? { |def_val| values.include?(def_val) }
|
400
|
-
end
|
457
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
|
401
458
|
|
402
|
-
if except_values && !except_values.is_a?(Proc)
|
459
|
+
if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
403
460
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
|
404
|
-
|
461
|
+
|
405
462
|
end
|
406
463
|
|
407
464
|
return unless excepts && !excepts.is_a?(Proc)
|
@@ -409,39 +466,34 @@ module Grape
|
|
409
466
|
unless Array(default).none? { |def_val| excepts.include?(def_val) }
|
410
467
|
end
|
411
468
|
|
412
|
-
def validate(type, options, attrs,
|
413
|
-
validator_class = Validations.validators[type.to_s]
|
414
|
-
|
415
|
-
raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
|
416
|
-
|
469
|
+
def validate(type, options, attrs, doc, opts)
|
417
470
|
validator_options = {
|
418
|
-
attributes:
|
419
|
-
options:
|
420
|
-
required:
|
421
|
-
params_scope:
|
422
|
-
opts:
|
423
|
-
validator_class:
|
471
|
+
attributes: attrs,
|
472
|
+
options: options,
|
473
|
+
required: doc.required,
|
474
|
+
params_scope: self,
|
475
|
+
opts: opts,
|
476
|
+
validator_class: Validations.require_validator(type)
|
424
477
|
}
|
425
478
|
@api.namespace_stackable(:validations, validator_options)
|
426
479
|
end
|
427
480
|
|
428
481
|
def validate_value_coercion(coerce_type, *values_list)
|
429
482
|
return unless coerce_type
|
483
|
+
|
430
484
|
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
|
431
485
|
values_list.each do |values|
|
432
486
|
next if !values || values.is_a?(Proc)
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
unless value_types.all? { |v| v.is_a? coerce_type }
|
438
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
439
|
-
end
|
487
|
+
|
488
|
+
value_types = values.is_a?(Range) ? [values.begin, values.end].compact : values
|
489
|
+
value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
|
490
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
|
440
491
|
end
|
441
492
|
end
|
442
493
|
|
443
494
|
def extract_message_option(attrs)
|
444
495
|
return nil unless attrs.is_a?(Array)
|
496
|
+
|
445
497
|
opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
|
446
498
|
opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
|
447
499
|
end
|
@@ -461,9 +513,16 @@ module Grape
|
|
461
513
|
|
462
514
|
{
|
463
515
|
allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
|
464
|
-
fail_fast:
|
516
|
+
fail_fast: validations.delete(:fail_fast) || false
|
465
517
|
}
|
466
518
|
end
|
519
|
+
|
520
|
+
def validates_presence(validations, attrs, doc, opts)
|
521
|
+
return unless validations.key?(:presence) && validations[:presence]
|
522
|
+
|
523
|
+
validate(:presence, validations.delete(:presence), attrs, doc, opts)
|
524
|
+
validations.delete(:message) if validations.key?(:message)
|
525
|
+
end
|
467
526
|
end
|
468
527
|
end
|
469
528
|
end
|
@@ -14,8 +14,6 @@ module Grape
|
|
14
14
|
# behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`
|
15
15
|
# maintains Virtus behavior in coercing.
|
16
16
|
class ArrayCoercer < DryTypeCoercer
|
17
|
-
register_collection Array
|
18
|
-
|
19
17
|
def initialize(type, strict = false)
|
20
18
|
super
|
21
19
|
|