grape 1.3.3 → 1.6.2
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 +111 -2
- data/CONTRIBUTING.md +2 -1
- data/README.md +135 -23
- data/UPGRADING.md +237 -46
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +34 -42
- data/lib/grape/api.rb +21 -16
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +8 -5
- data/lib/grape/dsl/inside_route.rb +72 -53
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +8 -9
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +29 -42
- data/lib/grape/error_formatter/json.rb +2 -6
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/empty_message_body.rb +11 -0
- data/lib/grape/exceptions/validation.rb +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- 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 +1 -1
- data/lib/grape/middleware/auth/base.rb +3 -3
- data/lib/grape/middleware/auth/dsl.rb +7 -1
- data/lib/grape/middleware/base.rb +6 -3
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +7 -7
- data/lib/grape/middleware/stack.rb +10 -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/parser/json.rb +1 -1
- data/lib/grape/parser/xml.rb +1 -1
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +3 -3
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +31 -30
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +2 -2
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +4 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +97 -62
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +4 -5
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- data/lib/grape/validations/validators/all_or_none.rb +8 -5
- data/lib/grape/validations/validators/allow_blank.rb +9 -7
- data/lib/grape/validations/validators/as.rb +6 -8
- data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
- data/lib/grape/validations/validators/base.rb +74 -69
- data/lib/grape/validations/validators/coerce.rb +63 -76
- data/lib/grape/validations/validators/default.rb +36 -34
- data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
- data/lib/grape/validations/validators/except_values.rb +13 -11
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
- data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
- data/lib/grape/validations/validators/presence.rb +7 -4
- data/lib/grape/validations/validators/regexp.rb +8 -5
- data/lib/grape/validations/validators/same_as.rb +18 -15
- data/lib/grape/validations/validators/values.rb +61 -56
- data/lib/grape/validations.rb +6 -0
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +7 -3
- data/spec/grape/api/custom_validations_spec.rb +77 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -3
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -0
- data/spec/grape/api/recognize_path_spec.rb +1 -1
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
- data/spec/grape/api_remount_spec.rb +25 -19
- data/spec/grape/api_spec.rb +576 -211
- data/spec/grape/dsl/callbacks_spec.rb +2 -1
- data/spec/grape/dsl/headers_spec.rb +39 -9
- data/spec/grape/dsl/helpers_spec.rb +3 -2
- data/spec/grape/dsl/inside_route_spec.rb +185 -34
- data/spec/grape/dsl/logger_spec.rb +16 -18
- data/spec/grape/dsl/middleware_spec.rb +2 -1
- data/spec/grape/dsl/parameters_spec.rb +2 -0
- data/spec/grape/dsl/request_response_spec.rb +1 -0
- data/spec/grape/dsl/routing_spec.rb +10 -7
- data/spec/grape/endpoint/declared_spec.rb +848 -0
- data/spec/grape/endpoint_spec.rb +77 -589
- data/spec/grape/entity_spec.rb +29 -23
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
- data/spec/grape/exceptions/validation_spec.rb +5 -3
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
- data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
- data/spec/grape/loading_spec.rb +8 -8
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
- data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
- data/spec/grape/middleware/base_spec.rb +24 -15
- data/spec/grape/middleware/error_spec.rb +3 -3
- data/spec/grape/middleware/exception_spec.rb +111 -161
- data/spec/grape/middleware/formatter_spec.rb +28 -7
- data/spec/grape/middleware/globals_spec.rb +7 -4
- data/spec/grape/middleware/stack_spec.rb +15 -12
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
- data/spec/grape/middleware/versioner/header_spec.rb +14 -13
- data/spec/grape/middleware/versioner/param_spec.rb +7 -1
- data/spec/grape/middleware/versioner/path_spec.rb +5 -1
- data/spec/grape/middleware/versioner_spec.rb +1 -1
- data/spec/grape/parser_spec.rb +4 -0
- data/spec/grape/path_spec.rb +52 -52
- data/spec/grape/presenters/presenter_spec.rb +7 -6
- data/spec/grape/request_spec.rb +6 -4
- data/spec/grape/util/inheritable_setting_spec.rb +7 -7
- data/spec/grape/util/inheritable_values_spec.rb +3 -2
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
- data/spec/grape/util/stackable_values_spec.rb +7 -5
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
- data/spec/grape/validations/params_scope_spec.rb +72 -10
- data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
- data/spec/grape/validations/types_spec.rb +8 -8
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
- data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
- data/spec/grape/validations/validators/coerce_spec.rb +248 -33
- data/spec/grape/validations/validators/default_spec.rb +121 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
- data/spec/grape/validations/validators/except_values_spec.rb +4 -3
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
- data/spec/grape/validations/validators/presence_spec.rb +16 -1
- data/spec/grape/validations/validators/regexp_spec.rb +25 -31
- data/spec/grape/validations/validators/same_as_spec.rb +14 -20
- data/spec/grape/validations/validators/values_spec.rb +183 -178
- data/spec/grape/validations_spec.rb +342 -29
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- data/spec/integration/multi_json/json_spec.rb +1 -1
- data/spec/integration/multi_xml/xml_spec.rb +1 -1
- data/spec/shared/versioning_examples.rb +32 -29
- data/spec/spec_helper.rb +12 -12
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +110 -102
data/lib/grape/request.rb
CHANGED
@@ -8,13 +8,15 @@ module Grape
|
|
8
8
|
|
9
9
|
alias rack_params params
|
10
10
|
|
11
|
-
def initialize(env, options
|
11
|
+
def initialize(env, **options)
|
12
12
|
extend options[:build_params_with] || Grape.config.param_builder
|
13
13
|
super(env)
|
14
14
|
end
|
15
15
|
|
16
16
|
def params
|
17
17
|
@params ||= build_params
|
18
|
+
rescue EOFError
|
19
|
+
raise Grape::Exceptions::EmptyMessageBody.new(content_type)
|
18
20
|
end
|
19
21
|
|
20
22
|
def headers
|
@@ -35,6 +37,7 @@ module Grape
|
|
35
37
|
Grape::Util::LazyObject.new do
|
36
38
|
env.each_pair.with_object({}) do |(k, v), headers|
|
37
39
|
next unless k.to_s.start_with? HTTP_PREFIX
|
40
|
+
|
38
41
|
transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k)
|
39
42
|
headers[transformed_header] = v
|
40
43
|
end
|
@@ -4,7 +4,7 @@ module Grape
|
|
4
4
|
class Router
|
5
5
|
# this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251
|
6
6
|
class AttributeTranslator
|
7
|
-
attr_reader :attributes
|
7
|
+
attr_reader :attributes
|
8
8
|
|
9
9
|
ROUTE_ATTRIBUTES = %i[
|
10
10
|
prefix
|
@@ -23,7 +23,7 @@ module Grape
|
|
23
23
|
|
24
24
|
ROUTER_ATTRIBUTES = %i[pattern index].freeze
|
25
25
|
|
26
|
-
def initialize(attributes
|
26
|
+
def initialize(**attributes)
|
27
27
|
@attributes = attributes
|
28
28
|
end
|
29
29
|
|
@@ -37,7 +37,7 @@ module Grape
|
|
37
37
|
attributes
|
38
38
|
end
|
39
39
|
|
40
|
-
def method_missing(method_name, *args)
|
40
|
+
def method_missing(method_name, *args)
|
41
41
|
if setter?(method_name[-1])
|
42
42
|
attributes[method_name[0..-1]] = *args
|
43
43
|
else
|
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
@@ -7,20 +7,12 @@ module Grape
|
|
7
7
|
class Router
|
8
8
|
attr_reader :map, :compiled
|
9
9
|
|
10
|
-
class NormalizePathCache < Grape::Util::Cache
|
11
|
-
def initialize
|
12
|
-
@cache = Hash.new do |h, path|
|
13
|
-
normalized_path = +"/#{path}"
|
14
|
-
normalized_path.squeeze!('/')
|
15
|
-
normalized_path.sub!(%r{/+\Z}, '')
|
16
|
-
normalized_path = '/' if normalized_path.empty?
|
17
|
-
h[path] = -normalized_path
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
10
|
def self.normalize_path(path)
|
23
|
-
|
11
|
+
path = +"/#{path}"
|
12
|
+
path.squeeze!('/')
|
13
|
+
path.sub!(%r{/+\Z}, '')
|
14
|
+
path = '/' if path == ''
|
15
|
+
path
|
24
16
|
end
|
25
17
|
|
26
18
|
def self.supported_methods
|
@@ -36,6 +28,7 @@ module Grape
|
|
36
28
|
|
37
29
|
def compile!
|
38
30
|
return if compiled
|
31
|
+
|
39
32
|
@union = Regexp.union(@neutral_regexes)
|
40
33
|
@neutral_regexes = nil
|
41
34
|
self.class.supported_methods.each do |method|
|
@@ -55,7 +48,7 @@ module Grape
|
|
55
48
|
|
56
49
|
def associate_routes(pattern, **options)
|
57
50
|
@neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
|
58
|
-
@neutral_map << Grape::Router::AttributeTranslator.new(options
|
51
|
+
@neutral_map << Grape::Router::AttributeTranslator.new(**options, pattern: pattern, index: @neutral_map.length)
|
59
52
|
end
|
60
53
|
|
61
54
|
def call(env)
|
@@ -68,6 +61,7 @@ module Grape
|
|
68
61
|
def recognize_path(input)
|
69
62
|
any = with_optimization { greedy_match?(input) }
|
70
63
|
return if any == default_response
|
64
|
+
|
71
65
|
any.endpoint
|
72
66
|
end
|
73
67
|
|
@@ -88,6 +82,7 @@ module Grape
|
|
88
82
|
map[method].each do |route|
|
89
83
|
next if exact_route == route
|
90
84
|
next unless route.match?(input)
|
85
|
+
|
91
86
|
response = process_route(route, env)
|
92
87
|
break unless cascade?(response)
|
93
88
|
end
|
@@ -99,37 +94,35 @@ module Grape
|
|
99
94
|
response = yield(input, method)
|
100
95
|
|
101
96
|
return response if response && !(cascade = cascade?(response))
|
102
|
-
neighbor = greedy_match?(input)
|
103
97
|
|
104
|
-
|
98
|
+
last_neighbor_route = greedy_match?(input)
|
99
|
+
|
100
|
+
# If last_neighbor_route exists and request method is OPTIONS,
|
105
101
|
# return response by using #call_with_allow_headers.
|
106
|
-
return call_with_allow_headers(
|
107
|
-
env,
|
108
|
-
neighbor.allow_header,
|
109
|
-
neighbor.endpoint
|
110
|
-
) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade
|
102
|
+
return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade
|
111
103
|
|
112
104
|
route = match?(input, '*')
|
113
|
-
|
105
|
+
|
106
|
+
return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route
|
114
107
|
|
115
108
|
if route
|
116
109
|
response = process_route(route, env)
|
117
110
|
return response if response && !(cascade = cascade?(response))
|
118
111
|
end
|
119
112
|
|
120
|
-
|
113
|
+
return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route
|
114
|
+
|
115
|
+
nil
|
121
116
|
end
|
122
117
|
|
123
118
|
def process_route(route, env)
|
124
|
-
|
125
|
-
routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS]
|
126
|
-
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input)
|
119
|
+
prepare_env_from_route(env, route)
|
127
120
|
route.exec(env)
|
128
121
|
end
|
129
122
|
|
130
123
|
def make_routing_args(default_args, route, input)
|
131
124
|
args = default_args || { route_info: route }
|
132
|
-
args.merge(route.params(input))
|
125
|
+
args.merge(route.params(input) || {})
|
133
126
|
end
|
134
127
|
|
135
128
|
def extract_input_and_method(env)
|
@@ -150,19 +143,27 @@ module Grape
|
|
150
143
|
def match?(input, method)
|
151
144
|
current_regexp = @optimized_map[method]
|
152
145
|
return unless current_regexp.match(input)
|
146
|
+
|
153
147
|
last_match = Regexp.last_match
|
154
148
|
@map[method].detect { |route| last_match["_#{route.index}"] }
|
155
149
|
end
|
156
150
|
|
157
151
|
def greedy_match?(input)
|
158
152
|
return unless @union.match(input)
|
153
|
+
|
159
154
|
last_match = Regexp.last_match
|
160
155
|
@neutral_map.detect { |route| last_match["_#{route.index}"] }
|
161
156
|
end
|
162
157
|
|
163
|
-
def call_with_allow_headers(env,
|
164
|
-
env
|
165
|
-
|
158
|
+
def call_with_allow_headers(env, route)
|
159
|
+
prepare_env_from_route(env, route)
|
160
|
+
env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze
|
161
|
+
route.endpoint.call(env)
|
162
|
+
end
|
163
|
+
|
164
|
+
def prepare_env_from_route(env, route)
|
165
|
+
input, = *extract_input_and_method(env)
|
166
|
+
env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input)
|
166
167
|
end
|
167
168
|
|
168
169
|
def cascade?(response)
|
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Grape
|
4
|
-
module
|
5
|
-
# A simple class used to identify responses which represent files and do not
|
4
|
+
module ServeStream
|
5
|
+
# A simple class used to identify responses which represent streams (or files) and do not
|
6
6
|
# need to be formatted or pre-read by Rack::Response
|
7
|
-
class
|
8
|
-
attr_reader :
|
7
|
+
class StreamResponse
|
8
|
+
attr_reader :stream
|
9
9
|
|
10
|
-
# @param
|
11
|
-
def initialize(
|
12
|
-
@
|
10
|
+
# @param stream [Object]
|
11
|
+
def initialize(stream)
|
12
|
+
@stream = stream
|
13
13
|
end
|
14
14
|
|
15
15
|
# Equality provided mostly for tests.
|
16
16
|
#
|
17
17
|
# @return [Boolean]
|
18
18
|
def ==(other)
|
19
|
-
|
19
|
+
stream == other.stream
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -9,8 +9,8 @@ module Grape
|
|
9
9
|
|
10
10
|
# @param inherited_values [Object] An object implementing an interface
|
11
11
|
# of the Hash class.
|
12
|
-
def initialize(inherited_values =
|
13
|
-
@inherited_values = inherited_values
|
12
|
+
def initialize(inherited_values = nil)
|
13
|
+
@inherited_values = inherited_values || {}
|
14
14
|
@new_values = {}
|
15
15
|
end
|
16
16
|
|
@@ -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
|
@@ -4,6 +4,7 @@ module Grape
|
|
4
4
|
module Util
|
5
5
|
class LazyValue
|
6
6
|
attr_reader :access_keys
|
7
|
+
|
7
8
|
def initialize(value, access_keys = [])
|
8
9
|
@value = value
|
9
10
|
@access_keys = access_keys
|
@@ -48,9 +49,10 @@ module Grape
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def []=(key, value)
|
51
|
-
@value_hash[key] =
|
52
|
+
@value_hash[key] = case value
|
53
|
+
when Hash
|
52
54
|
LazyValueHash.new(value)
|
53
|
-
|
55
|
+
when Array
|
54
56
|
LazyValueArray.new(value)
|
55
57
|
else
|
56
58
|
LazyValue.new(value)
|
@@ -48,6 +48,14 @@ module Grape
|
|
48
48
|
def yield_attributes(_resource_params, _attrs)
|
49
49
|
raise NotImplementedError
|
50
50
|
end
|
51
|
+
|
52
|
+
# This is a special case so that we can ignore tree's where option
|
53
|
+
# values are missing lower down. Unfortunately we can remove this
|
54
|
+
# are the parameter parsing stage as they are required to ensure
|
55
|
+
# the correct indexing is maintained
|
56
|
+
def skip?(val)
|
57
|
+
val == Grape::DSL::Parameters::EmptyOptionalValue
|
58
|
+
end
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
@@ -13,6 +13,8 @@ module Grape
|
|
13
13
|
# @param opts [Hash] options for this scope
|
14
14
|
# @option opts :element [Symbol] the element that contains this scope; for
|
15
15
|
# this to be relevant, @parent must be set
|
16
|
+
# @option opts :element_renamed [Symbol, nil] whenever this scope should
|
17
|
+
# be renamed and to what, given +nil+ no renaming is done
|
16
18
|
# @option opts :parent [ParamsScope] the scope containing this scope
|
17
19
|
# @option opts :api [API] the API endpoint to modify
|
18
20
|
# @option opts :optional [Boolean] whether or not this scope needs to have
|
@@ -23,23 +25,24 @@ module Grape
|
|
23
25
|
# validate if this param is present in the parent scope
|
24
26
|
# @yield the instance context, open for parameter definitions
|
25
27
|
def initialize(opts, &block)
|
26
|
-
@element
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@
|
28
|
+
@element = opts[:element]
|
29
|
+
@element_renamed = opts[:element_renamed]
|
30
|
+
@parent = opts[:parent]
|
31
|
+
@api = opts[:api]
|
32
|
+
@optional = opts[:optional] || false
|
33
|
+
@type = opts[:type]
|
34
|
+
@group = opts[:group] || {}
|
35
|
+
@dependent_on = opts[:dependent_on]
|
33
36
|
@declared_params = []
|
34
37
|
@index = nil
|
35
38
|
|
36
|
-
instance_eval(&block) if
|
39
|
+
instance_eval(&block) if block
|
37
40
|
|
38
41
|
configure_declared_params
|
39
42
|
end
|
40
43
|
|
41
44
|
def configuration
|
42
|
-
@api.configuration.evaluate
|
45
|
+
@api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration
|
43
46
|
end
|
44
47
|
|
45
48
|
# @return [Boolean] whether or not this entire scope needs to be
|
@@ -50,18 +53,19 @@ module Grape
|
|
50
53
|
return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))
|
51
54
|
return false unless meets_dependency?(scoped_params, parameters)
|
52
55
|
return true if parent.nil?
|
56
|
+
|
53
57
|
parent.should_validate?(parameters)
|
54
58
|
end
|
55
59
|
|
56
60
|
def meets_dependency?(params, request_params)
|
57
|
-
if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
58
|
-
return false
|
59
|
-
end
|
60
|
-
|
61
61
|
return true unless @dependent_on
|
62
|
+
|
63
|
+
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
|
64
|
+
|
62
65
|
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
|
63
|
-
|
64
|
-
params
|
66
|
+
|
67
|
+
# params might be anything what looks like a hash, so it must implement a `key?` method
|
68
|
+
return false unless params.respond_to?(:key?)
|
65
69
|
|
66
70
|
@dependent_on.each do |dependency|
|
67
71
|
if dependency.is_a?(Hash)
|
@@ -127,18 +131,35 @@ module Grape
|
|
127
131
|
if lateral?
|
128
132
|
@parent.push_declared_params(attrs, **opts)
|
129
133
|
else
|
130
|
-
|
131
|
-
|
132
|
-
@api.route_setting(:renamed_params) << { attrs.first => opts[:as] }
|
133
|
-
attrs = [opts[:as]]
|
134
|
-
end
|
134
|
+
push_renamed_param(full_path + [attrs.first], opts[:as]) \
|
135
|
+
if opts && opts[:as]
|
135
136
|
|
136
137
|
@declared_params.concat attrs
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
141
|
+
# Get the full path of the parameter scope in the hierarchy.
|
142
|
+
#
|
143
|
+
# @return [Array<Symbol>] the nesting/path of the current parameter scope
|
144
|
+
def full_path
|
145
|
+
nested? ? @parent.full_path + [@element] : []
|
146
|
+
end
|
147
|
+
|
140
148
|
private
|
141
149
|
|
150
|
+
# Add a new parameter which should be renamed when using the +#declared+
|
151
|
+
# method.
|
152
|
+
#
|
153
|
+
# @param path [Array<String, Symbol>] the full path of the parameter
|
154
|
+
# (including the parameter name as last array element)
|
155
|
+
# @param new_name [String, Symbol] the new name of the parameter (the
|
156
|
+
# renamed name, with the +as: ...+ semantic)
|
157
|
+
def push_renamed_param(path, new_name)
|
158
|
+
base = @api.route_setting(:renamed_params) || {}
|
159
|
+
base[Array(path).map(&:to_s)] = new_name.to_s
|
160
|
+
@api.route_setting(:renamed_params, base)
|
161
|
+
end
|
162
|
+
|
142
163
|
def require_required_and_optional_fields(context, opts)
|
143
164
|
if context == :all
|
144
165
|
optional_fields = Array(opts[:except])
|
@@ -150,6 +171,7 @@ module Grape
|
|
150
171
|
required_fields.each do |field|
|
151
172
|
field_opts = opts[:using][field]
|
152
173
|
raise ArgumentError, "required field not exist: #{field}" unless field_opts
|
174
|
+
|
153
175
|
requires(field, field_opts)
|
154
176
|
end
|
155
177
|
optional_fields.each do |field|
|
@@ -189,11 +211,12 @@ module Grape
|
|
189
211
|
end
|
190
212
|
|
191
213
|
self.class.new(
|
192
|
-
api:
|
193
|
-
element:
|
194
|
-
|
214
|
+
api: @api,
|
215
|
+
element: attrs.first,
|
216
|
+
element_renamed: attrs[1][:as],
|
217
|
+
parent: self,
|
195
218
|
optional: optional,
|
196
|
-
type:
|
219
|
+
type: type || Array,
|
197
220
|
&block
|
198
221
|
)
|
199
222
|
end
|
@@ -207,11 +230,11 @@ module Grape
|
|
207
230
|
# @yield parameter scope
|
208
231
|
def new_lateral_scope(options, &block)
|
209
232
|
self.class.new(
|
210
|
-
api:
|
211
|
-
element:
|
212
|
-
parent:
|
213
|
-
options:
|
214
|
-
type:
|
233
|
+
api: @api,
|
234
|
+
element: nil,
|
235
|
+
parent: self,
|
236
|
+
options: @optional,
|
237
|
+
type: type == Array ? Array : Hash,
|
215
238
|
dependent_on: options[:dependent_on],
|
216
239
|
&block
|
217
240
|
)
|
@@ -224,23 +247,25 @@ module Grape
|
|
224
247
|
# @yield parameter scope
|
225
248
|
def new_group_scope(attrs, &block)
|
226
249
|
self.class.new(
|
227
|
-
api:
|
228
|
-
parent:
|
229
|
-
group:
|
250
|
+
api: @api,
|
251
|
+
parent: self,
|
252
|
+
group: attrs.first,
|
230
253
|
&block
|
231
254
|
)
|
232
255
|
end
|
233
256
|
|
234
257
|
# Pushes declared params to parent or settings
|
235
258
|
def configure_declared_params
|
259
|
+
push_renamed_param(full_path, @element_renamed) if @element_renamed
|
260
|
+
|
236
261
|
if nested?
|
237
262
|
@parent.push_declared_params [element => @declared_params]
|
238
263
|
else
|
239
264
|
@api.namespace_stackable(:declared_params, @declared_params)
|
240
|
-
|
241
|
-
@api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params)
|
242
|
-
@api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten)
|
243
265
|
end
|
266
|
+
|
267
|
+
# params were stored in settings, it can be cleaned from the params scope
|
268
|
+
@declared_params = nil
|
244
269
|
end
|
245
270
|
|
246
271
|
def validates(attrs, validations)
|
@@ -281,16 +306,15 @@ module Grape
|
|
281
306
|
|
282
307
|
doc_attrs[:documentation] = validations.delete(:documentation) if validations.key?(:documentation)
|
283
308
|
|
284
|
-
|
285
|
-
@api.document_attribute(full_attrs, doc_attrs)
|
309
|
+
document_attribute(attrs, doc_attrs)
|
286
310
|
|
287
311
|
opts = derive_validator_options(validations)
|
288
312
|
|
313
|
+
order_specific_validations = Set[:as]
|
314
|
+
|
289
315
|
# Validate for presence before any other validators
|
290
|
-
|
291
|
-
|
292
|
-
validations.delete(:presence)
|
293
|
-
validations.delete(:message) if validations.key?(:message)
|
316
|
+
validates_presence(validations, attrs, doc_attrs, opts) do |validation_type|
|
317
|
+
order_specific_validations << validation_type
|
294
318
|
end
|
295
319
|
|
296
320
|
# Before we run the rest of the validators, let's handle
|
@@ -299,6 +323,8 @@ module Grape
|
|
299
323
|
coerce_type validations, attrs, doc_attrs, opts
|
300
324
|
|
301
325
|
validations.each do |type, options|
|
326
|
+
next if order_specific_validations.include?(type)
|
327
|
+
|
302
328
|
validate(type, options, attrs, doc_attrs, opts)
|
303
329
|
end
|
304
330
|
end
|
@@ -317,9 +343,7 @@ module Grape
|
|
317
343
|
# @return [class-like] type to which the parameter will be coerced
|
318
344
|
# @raise [ArgumentError] if the given type options are invalid
|
319
345
|
def infer_coercion(validations)
|
320
|
-
if validations.key?(:type) && validations.key?(:types)
|
321
|
-
raise ArgumentError, ':type may not be supplied with :types'
|
322
|
-
end
|
346
|
+
raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)
|
323
347
|
|
324
348
|
validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)
|
325
349
|
validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)
|
@@ -355,6 +379,7 @@ module Grape
|
|
355
379
|
# but not special JSON types, which
|
356
380
|
# already imply coercion method
|
357
381
|
return unless [JSON, Array[JSON]].include? validations[:coerce]
|
382
|
+
|
358
383
|
raise ArgumentError, 'coerce_with disallowed for type: JSON'
|
359
384
|
end
|
360
385
|
|
@@ -382,6 +407,7 @@ module Grape
|
|
382
407
|
|
383
408
|
def guess_coerce_type(coerce_type, *values_list)
|
384
409
|
return coerce_type unless coerce_type == Array
|
410
|
+
|
385
411
|
values_list.each do |values|
|
386
412
|
next if !values || values.is_a?(Proc)
|
387
413
|
return values.first.class if values.is_a?(Range) || !values.empty?
|
@@ -392,14 +418,11 @@ module Grape
|
|
392
418
|
def check_incompatible_option_values(default, values, except_values, excepts)
|
393
419
|
return unless default && !default.is_a?(Proc)
|
394
420
|
|
395
|
-
if values && !values.is_a?(Proc)
|
396
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) \
|
397
|
-
unless Array(default).all? { |def_val| values.include?(def_val) }
|
398
|
-
end
|
421
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }
|
399
422
|
|
400
|
-
if except_values && !except_values.is_a?(Proc)
|
423
|
+
if except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }
|
401
424
|
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values) \
|
402
|
-
|
425
|
+
|
403
426
|
end
|
404
427
|
|
405
428
|
return unless excepts && !excepts.is_a?(Proc)
|
@@ -413,11 +436,11 @@ module Grape
|
|
413
436
|
raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class
|
414
437
|
|
415
438
|
validator_options = {
|
416
|
-
attributes:
|
417
|
-
options:
|
418
|
-
required:
|
419
|
-
params_scope:
|
420
|
-
opts:
|
439
|
+
attributes: attrs,
|
440
|
+
options: options,
|
441
|
+
required: doc_attrs[:required],
|
442
|
+
params_scope: self,
|
443
|
+
opts: opts,
|
421
444
|
validator_class: validator_class
|
422
445
|
}
|
423
446
|
@api.namespace_stackable(:validations, validator_options)
|
@@ -425,21 +448,20 @@ module Grape
|
|
425
448
|
|
426
449
|
def validate_value_coercion(coerce_type, *values_list)
|
427
450
|
return unless coerce_type
|
451
|
+
|
428
452
|
coerce_type = coerce_type.first if coerce_type.is_a?(Array)
|
429
453
|
values_list.each do |values|
|
430
454
|
next if !values || values.is_a?(Proc)
|
455
|
+
|
431
456
|
value_types = values.is_a?(Range) ? [values.begin, values.end] : values
|
432
|
-
if coerce_type == Grape::API::Boolean
|
433
|
-
|
434
|
-
end
|
435
|
-
unless value_types.all? { |v| v.is_a? coerce_type }
|
436
|
-
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values)
|
437
|
-
end
|
457
|
+
value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean
|
458
|
+
raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)
|
438
459
|
end
|
439
460
|
end
|
440
461
|
|
441
462
|
def extract_message_option(attrs)
|
442
463
|
return nil unless attrs.is_a?(Array)
|
464
|
+
|
443
465
|
opts = attrs.last.is_a?(Hash) ? attrs.pop : {}
|
444
466
|
opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil
|
445
467
|
end
|
@@ -459,9 +481,22 @@ module Grape
|
|
459
481
|
|
460
482
|
{
|
461
483
|
allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,
|
462
|
-
fail_fast:
|
484
|
+
fail_fast: validations.delete(:fail_fast) || false
|
463
485
|
}
|
464
486
|
end
|
487
|
+
|
488
|
+
def validates_presence(validations, attrs, doc_attrs, opts)
|
489
|
+
return unless validations.key?(:presence) && validations[:presence]
|
490
|
+
|
491
|
+
validate(:presence, validations[:presence], attrs, doc_attrs, opts)
|
492
|
+
yield :presence
|
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)
|
499
|
+
end
|
465
500
|
end
|
466
501
|
end
|
467
502
|
end
|