grape 1.3.3 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|