grape 1.5.3 → 1.7.1
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 +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
|
|